From 41c5a84dacfd036b8d8f01f72226ac5a519995e3 Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 14 May 2024 17:49:09 +0200 Subject: Organise tools somewhat, set proper icons for nav menu --- LibMatrix | 2 +- MatrixRoomUtils.sln | 6 - MatrixUtils.Web/MatrixUtils.Web.csproj | 21 +- .../Client/ClientComponents/ClientRoomList.razor | 15 -- .../Client/ClientComponents/ClientStatusList.razor | 35 --- .../Client/ClientComponents/ClientSyncWrapper.cs | 41 --- .../Client/ClientComponents/MatrixClient.razor | 31 --- MatrixUtils.Web/Pages/Client/Index.razor | 72 ------ MatrixUtils.Web/Pages/Dev/ModalTest.razor | 88 ------- MatrixUtils.Web/Pages/Index.razor | 2 +- .../Client/ClientComponents/ClientRoomList.razor | 15 ++ .../Client/ClientComponents/ClientStatusList.razor | 35 +++ .../Client/ClientComponents/ClientSyncWrapper.cs | 41 +++ .../Client/ClientComponents/MatrixClient.razor | 31 +++ MatrixUtils.Web/Pages/Labs/Client/Index.razor | 72 ++++++ MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor | 104 ++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor | 11 + .../Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor | 151 +++++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor | 240 ++++++++++++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor | 191 ++++++++++++++ MatrixUtils.Web/Pages/Labs/Index.razor | 11 + MatrixUtils.Web/Pages/Labs/Index.razor.css | 0 MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor | 87 +++++++ .../MainTabComponents/MainTabSpaceItem.razor | 56 +++++ .../MainTabComponents/MainTabSpaceItem.razor.css | 29 +++ .../RoomsIndex2ByRoomTypeTab.razor | 53 ++++ .../Index2Components/RoomsIndex2DMsTab.razor | 53 ++++ .../Index2Components/RoomsIndex2MainTab.razor | 206 +++++++++++++++ .../Index2Components/RoomsIndex2MainTab.razor.css | 0 .../RoomsIndex2SyncContainer.razor | 201 +++++++++++++++ .../Moderation/DraupnirProtectedRoomsEditor.razor | 99 -------- MatrixUtils.Web/Pages/Rooms/Index2.razor | 87 ------- .../MainTabComponents/MainTabSpaceItem.razor | 56 ----- .../MainTabComponents/MainTabSpaceItem.razor.css | 29 --- .../RoomsIndex2ByRoomTypeTab.razor | 53 ---- .../Rooms/Index2Components/RoomsIndex2DMsTab.razor | 53 ---- .../Index2Components/RoomsIndex2MainTab.razor | 210 ---------------- .../Index2Components/RoomsIndex2MainTab.razor.css | 0 .../RoomsIndex2SyncContainer.razor | 202 --------------- MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor | 82 ------ MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor | 52 ++++ .../Pages/Tools/Debug/MediaLocator.razor | 109 ++++++++ .../Pages/Tools/Debug/MigrateRoom.razor | 103 ++++++++ MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor | 114 +++++++++ MatrixUtils.Web/Pages/Tools/Index.razor | 39 ++- MatrixUtils.Web/Pages/Tools/Index.razor.css | 6 + .../Pages/Tools/Info/KnownHomeserverList.razor | 51 ++++ .../Pages/Tools/Info/PolicyListActivity.razor | 158 ++++++++++++ .../Pages/Tools/Info/PolicyListActivity.razor.css | 12 + .../Pages/Tools/Info/SessionCount.razor | 155 ++++++++++++ MatrixUtils.Web/Pages/Tools/InviteCounter.razor | 73 ------ .../Pages/Tools/KnownHomeserverList.razor | 51 ---- MatrixUtils.Web/Pages/Tools/LeaveRoom.razor | 52 ---- MatrixUtils.Web/Pages/Tools/MassCMEBan.razor | 75 ------ MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor | 107 -------- MatrixUtils.Web/Pages/Tools/MediaLocator.razor | 109 -------- MatrixUtils.Web/Pages/Tools/MigrateRoom.razor | 103 -------- .../Moderation/DraupnirProtectedRoomsEditor.razor | 102 ++++++++ .../Pages/Tools/Moderation/InviteCounter.razor | 68 +++++ .../Pages/Tools/Moderation/MassCMEBan.razor | 69 ++++++ .../Pages/Tools/Moderation/MembershipHistory.razor | 276 +++++++++++++++++++++ .../Pages/Tools/Moderation/RoomIntersections.razor | 197 +++++++++++++++ .../Pages/Tools/Moderation/UserTrace.razor | 194 +++++++++++++++ .../Pages/Tools/PolicyListActivity.razor | 158 ------------ .../Pages/Tools/PolicyListActivity.razor.css | 12 - .../Pages/Tools/RoomIntersections.razor | 197 --------------- MatrixUtils.Web/Pages/Tools/SessionCount.razor | 155 ------------ MatrixUtils.Web/Pages/Tools/SpaceDebug.razor | 114 --------- .../Pages/Tools/User/CopyPowerlevel.razor | 82 ++++++ .../Pages/Tools/User/MassJoinRoom.razor | 107 ++++++++ .../Pages/Tools/User/ViewAccountData.razor | 27 ++ MatrixUtils.Web/Pages/Tools/UserTrace.razor | 198 --------------- MatrixUtils.Web/Pages/Tools/ViewAccountData.razor | 27 -- MatrixUtils.Web/Pages/User/DMSpace.razor | 104 -------- .../Pages/User/DMSpaceStages/DMSpaceStage0.razor | 11 - .../Pages/User/DMSpaceStages/DMSpaceStage1.razor | 151 ----------- .../Pages/User/DMSpaceStages/DMSpaceStage2.razor | 240 ------------------ .../Pages/User/DMSpaceStages/DMSpaceStage3.razor | 191 -------------- MatrixUtils.Web/Shared/NavMenu.razor | 20 +- MatrixUtils.Web/Shared/NavMenu.razor.css | 5 + 80 files changed, 3540 insertions(+), 3305 deletions(-) delete mode 100644 MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor delete mode 100644 MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor delete mode 100644 MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs delete mode 100644 MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor delete mode 100644 MatrixUtils.Web/Pages/Client/Index.razor delete mode 100644 MatrixUtils.Web/Pages/Dev/ModalTest.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs create mode 100644 MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Client/Index.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Index.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Index.razor.css create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css create mode 100644 MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor delete mode 100644 MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css delete mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Index.razor.css create mode 100644 MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css create mode 100644 MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/InviteCounter.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/LeaveRoom.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/MassCMEBan.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/MediaLocator.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/MigrateRoom.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css delete mode 100644 MatrixUtils.Web/Pages/Tools/RoomIntersections.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/SessionCount.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/SpaceDebug.razor create mode 100644 MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor create mode 100644 MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor create mode 100644 MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/UserTrace.razor delete mode 100644 MatrixUtils.Web/Pages/Tools/ViewAccountData.razor delete mode 100644 MatrixUtils.Web/Pages/User/DMSpace.razor delete mode 100644 MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor delete mode 100644 MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor delete mode 100644 MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor delete mode 100644 MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor diff --git a/LibMatrix b/LibMatrix index 896ee7f..b5860ce 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit 896ee7f099f817e8cc9aba96a9db00fcce671632 +Subproject commit b5860ce2011b96a2919d5306445b0e8bd8408b3d diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln index 08f236d..f52a446 100644 --- a/MatrixRoomUtils.sln +++ b/MatrixRoomUtils.sln @@ -60,8 +60,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "Lib 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}") = "BlazorApp1", "tmp\BlazorApp1\BlazorApp1.csproj", "{2057E543-84D9-483A-9A14-6399E7062419}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -164,10 +162,6 @@ Global {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 - {2057E543-84D9-483A-9A14-6399E7062419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2057E543-84D9-483A-9A14-6399E7062419}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2057E543-84D9-483A-9A14-6399E7062419}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2057E543-84D9-483A-9A14-6399E7062419}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F4E241C3-0300-4B87-8707-BCBDEF1F0185} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index c2732de..f27ae40 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -48,7 +48,26 @@ - + <_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" /> + + + + + + + + + + + + + + + + diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor deleted file mode 100644 index 845f30d..0000000 --- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor +++ /dev/null @@ -1,15 +0,0 @@ -@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext -@* user header and room list *@ -@foreach (var room in Data.SyncWrapper.Rooms) { - - @room.RoomName - -
-} - -@code { - - [Parameter] - public ClientContext Data { get; set; } = null!; - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor deleted file mode 100644 index 1100c98..0000000 --- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor +++ /dev/null @@ -1,35 +0,0 @@ -@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext; -@using System.Collections.ObjectModel - -@foreach (var ctx in Data) { -
-        @ctx.Homeserver.UserId - @ctx.SyncWrapper.Status
-    
-} - -@code { - - [Parameter] - public ObservableCollection Data { get; set; } = null!; - - protected override void OnInitialized() { - Data.CollectionChanged += (_, e) => { - foreach (var item in e.NewItems?.Cast() ?? []) { - item.SyncWrapper.PropertyChanged += (_, pe) => { - if (pe.PropertyName == nameof(item.SyncWrapper.Status)) - StateHasChanged(); - }; - } - - StateHasChanged(); - }; - - Data.ToList().ForEach(ctx => { - ctx.SyncWrapper.PropertyChanged += (_, pe) => { - if (pe.PropertyName == nameof(ctx.SyncWrapper.Status)) - StateHasChanged(); - }; - }); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs deleted file mode 100644 index 16051b8..0000000 --- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.ObjectModel; -using ArcaneLibs; -using LibMatrix; -using LibMatrix.Helpers; -using LibMatrix.Homeservers; -using LibMatrix.Responses; -using MatrixUtils.Abstractions; - -namespace MatrixUtils.Web.Pages.Client.ClientComponents; - -public class ClientSyncWrapper(AuthenticatedHomeserverGeneric homeserver) : NotifyPropertyChanged { - private SyncHelper _syncHelper = new SyncHelper(homeserver) { - MinimumDelay = TimeSpan.FromMilliseconds(2000), - IsInitialSync = false - }; - private string _status = "Loading..."; - - public ObservableCollection AccountData { get; set; } = new(); - public ObservableCollection Rooms { get; set; } = new(); - - public string Status { - get => _status; - set => SetField(ref _status, value); - } - - public async Task Start() { - Task.Yield(); - var resp = _syncHelper.EnumerateSyncAsync(); - Status = $"[{DateTime.Now:s}] Syncing..."; - await foreach (var response in resp) { - Task.Yield(); - Status = $"[{DateTime.Now:s}] {response.Rooms?.Join?.Count ?? 0 + response.Rooms?.Invite?.Count ?? 0 + response.Rooms?.Leave?.Count ?? 0} rooms, {response.AccountData?.Events?.Count ?? 0} account data, {response.ToDevice?.Events?.Count ?? 0} to-device, {response.DeviceLists?.Changed?.Count ?? 0} device lists, {response.Presence?.Events?.Count ?? 0} presence updates"; - await HandleSyncResponse(response); - await Task.Yield(); - } - } - - private async Task HandleSyncResponse(SyncResponse resp) { - - } -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor deleted file mode 100644 index b4a81f7..0000000 --- a/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor +++ /dev/null @@ -1,31 +0,0 @@ -@using Index = MatrixUtils.Web.Pages.Client.Index -@using MatrixUtils.Web.Pages.Client.ClientComponents - -
-
-
- -
-
- @if (Data.SelectedRoom != null) { - - - } - else { -

No room selected

- } -
- @if (Data.SelectedRoom != null) { -
- -
- } -
-
- -@code { - - [Parameter] - public Index.ClientContext Data { get; set; } = null!; - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/Index.razor b/MatrixUtils.Web/Pages/Client/Index.razor deleted file mode 100644 index 2a9a327..0000000 --- a/MatrixUtils.Web/Pages/Client/Index.razor +++ /dev/null @@ -1,72 +0,0 @@ -@page "/Client" -@using LibMatrix -@using MatrixUtils.Abstractions -@using MatrixUtils.Web.Pages.Client.ClientComponents -@using System.Collections.ObjectModel - -

Client

- - -@foreach (var client in Clients) { - - @client.Homeserver.WhoAmI.UserId - -} - - - -@* @foreach (var client in Clients) { *@ -@*
*@ -@* @client.Homeserver.UserId - @client.SyncWrapper.Status *@ -@*
*@ -@* } *@ - -@if (SelectedClient != null) { -
- -
-} - -@code { - - private static readonly ObservableCollection Clients = []; - private static ClientContext _selectedClient; - - private ClientContext SelectedClient { - get => _selectedClient; - set { - _selectedClient = value; - StateHasChanged(); - } - } - - protected override async Task OnInitializedAsync() { - var tokens = await RMUStorage.GetAllTokens(); - var tasks = tokens.Select(async token => { - try { - var cc = new ClientContext() { - Homeserver = await RMUStorage.GetSession(token) - }; - cc.SyncWrapper = new ClientSyncWrapper(cc.Homeserver); - -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - cc.SyncWrapper.Start(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - - Clients.Add(cc); - StateHasChanged(); - } - catch { } - }).ToList(); - await Task.WhenAll(tasks); - } - - public class ClientContext { - public AuthenticatedHomeserverGeneric Homeserver { get; set; } - public ClientSyncWrapper SyncWrapper { get; set; } - - public RoomInfo? SelectedRoom { get; set; } - } - -} - diff --git a/MatrixUtils.Web/Pages/Dev/ModalTest.razor b/MatrixUtils.Web/Pages/Dev/ModalTest.razor deleted file mode 100644 index 4a0487f..0000000 --- a/MatrixUtils.Web/Pages/Dev/ModalTest.razor +++ /dev/null @@ -1,88 +0,0 @@ -@page "/Dev/ModalTest" -@inject IJSRuntime JsRuntime -

ModalTest

- -@foreach (var (key, value) in _windowInfos) { - @* @value.Content *@ -} -@for (var i = 0; i < 5; i++) { - var i1 = i; - - @for (var j = 0; j < i1; j++) { -

@j

- } -
-} - -@code { - - private Dictionary _windowInfos = new(); - - private class WindowInfo { - public double X; - public double Y; - public string Title; - public RenderFragment Content; - } - - protected override async Task OnInitializedAsync() { - double _x = 2; - double _xv = 20; - double _y = 0; - double multiplier = 1; - - for (var i = 0; i < 200; i++) { - var i1 = i; - _windowInfos.Add(_windowInfos.Count, new WindowInfo { - X = _x, - Y = _y, - Title = "Win" + i1, - Content = builder => { - builder.OpenComponent(0); - builder.AddAttribute(1, "X", _x); - builder.AddAttribute(2, "Y", _y); - builder.AddAttribute(3, "Title", "Win" + i1); - builder.AddAttribute(4, "ChildContent", (RenderFragment)(builder2 => { - builder2.OpenElement(0, "h1"); - builder2.AddContent(1, "Hello " + i1); - builder2.CloseElement(); - })); - builder.CloseComponent(); - } - }); - //_x += _xv /= 1000/System.Math.Sqrt((double)_windowInfos.Count)*_windowInfos.Count.ToString().Length*multiplier; - _y += 20; - _x += 20; - var dimension = await JsRuntime.InvokeAsync("getWindowDimensions"); - if (_x > dimension.Width - 100) _x %= dimension.Width - 100; - if (_y > dimension.Height - 50) { - _y %= dimension.Height - 50; - _xv = 20; - } - if ( - (_windowInfos.Count < 10 && _windowInfos.Count % 2 == 0) || - (_windowInfos.Count < 100 && _windowInfos.Count % 10 == 0) || - (_windowInfos.Count < 1000 && _windowInfos.Count % 50 == 0) || - (_windowInfos.Count < 10000 && _windowInfos.Count % 100 == 0) - ) { - StateHasChanged(); - await Task.Delay(25); - } - if(_windowInfos.Count > 750) multiplier = 2; - if(_windowInfos.Count > 1500) multiplier = 3; - - } - - await base.OnInitializedAsync(); - } - - private void OnCloseClicked(int i1) { - Console.WriteLine("Close clicked on " + i1); - } - - public class WindowDimension { - public int Width { get; set; } - public int Height { get; set; } - } - -} diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor index b3dc7d4..8dfd1be 100644 --- a/MatrixUtils.Web/Pages/Index.razor +++ b/MatrixUtils.Web/Pages/Index.razor @@ -186,7 +186,7 @@ Small collection of tools to do not-so-everyday things. ServerVersion = await (serverVersionTask ?? Task.FromResult(null)!), Homeserver = hs }); - if (updateSw.ElapsedMilliseconds > 250) { + if (updateSw.ElapsedMilliseconds > 25) { updateSw.Restart(); StateHasChanged(); } diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor new file mode 100644 index 0000000..b370080 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor @@ -0,0 +1,15 @@ +@using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext +@* user header and room list *@ +@foreach (var room in Data.SyncWrapper.Rooms) { + + @room.RoomName + +
+} + +@code { + + [Parameter] + public ClientContext Data { get; set; } = null!; + +} \ 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 new file mode 100644 index 0000000..c680c13 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor @@ -0,0 +1,35 @@ +@using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext; +@using System.Collections.ObjectModel + +@foreach (var ctx in Data) { +
+        @ctx.Homeserver.UserId - @ctx.SyncWrapper.Status
+    
+} + +@code { + + [Parameter] + public ObservableCollection Data { get; set; } = null!; + + protected override void OnInitialized() { + Data.CollectionChanged += (_, e) => { + foreach (var item in e.NewItems?.Cast() ?? []) { + item.SyncWrapper.PropertyChanged += (_, pe) => { + if (pe.PropertyName == nameof(item.SyncWrapper.Status)) + StateHasChanged(); + }; + } + + StateHasChanged(); + }; + + Data.ToList().ForEach(ctx => { + ctx.SyncWrapper.PropertyChanged += (_, pe) => { + if (pe.PropertyName == nameof(ctx.SyncWrapper.Status)) + StateHasChanged(); + }; + }); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs new file mode 100644 index 0000000..16051b8 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs @@ -0,0 +1,41 @@ +using System.Collections.ObjectModel; +using ArcaneLibs; +using LibMatrix; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using MatrixUtils.Abstractions; + +namespace MatrixUtils.Web.Pages.Client.ClientComponents; + +public class ClientSyncWrapper(AuthenticatedHomeserverGeneric homeserver) : NotifyPropertyChanged { + private SyncHelper _syncHelper = new SyncHelper(homeserver) { + MinimumDelay = TimeSpan.FromMilliseconds(2000), + IsInitialSync = false + }; + private string _status = "Loading..."; + + public ObservableCollection AccountData { get; set; } = new(); + public ObservableCollection Rooms { get; set; } = new(); + + public string Status { + get => _status; + set => SetField(ref _status, value); + } + + public async Task Start() { + Task.Yield(); + var resp = _syncHelper.EnumerateSyncAsync(); + Status = $"[{DateTime.Now:s}] Syncing..."; + await foreach (var response in resp) { + Task.Yield(); + Status = $"[{DateTime.Now:s}] {response.Rooms?.Join?.Count ?? 0 + response.Rooms?.Invite?.Count ?? 0 + response.Rooms?.Leave?.Count ?? 0} rooms, {response.AccountData?.Events?.Count ?? 0} account data, {response.ToDevice?.Events?.Count ?? 0} to-device, {response.DeviceLists?.Changed?.Count ?? 0} device lists, {response.Presence?.Events?.Count ?? 0} presence updates"; + await HandleSyncResponse(response); + await Task.Yield(); + } + } + + private async Task HandleSyncResponse(SyncResponse resp) { + + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor new file mode 100644 index 0000000..7d3e52a --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor @@ -0,0 +1,31 @@ +@using Index = MatrixUtils.Web.Pages.Labs.Client.Index +@using MatrixUtils.Web.Pages.Client.ClientComponents + +
+
+
+ +
+
+ @if (Data.SelectedRoom != null) { + + + } + else { +

No room selected

+ } +
+ @if (Data.SelectedRoom != null) { +
+ +
+ } +
+
+ +@code { + + [Parameter] + public Index.ClientContext Data { get; set; } = null!; + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Client/Index.razor b/MatrixUtils.Web/Pages/Labs/Client/Index.razor new file mode 100644 index 0000000..5b489b0 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Client/Index.razor @@ -0,0 +1,72 @@ +@page "/Labs/Client" +@using LibMatrix +@using MatrixUtils.Abstractions +@using MatrixUtils.Web.Pages.Client.ClientComponents +@using System.Collections.ObjectModel + +

Client

+ + +@foreach (var client in Clients) { + + @client.Homeserver.WhoAmI.UserId + +} + + + +@* @foreach (var client in Clients) { *@ +@*
*@ +@* @client.Homeserver.UserId - @client.SyncWrapper.Status *@ +@*
*@ +@* } *@ + +@if (SelectedClient != null) { +
+ +
+} + +@code { + + private static readonly ObservableCollection Clients = []; + private static ClientContext _selectedClient; + + private ClientContext SelectedClient { + get => _selectedClient; + set { + _selectedClient = value; + StateHasChanged(); + } + } + + protected override async Task OnInitializedAsync() { + var tokens = await RMUStorage.GetAllTokens(); + var tasks = tokens.Select(async token => { + try { + var cc = new ClientContext() { + Homeserver = await RMUStorage.GetSession(token) + }; + cc.SyncWrapper = new ClientSyncWrapper(cc.Homeserver); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + cc.SyncWrapper.Start(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + Clients.Add(cc); + StateHasChanged(); + } + catch { } + }).ToList(); + await Task.WhenAll(tasks); + } + + public class ClientContext { + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + public ClientSyncWrapper SyncWrapper { get; set; } + + public RoomInfo? SelectedRoom { get; set; } + } + +} + diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor new file mode 100644 index 0000000..c0dc8a6 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor @@ -0,0 +1,104 @@ +@page "/Labs/DMSpace/Setup" +@using LibMatrix +@using LibMatrix.Responses +@using MatrixUtils.Abstractions +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using MatrixUtils.Web.Pages.Labs.DMSpace.DMSpaceStages +@using System.Text.Json.Serialization +

DM Space Management

+
+ + @switch (Stage) { + case -1: +

Initialising...

+ break; + case 0: + + break; + case 1: + + break; + case 2: + + break; + case 3: + + break; + default: +

Stage is unknown value: @Stage!

+ break; + } +
+ +@code { + private int _stage = -1; + + [Parameter, SupplyParameterFromQuery(Name = "stage")] + public int Stage { + get => _stage; + set { + _stage = value; + Console.WriteLine($"Stage is now {value}"); + StateHasChanged(); + } + } + + + public DMSpace? DMSpaceRootPage { get; set; } + + protected override async Task OnInitializedAsync() { + if (NavigationManager.Uri.Contains("?stage=")) { + NavigationManager.NavigateTo(NavigationManager.Uri.Replace("stage=", ""), true); //"/User/DMSpace/Setup" + } + DMSpaceRootPage = this; + SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + if (SetupData.Homeserver is null) return; + try { + SetupData.DmSpaceConfiguration = await SetupData.Homeserver.GetAccountDataAsync("gay.rory.dm_space"); + var room = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await room.GetStateAsync(DMSpaceInfo.EventId); + Stage = 1; + } + catch (MatrixException e) { + if (e.ErrorCode is "M_NOT_FOUND" or "M_FORBIDDEN") { + Stage = 0; + SetupData.DmSpaceConfiguration = new(); + } + else throw; + } + finally { + StateHasChanged(); + } + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() { + StateHasChanged(); + await base.OnParametersSetAsync(); + } + + public DMSpaceSetupData SetupData { get; set; } = new(); + + public class DMSpaceSetupData { + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } + + public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + + public Dictionary? Spaces; + + public Dictionary>? DMRooms; + + public RoomInfo? DMSpaceRoomInfo { get; set; } + + + public class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor new file mode 100644 index 0000000..5f6508c --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor @@ -0,0 +1,11 @@ + + Welcome to the DM Space tool! + +

This wizard will help you set up a DM space.

+

This is useful for eg. sharing DM rooms across multiple accounts.

+
+Get started + +@code { + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor new file mode 100644 index 0000000..2176467 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor @@ -0,0 +1,151 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using Microsoft.Extensions.Primitives +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using MatrixUtils.Abstractions + + DM Space setup tool - stage 1: Configure space + +

You will need a space to use for DM rooms.

+@if (SetupData is not null) { + if (SetupData.Spaces is not null) { +

+ Selected space: + + + @foreach (var (id, roomInfo) in SetupData.Spaces) { + + } + +

+

+ + Create sub-spaces per user +

+ +
+ Disband + Next + } + else { +

Discovering spaces, please wait...

+ } +} +else { + Error: Setup data is null! +} + + +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + SemaphoreSlim _semaphoreSlim = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + + await _semaphoreSlim.WaitAsync(); + + Dictionary spaces = []; + SetupData.DmSpaceConfiguration ??= new(); + + Status = "Looking for spaces..."; + var userRoomsEnum = SetupData.Homeserver!.GetJoinedRoomsByType("m.space"); + + List userRooms = new(); + await foreach (var room in userRoomsEnum) { + userRooms.Add(room); + } + + var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); + await foreach (var room in roomChecks) + if (room.HasValue) + spaces.TryAdd(room.Value.id, room.Value.roomInfo); + + SetupData.Spaces = spaces; + + Status = "Done!"; + _semaphoreSlim.Release(); + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + if (string.IsNullOrWhiteSpace(SetupData!.DmSpaceConfiguration!.DMSpaceId)) { + var createRoomRequest = CreateRoomRequest.CreatePrivate(SetupData.Homeserver!, "Direct Messages"); + createRoomRequest.CreationContentBaseType.Type = "m.space"; + SetupData.DmSpaceConfiguration.DMSpaceId = (await SetupData.Homeserver!.CreateRoom(createRoomRequest)).RoomId; + } + + await SetupData.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, SetupData.DmSpaceConfiguration); + var space = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await space.SendStateEventAsync(DMSpaceInfo.EventId, SetupData.DmSpaceInfo); + SetupData.DMSpaceRoomInfo = new RoomInfo(space); + await SetupData.DMSpaceRoomInfo.FetchAllStateAsync(); + + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); + } + + public async Task<(string id, RoomInfo roomInfo)?> GetFeasibleSpaces(GenericRoom room) { + try { + var ri = new RoomInfo(room); + + await foreach(var evt in room.GetFullStateAsync()) + ri.StateEvents.Add(evt); + + var powerLevels = (await ri.GetStateEvent(RoomPowerLevelEventContent.EventId)).TypedContent as RoomPowerLevelEventContent; + if (!powerLevels.UserHasStatePermission(SetupData.Homeserver.WhoAmI.UserId, SpaceChildEventContent.EventId)) { + Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); + return null; + } + + Status = $"Found viable space: {ri.RoomName}"; + if (!string.IsNullOrWhiteSpace(SetupData.DmSpaceConfiguration!.DMSpaceId)) { + if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is { } dsi) { + SetupData.DmSpaceConfiguration.DMSpaceId = room.RoomId; + SetupData.DmSpaceInfo = dsi; + Console.WriteLine(dsi.ToJson(ignoreNull: true)); + } + } + + if (ri.RoomName == room.RoomId) + ri.RoomName = await room.GetNameOrFallbackAsync(); + + return (room.RoomId, ri); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); + else throw; + } + + return null; + } + + private async Task Disband() { + var space = new DMSpaceRoom(SetupData.Homeserver, SetupData.DmSpaceConfiguration.DMSpaceId); + await space.DisbandDMSpace(); + NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor new file mode 100644 index 0000000..a70e9c5 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor @@ -0,0 +1,240 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization +@using MatrixUtils.Abstractions + + DM Space setup tool - stage 2: Fix DM room attribution + +

This is just to make sure that your DMs are attributed to the right person!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (SetupData is not null) { + if (SetupData.DMRooms is { Count: > 0 }) { + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.Id)) { + + @foreach (var roomInfo in room) { + + Reassign + + } + } + } + else { +

DM room list is loading, please wait...

+ } +} +else { + Error: DMSpaceRootPage is null! +} + +
+Next + +@{ + var _offset = 0; +} +@foreach (var (room, usersList) in duplicateDmRooms) { + +

Found room assigned to multiple users:

+

Users:

+ @foreach (var userProfileResponse in usersList) { + + Assign to + + +
+ } +
+} + +@if (DmToReassign is not null) { + + + @foreach (var userProfileResponse in roomMembers[DmToReassign]) { + + Assign to + + +
+ } +
+} + +@code { + + private string newMxid { get; set; } = ""; + + private RoomInfo? DmToReassign { + get => _dmToReassign; + set { + _dmToReassign = value; + StateHasChanged(); + } + } + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + private RoomInfo? _dmToReassign; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + private Dictionary> duplicateDmRooms { get; set; } = new(); + private Dictionary> roomMembers { get; set; } = new(); + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + await _semaphore.WaitAsync(); + DmToReassign = null; + var hs = SetupData.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Optimising DM list from account data..."; + var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); + foreach (var (user, rooms) in dms) { + for (var i = rooms.Count - 1; i >= 0; i--) { + var roomId = rooms[i]; + if (!joinedRooms.Contains(roomId)) + rooms.RemoveAt(i); + } + + dms[user] = rooms.Distinct().ToList(); + } + + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); + + Status = "DM list optimised, fetching info..."; + + SetupData.DMRooms = new Dictionary>(); + + var results = dms.Select(async x => { + var (userId, rooms) = x; + DMSpace.DMSpaceSetupData.UserProfileWithId userProfile; + try { + var profile = await SetupData.Homeserver.GetProfileAsync(userId); + userProfile = new() { + AvatarUrl = profile.AvatarUrl, + Id = userId, + DisplayName = profile.DisplayName + }; + } + catch { + userProfile = new() { + AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + DisplayName = userId, + Id = userId + }; + } + + var roomList = new List(); + var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + await foreach (var result in tasks) + roomList.Add(result); + return (userProfile, roomList); + // StateHasChanged(); + }).ToAsyncEnumerable(); + await foreach (var res in results) { + SetupData.DMRooms.Add(res.userProfile, res.roomList); + // Status = $"Listed {dmRooms.Count} users"; + } + + _semaphore.Release(); + var duplicateDmRoomIds = new Dictionary>(); + foreach (var (user, rooms) in SetupData.DMRooms) { + foreach (var roomInfo in rooms) { + if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) + duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); + duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); + } + } + + duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); + foreach (var (roomId, users) in duplicateDmRoomIds) { + duplicateDmRooms.Add(SetupData.DMRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); + } + + // StateHasChanged(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo(room); + await roomInfo.FetchAllStateAsync(); + roomMembers[roomInfo] = new(); + // roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + + if(roomInfo.RoomName == room.RoomId) + try { + roomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch { } + + var membersEnum = room.GetMembersEnumerableAsync(true); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent) + roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private async Task SetRoomAssignment(string roomId, string userId) { + var hs = SetupData.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Updating DM list from account data..."; + + foreach (var (user, rooms) in dms) { + rooms.RemoveAll(x => x == roomId); + dms[user] = rooms.Distinct().ToList(); + } + + if (!dms.ContainsKey(userId)) + dms.Add(userId, new()); + dms[userId].Add(roomId); + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); + + duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); + StateHasChanged(); + if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); + } + +} diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor new file mode 100644 index 0000000..865e956 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor @@ -0,0 +1,191 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization +@using MatrixUtils.Abstractions + + + DM Space setup tool - stage 3: Preview space layout + +

This gives you a preview of how your settings would impact layout!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (SetupData is not null) { + @if (SetupData.DMSpaceRoomInfo is not null) { +

+ + Create sub-spaces per user +

+ @if (!SetupData.DmSpaceInfo.LayerByUser) { + + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { + @foreach (var roomInfo in room) { +
+ +
+ } + } + } + else { + + @foreach (var (user, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { +
+ @{ + RoomInfo fakeRoom = new(SetupData.DMSpaceRoomInfo.Room) { + RoomName = user.DisplayName ?? user.Id, + RoomIcon = user.AvatarUrl + }; + } + +
+ @foreach (var roomInfo in room) { +
+ +
+ } + } + } + } + else { + Error: SetupData.DMSpaceRoomInfo is null! + } +} +else { + Error: DMSpaceRootPageConfiguration is null! +} + +
+Next + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + await _semaphore.WaitAsync(); + var hs = SetupData.Homeserver; + // var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration.DMSpaceId); + // SetupData. + // dmSpaceRoomInfo = new() { + // RoomName = await dmSpaceRoom.GetNameAsync(), + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), + // RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // Room = dmSpaceRoom + // }; + // dmSpaceInfo = await dmSpaceRoom.GetDMSpaceInfo(); + // Status = "Loading DM list from account data..."; + // var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + + Status = "DM list optimised, fetching info..."; + // var results = dms.Select(async x => { + // var (userId, rooms) = x; + // UserProfileWithId userProfile; + // try { + // var profile = await SetupData.Homeserver.GetProfileAsync(userId); + // userProfile = new() { + // AvatarUrl = profile.AvatarUrl, + // Id = userId, + // DisplayName = profile.DisplayName + // }; + // } + // catch { + // userProfile = new() { + // AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // DisplayName = userId, + // Id = userId + // }; + // } + // var roomList = new List(); + // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + // await foreach (var result in tasks) + // roomList.Add(result); + // return (userProfile, roomList); + // }).ToAsyncEnumerable(); + // await foreach (var res in results) { + // dmRooms.Add(new RoomInfo() { + // Room = dmSpaceRoom, + // RoomIcon = res.userProfile.AvatarUrl, + // RoomName = res.userProfile.DisplayName, + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() + // }, res.roomList); + // } + await SetupData.DMSpaceRoomInfo!.FetchAllStateAsync(); + _semaphore.Release(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + var hs = SetupData.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration!.DMSpaceId!); + await dmSpaceRoom.ImportNativeDMs(); + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo(room); + var roomMembers = new List(); + roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + try { + roomInfo.RoomName = await room.GetNameAsync(); + } + catch { } + + var membersEnum = room.GetMembersEnumerableAsync(true); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent) + roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { + List displayNames = new List(); + foreach (var member in roomMembers) + if (!string.IsNullOrWhiteSpace(member.DisplayName)) + displayNames.Add(member.DisplayName); + roomInfo.RoomName = string.Join(", ", displayNames); + } + + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Index.razor b/MatrixUtils.Web/Pages/Labs/Index.razor new file mode 100644 index 0000000..fbe4b62 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Index.razor @@ -0,0 +1,11 @@ +@page "/Labs" +

Index of /Labs

+

Welcome to RMU Laboratories! We wish you a safe and informative time!

+

These pages are a work in progress, and may not work or cause permanent account changes!

+

We do not claim responsibility in case something goes wrong here!

+

Here be dragons!!

+
+ +Room List v3
+Client implementation attempt
+DM Space setup
\ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Index.razor.css b/MatrixUtils.Web/Pages/Labs/Index.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor new file mode 100644 index 0000000..3392960 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor @@ -0,0 +1,87 @@ +@page "/Labs/Rooms2" +@using LibMatrix.Responses +@using System.Collections.ObjectModel +@using System.ComponentModel +@using MatrixUtils.Abstractions +@using MatrixUtils.Web.Pages.Labs.Rooms2.Index2Components +@inject ILogger logger +

Room list

+ + +@if (Data.Homeserver is null || Data.GlobalProfile is null) { +

Creating homeserver instance and fetching global profile...

+ return; +} + +
+ Main + DMs + By room type +
+
+ + @switch (SelectedTab) { + case Tab.Main: +

Main tab

+ + break; + case Tab.DMs: +

DMs tab

+ + break; + case Tab.ByRoomType: +

By room type tab

+ + break; + default: + throw new InvalidEnumArgumentException(); + } +
+
+ +@* Create new room *@ + + +@code { + + private Tab SelectedTab { + get => _selectedTab; + set { + _selectedTab = value; + StateHasChanged(); + } + } + + public RoomListViewData Data { get; set; } = new RoomListViewData(); + + protected override async Task OnInitializedAsync() { + Data.Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + if (Data.Homeserver is null) return; + var rooms = await Data.Homeserver.GetJoinedRooms(); + Data.GlobalProfile = await Data.Homeserver.GetProfileAsync(Data.Homeserver.WhoAmI.UserId); + + foreach (var room in rooms) { + Data.Rooms.Add(new RoomInfo(room)); + } + StateHasChanged(); + + await base.OnInitializedAsync(); + } + + private Tab _selectedTab = Tab.Main; + + private enum Tab { + Main, + DMs, + ByRoomType + } + + public class RoomListViewData { + public ObservableCollection Rooms { get; } = []; + + public UserProfileResponse? GlobalProfile { get; set; } + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor new file mode 100644 index 0000000..6483f01 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor @@ -0,0 +1,56 @@ +@using MatrixUtils.Abstractions +
+
+ @if (IsSpaceOpened()) { + â–¼ + } + else { + â–¶ + } + + + @Space.RoomName +
+ @if (IsSpaceOpened()) { + meow + } +
+ +@code { + + [Parameter] + public RoomInfo Space { get; set; } + + [Parameter] + public RoomInfo SelectedSpace { get; set; } + + [Parameter] + public EventCallback SelectedSpaceChanged { get; set; } + + [Parameter] + public List OpenedSpaces { get; set; } + + protected override Task OnInitializedAsync() { + Space.PropertyChanged += (sender, args) => { StateHasChanged(); }; + return base.OnInitializedAsync(); + } + + public void ToggleSpace() { + if (OpenedSpaces.Contains(Space)) { + OpenedSpaces.Remove(Space); + } + else { + OpenedSpaces.Add(Space); + } + } + + public void SelectSpace() { + SelectedSpace = Space; + SelectedSpaceChanged.InvokeAsync(Space); + } + + public bool IsSpaceOpened() { + return OpenedSpaces.Contains(Space); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css new file mode 100644 index 0000000..d6e413f --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css @@ -0,0 +1,29 @@ +.spaceNameEllipsis { + padding-left: 8px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + width: calc(100% - 64px); +} + +.spaceListItem { + display: block; + width: 100%; + height: 3em; +} + +.spaceListItemContainer { + display: flex; + align-items: center; + vertical-align: center; + justify-content: space-between; + padding: 0 16px; + width: 100%; + height: 100%; +} + +.spaceListItem > img { + display: inline-block; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor new file mode 100644 index 0000000..f4cf849 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor @@ -0,0 +1,53 @@ +@using MatrixUtils.Abstractions +@using System.Security.Cryptography +@using ArcaneLibs.Extensions +

RoomsIndex2MainTab

+ +
+
+
+ Uncategorised rooms + @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) { +
+

@space.RoomName

+
+ } +
+
+

omae wa mou shindeiru

+
+
+
+ +@code { + + [CascadingParameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + Data.Rooms.CollectionChanged += (sender, args) => { + DebouncedStateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var newItem in args.NewItems) { + (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + }; + await base.OnInitializedAsync(); + } + + //debounce StateHasChanged, we dont want to reredner on every key stroke + + private CancellationTokenSource _debounceCts = new CancellationTokenSource(); + + private async Task DebouncedStateHasChanged() { + _debounceCts.Cancel(); + _debounceCts = new CancellationTokenSource(); + try { + await Task.Delay(100, _debounceCts.Token); + Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); + StateHasChanged(); + } + catch (TaskCanceledException) { } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor new file mode 100644 index 0000000..f4cf849 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor @@ -0,0 +1,53 @@ +@using MatrixUtils.Abstractions +@using System.Security.Cryptography +@using ArcaneLibs.Extensions +

RoomsIndex2MainTab

+ +
+
+
+ Uncategorised rooms + @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) { +
+

@space.RoomName

+
+ } +
+
+

omae wa mou shindeiru

+
+
+
+ +@code { + + [CascadingParameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + Data.Rooms.CollectionChanged += (sender, args) => { + DebouncedStateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var newItem in args.NewItems) { + (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + }; + await base.OnInitializedAsync(); + } + + //debounce StateHasChanged, we dont want to reredner on every key stroke + + private CancellationTokenSource _debounceCts = new CancellationTokenSource(); + + private async Task DebouncedStateHasChanged() { + _debounceCts.Cancel(); + _debounceCts = new CancellationTokenSource(); + try { + await Task.Delay(100, _debounceCts.Token); + Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); + StateHasChanged(); + } + catch (TaskCanceledException) { } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor new file mode 100644 index 0000000..7ccfae2 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor @@ -0,0 +1,206 @@ +@using MatrixUtils.Abstractions +@using System.ComponentModel +@using LibMatrix.EventTypes.Spec.State +@using MatrixUtils.Web.Pages.Labs.Rooms2.Index2Components.MainTabComponents +

RoomsIndex2MainTab

+ +@*
*@ +@*
*@ +@*
*@ +@* Uncategorised rooms *@ +@* @foreach (var space in GetTopLevelSpaces()) { *@ +@* *@ +@*
*@ +@*
@space.RoomName
*@ +@*
*@ +@*
*@ +@* } *@ +@*
*@ +@*
*@ +@*

Placeholder for rooms list...

*@ +@*
*@ +@*
*@ +@*
*@ + +
+
+
+ Uncategorised rooms + @foreach (var space in GetTopLevelSpaces()) { + @* @RecursingSpaceChildren(space) *@ + + } +
+
+

Placeholder for rooms list...

+ @if (SelectedSpace != null) { + foreach (var room in GetSpaceChildRooms(SelectedSpace)) { +

@room.RoomName

+ } + } +
+
+
+ + +@code { + + [CascadingParameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + Data.Rooms.CollectionChanged += (sender, args) => { + DebouncedStateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var newItem in args.NewItems) { + (newItem as RoomInfo).PropertyChanged += OnRoomListChanged; + (newItem as RoomInfo).StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + }; + foreach (var newItem in Data.Rooms) { + newItem.PropertyChanged += OnRoomListChanged; + newItem.StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + + await base.OnInitializedAsync(); + StateHasChanged(); + } + + private void OnRoomListChanged(object? sender, PropertyChangedEventArgs e) { + if (e.PropertyName == "RoomName" || e.PropertyName == "RoomType") + DebouncedStateHasChanged(); + } + + private CancellationTokenSource _debounceCts = new CancellationTokenSource(); + + private async Task DebouncedStateHasChanged() { + _debounceCts.Cancel(); + _debounceCts = new CancellationTokenSource(); + try { + Console.WriteLine("DebouncedStateHasChanged - Waiting 50ms..."); + await Task.Delay(50, _debounceCts.Token); + Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); + StateHasChanged(); + } + catch (TaskCanceledException) { } + } + + private List GetTopLevelSpaces() { + var spaces = Data.Rooms.Where(x => x.RoomType == "m.space").OrderBy(x => x.RoomName).ToList(); + var allSpaceChildEvents = spaces.SelectMany(x => x.StateEvents.Where(y => + y.Type == SpaceChildEventContent.EventId && + y.RawContent!.Count > 0 + )).ToList(); + + Console.WriteLine($"Child count: {allSpaceChildEvents.Count}"); + + spaces.RemoveAll(x => allSpaceChildEvents.Any(y => y.StateKey == x.Room.RoomId)); + + if (allSpaceChildEvents.Count == 0) { + Console.WriteLine("No space children found, returning nothing..."); + return []; + } + + return spaces.ToList(); + } + + private List GetSpaceChildren(RoomInfo space) { + var childEvents = space.StateEvents.Where(x => + x.Type == SpaceChildEventContent.EventId && + x.RawContent!.Count > 0 + ).ToList(); + var children = childEvents.Select(x => Data.Rooms.FirstOrDefault(y => y.Room.RoomId == x.StateKey)).Where(x => x is not null).ToList(); + return children; + } + + private List GetSpaceChildSpaces(RoomInfo space) { + var children = GetSpaceChildren(space); + var childSpaces = children.Where(x => x.RoomType == "m.space").ToList(); + return childSpaces; + } + + private List GetSpaceChildRooms(RoomInfo space) { + var children = GetSpaceChildren(space); + var childRooms = children.Where(x => x.RoomType != "m.space").ToList(); + return childRooms; + } + + private RoomInfo? SelectedSpace { get; set; } + private List OpenedSpaces { get; set; } = new List(); + + // private RenderFragment RecursingSpaceChildren(RoomInfo space, List? parents = null, int depth = 0) { + // parents ??= []; + // var totalSw = Stopwatch.StartNew(); + // var children = GetSpaceChildSpaces(space); + // + // var randomColor = RandomNumberGenerator.GetBytes(3).Append((byte)0x33).ToArray().AsHexString().Replace(" ", ""); + // var isExpanded = OpenedSpaces.Contains(space); + // + // // Console.WriteLine($"RecursingSpaceChildren::FetchData - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + // + // // var renderSw = Stopwatch.StartNew(); + // var rf = new RenderFragment(builder => { + // builder.OpenElement(0, "div"); + // //space list entry render fragment + // // builder.AddContent(1, SpaceListEntry(space)); + // builder.OpenComponent(1); + // builder.AddAttribute(2, "Space", space); + // builder.AddAttribute(2, "OpenedSpaces", OpenedSpaces); + // builder.CloseComponent(); + // builder.CloseElement(); + // //space children render fragment + // if (isExpanded) { + // builder.OpenElement(2, "div"); + // builder.AddAttribute(3, "style", "padding-left: 10px;"); + // foreach (var child in children) { + // builder.AddContent(4, RecursingSpaceChildren(child, parents.Append(space).ToList(), depth + 1)); + // } + // + // builder.CloseElement(); + // } + // }); + // + // // Console.WriteLine($"RecursingSpaceChildren::Render - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {renderSw.Elapsed}"); + // if (totalSw.ElapsedMilliseconds > 20) + // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + // // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + // return rf; + // } + + // private RenderFragment SpaceListEntry(RoomInfo space) { + // return builder => { + // { + // builder.OpenElement(0, "div"); + // builder.AddAttribute(1, "style", "display: block; width: 100%; height: 50px;"); + // builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, () => { + // if (OpenedSpaces.Contains(space)) { + // OpenedSpaces.Remove(space); + // } + // else { + // OpenedSpaces.Add(space); + // } + // + // StateHasChanged(); + // })); + // { + // builder.OpenComponent(5); + // builder.AddAttribute(6, "Homeserver", Data.Homeserver); + // builder.AddAttribute(7, "MxcUri", space.RoomIcon); + // builder.AddAttribute(8, "Circular", true); + // builder.AddAttribute(9, "Width", 32); + // builder.AddAttribute(10, "Height", 32); + // builder.CloseComponent(); + // } + // { + // // room name, ellipsized + // builder.OpenElement(11, "span"); + // builder.AddAttribute(12, "class", "spaceNameEllipsis"); + // builder.AddContent(13, space.RoomName); + // builder.CloseElement(); + // } + // builder.CloseElement(); + // } + // }; + // } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor new file mode 100644 index 0000000..91f228d --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor @@ -0,0 +1,201 @@ +@using LibMatrix.Helpers +@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 +@inject ILogger logger +
RoomsIndex2SyncContainer
+@foreach (var (name, value) in _statusList) { +
[@name] @value.Status
+} + +@code { + + [Parameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + private SyncHelper syncHelper; + + private Queue> queue = new(); + + private ObservableCollection<(string name, ObservableStatus value)> _statusList = new(); + + protected override async Task OnInitializedAsync() { + _statusList.CollectionChanged += (sender, args) => { + StateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var item in args.NewItems) { + if (item is not (string name, ObservableStatus value)) continue; + value.PropertyChanged += (sender, args) => { + if(value.Show) StateHasChanged(); + }; + } + }; + + while (Data.Homeserver is null) { + await Task.Delay(100); + } + + await SetUpSync(); + } + + private async Task SetUpSync() { + var status = await GetOrAddStatus("Main"); + var syncHelpers = new Dictionary() { + ["Main"] = new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetBasicRoomInfo), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + } + }; + status.Status = "Initial sync... Checking server filter capability..."; + var syncRes = await syncHelpers["Main"].SyncAsync(); + if (!syncRes.Rooms?.Join?.Any(x => x.Value.State?.Events?.Any(y => y.Type == SpaceChildEventContent.EventId) ?? false) ?? true) { + status.Status = "Initial sync indicates that server supports filters, starting helpers!"; + syncHelpers.Add("SpaceRelations", new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetSpaceRelations), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + }); + + syncHelpers.Add("Profile", new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetOwnMemberEvents), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + }); + } + else status.Status = "Initial sync indicates that server does not support filters, continuing without extra filters!"; + + await HandleSyncResponse(syncRes); + + // profileSyncHelper = new SyncHelper(Homeserver, logger) { + // Timeout = 10000, + // Filter = profileUpdateFilter, + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + // }; + // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); + RunQueueProcessor(); + foreach (var helper in syncHelpers) { + Console.WriteLine($"Starting sync loop for {helper.Key}"); + RunSyncLoop(helper.Value, helper.Key); + } + } + + private async Task RunQueueProcessor() { + var status = await GetOrAddStatus("QueueProcessor"); + var statusd = await GetOrAddStatus("QueueProcessor/D", show: false); + while (true) { + await Task.Delay(1000); + try { + var renderTimeSw = Stopwatch.StartNew(); + while (queue.Count == 0) { + var delay = 1000; + Console.WriteLine("Queue is empty, waiting..."); + // Status2 = $"Queue is empty, waiting for {delay}ms..."; + await Task.Delay(delay); + } + + status.Status = $"Queue no longer empty after {renderTimeSw.Elapsed}!"; + renderTimeSw.Restart(); + + int maxUpdates = 5000; + while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { + var (roomId, roomData) = queueEntry; + statusd.Status = $"Dequeued room {roomId}"; + RoomInfo room; + + if (Data.Rooms.Any(x => x.Room.RoomId == roomId)) { + room = Data.Rooms.First(x => x.Room.RoomId == roomId); + statusd.Status = $"{roomId} already known with {room.StateEvents?.Count ?? 0} state events"; + } + else { + statusd.Status = $"Eencountered new room {roomId}!"; + room = new RoomInfo(Data.Homeserver!.GetRoom(roomId), roomData.State?.Events); + Data.Rooms.Add(room); + } + + if (roomData.State?.Events is { Count: > 0 }) + room.StateEvents!.MergeStateEventLists(roomData.State.Events); + else { + statusd.Status = $"Could not merge state for {room.Room.RoomId} as new data contains no state events!"; + } + + // await Task.Delay(10); + } + + status.Status = $"Got {Data.Rooms.Count} rooms so far! {queue.Count} entries left in processing queue... Parsed last response in {renderTimeSw.Elapsed}"; + + // RenderContents |= queue.Count == 0; + // await Task.Delay(Data.Rooms.Count); + } + catch (Exception e) { + Console.WriteLine("QueueWorker exception: " + e); + } + } + } + + private async Task RunSyncLoop(SyncHelper syncHelper, string name = "Unknown") { + var status = await GetOrAddStatus($"SYNC/{name}"); + status.Status = $"Initial syncing..."; + + var syncs = syncHelper.EnumerateSyncAsync(); + await foreach (var sync in syncs) { + var sw = Stopwatch.StartNew(); + status.Status = $"[{DateTime.Now}] Got {Data.Rooms.Count} rooms so far! {sync.Rooms?.Join?.Count ?? 0} new updates!"; + + await HandleSyncResponse(sync); + status.Status += $"\nProcessed sync in {sw.ElapsedMilliseconds}ms, queue length: {queue.Count}"; + } + } + + private async Task HandleSyncResponse(SyncResponse? sync) { + if (sync?.Rooms?.Join is { Count: > 0 }) + foreach (var joinedRoom in sync.Rooms.Join) + queue.Enqueue(joinedRoom); + + if (sync.Rooms.Leave is { Count: > 0 }) + foreach (var leftRoom in sync.Rooms.Leave) + if (Data.Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) + Data.Rooms.Remove(Data.Rooms.First(x => x.Room.RoomId == leftRoom.Key)); + } + + private SemaphoreSlim _syncLock = new(1, 1); + + private async Task GetOrAddStatus(string name, bool show = true, bool log = true) { + await _syncLock.WaitAsync(); + try { + if (_statusList.Any(x => x.name == name)) + return _statusList.First(x => x.name == name).value; + var status = new ObservableStatus() { + Name = name, + Log = log, + Show = show + }; + _statusList.Add((name, status)); + return status; + } + finally { + _syncLock.Release(); + } + } + + private class ObservableStatus : NotifyPropertyChanged { + private string _status = "Initialising..."; + public string Name { get; set; } = "Unknown"; + public bool Show { get; set; } = true; + public bool Log { get; set; } = true; + + public string Status { + get => _status; + set { + if(SetField(ref _status, value) && Log) + Console.WriteLine($"[{Name}]: {value}"); + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor deleted file mode 100644 index f9cbfa2..0000000 --- a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor +++ /dev/null @@ -1,99 +0,0 @@ -@page "/Moderation/DraupnirProtectedRoomsEditor" -@using System.Text.Json.Serialization -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.RoomTypes -

Edit Draupnir protected rooms

-
- -@if (data is not null) { -
-
-

Current rooms

-
    - @foreach (var room in data.Rooms) { -
  • @room
  • - } -
-
-

Tickyboxes

- - - - @* Checkbox column *@ - @* PL > kick *@ - @* PL > ban *@ - @* PL > m.room.server_acls event *@ - - - - - - @foreach (var room in Rooms.OrderBy(x => x.RoomName)) { - - - - - - - - - } - -
Kick?Ban?ACL?Room IDRoom name
- - @(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")@room.Room.RoomId@room.RoomName
-
-
-} -
-Apply - - -@code { - private DraupnirProtectedRoomsData data { get; set; } = new(); - private List Rooms { get; set; } = new(); - private AuthenticatedHomeserverGeneric hs { get; set; } - - protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - data = await hs.GetAccountDataAsync("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); - StateHasChanged(); - } - - private class DraupnirProtectedRoomsData { - [JsonPropertyName("rooms")] - public List 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/Rooms/Index2.razor b/MatrixUtils.Web/Pages/Rooms/Index2.razor deleted file mode 100644 index 98b8a1d..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2.razor +++ /dev/null @@ -1,87 +0,0 @@ -@page "/Rooms2" -@using LibMatrix.Responses -@using System.Collections.ObjectModel -@using System.ComponentModel -@using MatrixUtils.Abstractions -@using MatrixUtils.Web.Pages.Rooms.Index2Components -@inject ILogger logger -

Room list

- - -@if (Data.Homeserver is null || Data.GlobalProfile is null) { -

Creating homeserver instance and fetching global profile...

- return; -} - -
- Main - DMs - By room type -
-
- - @switch (SelectedTab) { - case Tab.Main: -

Main tab

- - break; - case Tab.DMs: -

DMs tab

- - break; - case Tab.ByRoomType: -

By room type tab

- - break; - default: - throw new InvalidEnumArgumentException(); - } -
-
- -@* Create new room *@ - - -@code { - - private Tab SelectedTab { - get => _selectedTab; - set { - _selectedTab = value; - StateHasChanged(); - } - } - - public RoomListViewData Data { get; set; } = new RoomListViewData(); - - protected override async Task OnInitializedAsync() { - Data.Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); - if (Data.Homeserver is null) return; - var rooms = await Data.Homeserver.GetJoinedRooms(); - Data.GlobalProfile = await Data.Homeserver.GetProfileAsync(Data.Homeserver.WhoAmI.UserId); - - foreach (var room in rooms) { - Data.Rooms.Add(new RoomInfo(room)); - } - StateHasChanged(); - - await base.OnInitializedAsync(); - } - - private Tab _selectedTab = Tab.Main; - - private enum Tab { - Main, - DMs, - ByRoomType - } - - public class RoomListViewData { - public ObservableCollection Rooms { get; } = []; - - public UserProfileResponse? GlobalProfile { get; set; } - - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor deleted file mode 100644 index 6483f01..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor +++ /dev/null @@ -1,56 +0,0 @@ -@using MatrixUtils.Abstractions -
-
- @if (IsSpaceOpened()) { - â–¼ - } - else { - â–¶ - } - - - @Space.RoomName -
- @if (IsSpaceOpened()) { - meow - } -
- -@code { - - [Parameter] - public RoomInfo Space { get; set; } - - [Parameter] - public RoomInfo SelectedSpace { get; set; } - - [Parameter] - public EventCallback SelectedSpaceChanged { get; set; } - - [Parameter] - public List OpenedSpaces { get; set; } - - protected override Task OnInitializedAsync() { - Space.PropertyChanged += (sender, args) => { StateHasChanged(); }; - return base.OnInitializedAsync(); - } - - public void ToggleSpace() { - if (OpenedSpaces.Contains(Space)) { - OpenedSpaces.Remove(Space); - } - else { - OpenedSpaces.Add(Space); - } - } - - public void SelectSpace() { - SelectedSpace = Space; - SelectedSpaceChanged.InvokeAsync(Space); - } - - public bool IsSpaceOpened() { - return OpenedSpaces.Contains(Space); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css deleted file mode 100644 index d6e413f..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css +++ /dev/null @@ -1,29 +0,0 @@ -.spaceNameEllipsis { - padding-left: 8px; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: middle; - width: calc(100% - 64px); -} - -.spaceListItem { - display: block; - width: 100%; - height: 3em; -} - -.spaceListItemContainer { - display: flex; - align-items: center; - vertical-align: center; - justify-content: space-between; - padding: 0 16px; - width: 100%; - height: 100%; -} - -.spaceListItem > img { - display: inline-block; -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor deleted file mode 100644 index f4cf849..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor +++ /dev/null @@ -1,53 +0,0 @@ -@using MatrixUtils.Abstractions -@using System.Security.Cryptography -@using ArcaneLibs.Extensions -

RoomsIndex2MainTab

- -
-
-
- Uncategorised rooms - @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) { -
-

@space.RoomName

-
- } -
-
-

omae wa mou shindeiru

-
-
-
- -@code { - - [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; - - protected override async Task OnInitializedAsync() { - Data.Rooms.CollectionChanged += (sender, args) => { - DebouncedStateHasChanged(); - if (args.NewItems is { Count: > 0 }) - foreach (var newItem in args.NewItems) { - (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); }; - } - }; - await base.OnInitializedAsync(); - } - - //debounce StateHasChanged, we dont want to reredner on every key stroke - - private CancellationTokenSource _debounceCts = new CancellationTokenSource(); - - private async Task DebouncedStateHasChanged() { - _debounceCts.Cancel(); - _debounceCts = new CancellationTokenSource(); - try { - await Task.Delay(100, _debounceCts.Token); - Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); - StateHasChanged(); - } - catch (TaskCanceledException) { } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor deleted file mode 100644 index f4cf849..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor +++ /dev/null @@ -1,53 +0,0 @@ -@using MatrixUtils.Abstractions -@using System.Security.Cryptography -@using ArcaneLibs.Extensions -

RoomsIndex2MainTab

- -
-
-
- Uncategorised rooms - @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) { -
-

@space.RoomName

-
- } -
-
-

omae wa mou shindeiru

-
-
-
- -@code { - - [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; - - protected override async Task OnInitializedAsync() { - Data.Rooms.CollectionChanged += (sender, args) => { - DebouncedStateHasChanged(); - if (args.NewItems is { Count: > 0 }) - foreach (var newItem in args.NewItems) { - (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); }; - } - }; - await base.OnInitializedAsync(); - } - - //debounce StateHasChanged, we dont want to reredner on every key stroke - - private CancellationTokenSource _debounceCts = new CancellationTokenSource(); - - private async Task DebouncedStateHasChanged() { - _debounceCts.Cancel(); - _debounceCts = new CancellationTokenSource(); - try { - await Task.Delay(100, _debounceCts.Token); - Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); - StateHasChanged(); - } - catch (TaskCanceledException) { } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor deleted file mode 100644 index b163a52..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor +++ /dev/null @@ -1,210 +0,0 @@ -@using MatrixUtils.Abstractions -@using System.Security.Cryptography -@using ArcaneLibs.Extensions -@using System.ComponentModel -@using System.Diagnostics -@using LibMatrix.EventTypes.Spec.State -@using MatrixUtils.Web.Pages.Rooms.Index2Components.MainTabComponents -@using Microsoft.AspNetCore.Components.Rendering -

RoomsIndex2MainTab

- -@*
*@ -@*
*@ -@*
*@ -@* Uncategorised rooms *@ -@* @foreach (var space in GetTopLevelSpaces()) { *@ -@* *@ -@*
*@ -@*
@space.RoomName
*@ -@*
*@ -@*
*@ -@* } *@ -@*
*@ -@*
*@ -@*

Placeholder for rooms list...

*@ -@*
*@ -@*
*@ -@*
*@ - -
-
-
- Uncategorised rooms - @foreach (var space in GetTopLevelSpaces()) { - @* @RecursingSpaceChildren(space) *@ - - } -
-
-

Placeholder for rooms list...

- @if (SelectedSpace != null) { - foreach (var room in GetSpaceChildRooms(SelectedSpace)) { -

@room.RoomName

- } - } -
-
-
- - -@code { - - [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; - - protected override async Task OnInitializedAsync() { - Data.Rooms.CollectionChanged += (sender, args) => { - DebouncedStateHasChanged(); - if (args.NewItems is { Count: > 0 }) - foreach (var newItem in args.NewItems) { - (newItem as RoomInfo).PropertyChanged += OnRoomListChanged; - (newItem as RoomInfo).StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; - } - }; - foreach (var newItem in Data.Rooms) { - newItem.PropertyChanged += OnRoomListChanged; - newItem.StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; - } - - await base.OnInitializedAsync(); - StateHasChanged(); - } - - private void OnRoomListChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == "RoomName" || e.PropertyName == "RoomType") - DebouncedStateHasChanged(); - } - - private CancellationTokenSource _debounceCts = new CancellationTokenSource(); - - private async Task DebouncedStateHasChanged() { - _debounceCts.Cancel(); - _debounceCts = new CancellationTokenSource(); - try { - Console.WriteLine("DebouncedStateHasChanged - Waiting 50ms..."); - await Task.Delay(50, _debounceCts.Token); - Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); - StateHasChanged(); - } - catch (TaskCanceledException) { } - } - - private List GetTopLevelSpaces() { - var spaces = Data.Rooms.Where(x => x.RoomType == "m.space").OrderBy(x => x.RoomName).ToList(); - var allSpaceChildEvents = spaces.SelectMany(x => x.StateEvents.Where(y => - y.Type == SpaceChildEventContent.EventId && - y.RawContent!.Count > 0 - )).ToList(); - - Console.WriteLine($"Child count: {allSpaceChildEvents.Count}"); - - spaces.RemoveAll(x => allSpaceChildEvents.Any(y => y.StateKey == x.Room.RoomId)); - - if (allSpaceChildEvents.Count == 0) { - Console.WriteLine("No space children found, returning nothing..."); - return []; - } - - return spaces.ToList(); - } - - private List GetSpaceChildren(RoomInfo space) { - var childEvents = space.StateEvents.Where(x => - x.Type == SpaceChildEventContent.EventId && - x.RawContent!.Count > 0 - ).ToList(); - var children = childEvents.Select(x => Data.Rooms.FirstOrDefault(y => y.Room.RoomId == x.StateKey)).Where(x => x is not null).ToList(); - return children; - } - - private List GetSpaceChildSpaces(RoomInfo space) { - var children = GetSpaceChildren(space); - var childSpaces = children.Where(x => x.RoomType == "m.space").ToList(); - return childSpaces; - } - - private List GetSpaceChildRooms(RoomInfo space) { - var children = GetSpaceChildren(space); - var childRooms = children.Where(x => x.RoomType != "m.space").ToList(); - return childRooms; - } - - private RoomInfo? SelectedSpace { get; set; } - private List OpenedSpaces { get; set; } = new List(); - - // private RenderFragment RecursingSpaceChildren(RoomInfo space, List? parents = null, int depth = 0) { - // parents ??= []; - // var totalSw = Stopwatch.StartNew(); - // var children = GetSpaceChildSpaces(space); - // - // var randomColor = RandomNumberGenerator.GetBytes(3).Append((byte)0x33).ToArray().AsHexString().Replace(" ", ""); - // var isExpanded = OpenedSpaces.Contains(space); - // - // // Console.WriteLine($"RecursingSpaceChildren::FetchData - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); - // - // // var renderSw = Stopwatch.StartNew(); - // var rf = new RenderFragment(builder => { - // builder.OpenElement(0, "div"); - // //space list entry render fragment - // // builder.AddContent(1, SpaceListEntry(space)); - // builder.OpenComponent(1); - // builder.AddAttribute(2, "Space", space); - // builder.AddAttribute(2, "OpenedSpaces", OpenedSpaces); - // builder.CloseComponent(); - // builder.CloseElement(); - // //space children render fragment - // if (isExpanded) { - // builder.OpenElement(2, "div"); - // builder.AddAttribute(3, "style", "padding-left: 10px;"); - // foreach (var child in children) { - // builder.AddContent(4, RecursingSpaceChildren(child, parents.Append(space).ToList(), depth + 1)); - // } - // - // builder.CloseElement(); - // } - // }); - // - // // Console.WriteLine($"RecursingSpaceChildren::Render - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {renderSw.Elapsed}"); - // if (totalSw.ElapsedMilliseconds > 20) - // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); - // // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); - // return rf; - // } - - // private RenderFragment SpaceListEntry(RoomInfo space) { - // return builder => { - // { - // builder.OpenElement(0, "div"); - // builder.AddAttribute(1, "style", "display: block; width: 100%; height: 50px;"); - // builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, () => { - // if (OpenedSpaces.Contains(space)) { - // OpenedSpaces.Remove(space); - // } - // else { - // OpenedSpaces.Add(space); - // } - // - // StateHasChanged(); - // })); - // { - // builder.OpenComponent(5); - // builder.AddAttribute(6, "Homeserver", Data.Homeserver); - // builder.AddAttribute(7, "MxcUri", space.RoomIcon); - // builder.AddAttribute(8, "Circular", true); - // builder.AddAttribute(9, "Width", 32); - // builder.AddAttribute(10, "Height", 32); - // builder.CloseComponent(); - // } - // { - // // room name, ellipsized - // builder.OpenElement(11, "span"); - // builder.AddAttribute(12, "class", "spaceNameEllipsis"); - // builder.AddContent(13, space.RoomName); - // builder.CloseElement(); - // } - // builder.CloseElement(); - // } - // }; - // } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css deleted file mode 100644 index e69de29..0000000 diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor deleted file mode 100644 index 418ee02..0000000 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor +++ /dev/null @@ -1,202 +0,0 @@ -@using LibMatrix.Helpers -@using LibMatrix.Responses -@using MatrixUtils.Abstractions -@using System.Diagnostics -@using System.Diagnostics.CodeAnalysis -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Extensions -@using LibMatrix.Utilities -@using System.Collections.ObjectModel -@using ArcaneLibs -@inject ILogger logger -
RoomsIndex2SyncContainer
-@foreach (var (name, value) in _statusList) { -
[@name] @value.Status
-} - -@code { - - [Parameter] - public Index2.RoomListViewData Data { get; set; } = null!; - - private SyncHelper syncHelper; - - private Queue> queue = new(); - - private ObservableCollection<(string name, ObservableStatus value)> _statusList = new(); - - protected override async Task OnInitializedAsync() { - _statusList.CollectionChanged += (sender, args) => { - StateHasChanged(); - if (args.NewItems is { Count: > 0 }) - foreach (var item in args.NewItems) { - if (item is not (string name, ObservableStatus value)) continue; - value.PropertyChanged += (sender, args) => { - if(value.Show) StateHasChanged(); - }; - } - }; - - while (Data.Homeserver is null) { - await Task.Delay(100); - } - - await SetUpSync(); - } - - private async Task SetUpSync() { - var status = await GetOrAddStatus("Main"); - var syncHelpers = new Dictionary() { - ["Main"] = new SyncHelper(Data.Homeserver, logger) { - Timeout = 30000, - FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetBasicRoomInfo), - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - } - }; - status.Status = "Initial sync... Checking server filter capability..."; - var syncRes = await syncHelpers["Main"].SyncAsync(); - if (!syncRes.Rooms?.Join?.Any(x => x.Value.State?.Events?.Any(y => y.Type == SpaceChildEventContent.EventId) ?? false) ?? true) { - status.Status = "Initial sync indicates that server supports filters, starting helpers!"; - syncHelpers.Add("SpaceRelations", new SyncHelper(Data.Homeserver, logger) { - Timeout = 30000, - FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetSpaceRelations), - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - }); - - syncHelpers.Add("Profile", new SyncHelper(Data.Homeserver, logger) { - Timeout = 30000, - FilterId = await Data.Homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetOwnMemberEvents), - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - }); - } - else status.Status = "Initial sync indicates that server does not support filters, continuing without extra filters!"; - - await HandleSyncResponse(syncRes); - - // profileSyncHelper = new SyncHelper(Homeserver, logger) { - // Timeout = 10000, - // Filter = profileUpdateFilter, - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - // }; - // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); - RunQueueProcessor(); - foreach (var helper in syncHelpers) { - Console.WriteLine($"Starting sync loop for {helper.Key}"); - RunSyncLoop(helper.Value, helper.Key); - } - } - - private async Task RunQueueProcessor() { - var status = await GetOrAddStatus("QueueProcessor"); - var statusd = await GetOrAddStatus("QueueProcessor/D", show: false); - while (true) { - await Task.Delay(1000); - try { - var renderTimeSw = Stopwatch.StartNew(); - while (queue.Count == 0) { - var delay = 1000; - Console.WriteLine("Queue is empty, waiting..."); - // Status2 = $"Queue is empty, waiting for {delay}ms..."; - await Task.Delay(delay); - } - - status.Status = $"Queue no longer empty after {renderTimeSw.Elapsed}!"; - renderTimeSw.Restart(); - - int maxUpdates = 5000; - while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { - var (roomId, roomData) = queueEntry; - statusd.Status = $"Dequeued room {roomId}"; - RoomInfo room; - - if (Data.Rooms.Any(x => x.Room.RoomId == roomId)) { - room = Data.Rooms.First(x => x.Room.RoomId == roomId); - statusd.Status = $"{roomId} already known with {room.StateEvents?.Count ?? 0} state events"; - } - else { - statusd.Status = $"Eencountered new room {roomId}!"; - room = new RoomInfo(Data.Homeserver!.GetRoom(roomId), roomData.State?.Events); - Data.Rooms.Add(room); - } - - if (roomData.State?.Events is { Count: > 0 }) - room.StateEvents!.MergeStateEventLists(roomData.State.Events); - else { - statusd.Status = $"Could not merge state for {room.Room.RoomId} as new data contains no state events!"; - } - - // await Task.Delay(10); - } - - status.Status = $"Got {Data.Rooms.Count} rooms so far! {queue.Count} entries left in processing queue... Parsed last response in {renderTimeSw.Elapsed}"; - - // RenderContents |= queue.Count == 0; - // await Task.Delay(Data.Rooms.Count); - } - catch (Exception e) { - Console.WriteLine("QueueWorker exception: " + e); - } - } - } - - private async Task RunSyncLoop(SyncHelper syncHelper, string name = "Unknown") { - var status = await GetOrAddStatus($"SYNC/{name}"); - status.Status = $"Initial syncing..."; - - var syncs = syncHelper.EnumerateSyncAsync(); - await foreach (var sync in syncs) { - var sw = Stopwatch.StartNew(); - status.Status = $"[{DateTime.Now}] Got {Data.Rooms.Count} rooms so far! {sync.Rooms?.Join?.Count ?? 0} new updates!"; - - await HandleSyncResponse(sync); - status.Status += $"\nProcessed sync in {sw.ElapsedMilliseconds}ms, queue length: {queue.Count}"; - } - } - - private async Task HandleSyncResponse(SyncResponse? sync) { - if (sync?.Rooms?.Join is { Count: > 0 }) - foreach (var joinedRoom in sync.Rooms.Join) - queue.Enqueue(joinedRoom); - - if (sync.Rooms.Leave is { Count: > 0 }) - foreach (var leftRoom in sync.Rooms.Leave) - if (Data.Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) - Data.Rooms.Remove(Data.Rooms.First(x => x.Room.RoomId == leftRoom.Key)); - } - - private SemaphoreSlim _syncLock = new(1, 1); - - private async Task GetOrAddStatus(string name, bool show = true, bool log = true) { - await _syncLock.WaitAsync(); - try { - if (_statusList.Any(x => x.name == name)) - return _statusList.First(x => x.name == name).value; - var status = new ObservableStatus() { - Name = name, - Log = log, - Show = show - }; - _statusList.Add((name, status)); - return status; - } - finally { - _syncLock.Release(); - } - } - - private class ObservableStatus : NotifyPropertyChanged { - private string _status = "Initialising..."; - public string Name { get; set; } = "Unknown"; - public bool Show { get; set; } = true; - public bool Log { get; set; } = true; - - public string Status { - get => _status; - set { - if(SetField(ref _status, value) && Log) - Console.WriteLine($"[{Name}]: {value}"); - } - } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor deleted file mode 100644 index 667b518..0000000 --- a/MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor +++ /dev/null @@ -1,82 +0,0 @@ -@page "/Tools/CopyPowerlevel" -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.RoomTypes -

Copy powerlevel

-
- -

Users:

-@foreach (var hs in hss) { -

@hs.WhoAmI.UserId

-} - -
-Execute -
-@foreach (var line in Enumerable.Reverse(log)) { -

@line

-} - -@code { - private List log { get; set; } = new(); - List hss { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); - if (session is not null) { - hss.Add(session); - StateHasChanged(); - } - } - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - foreach (var hs in hss) { - var rooms = await hs.GetJoinedRooms(); - var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - } - } - - private async Task Execute(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; - if (!pls.UserHasStatePermission(hs.WhoAmI.UserId, RoomPowerLevelEventContent.EventId)) return "I do not have permission to send PL in " + room.RoomId; - foreach (var ahs in hss) { - if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.GetUserPowerLevel(ahs.WhoAmI.UserId)) { - log.Add("I am same PL in " + room.RoomId); - continue; - } - - pls.SetUserPowerLevel(ahs.WhoAmI.UserId, pls.GetUserPowerLevel(hs.WhoAmI.UserId)); - await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls); - log.Add($"Updated powerlevel of {room.RoomId} to {pls.GetUserPowerLevel(ahs.WhoAmI.UserId)}"); - } - - } - catch (MatrixException e) { - return $"Failed to update PLs in {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to update PLs in {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor new file mode 100644 index 0000000..841552e --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor @@ -0,0 +1,52 @@ +@page "/Tools/LeaveRoom" +@using System.Collections.ObjectModel +

Leave room

+
+Room ID: + +
+Leave +

+@foreach (var line in Log) { +

@line

+} +@code { + AuthenticatedHomeserverGeneric? hs { get; set; } + ObservableCollection Log { get; set; } = new ObservableCollection(); + [Parameter, SupplyParameterFromQuery(Name = "roomId")] + public string? RoomId { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + Log.CollectionChanged += (sender, args) => StateHasChanged(); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Leave() { + if(string.IsNullOrWhiteSpace(RoomId)) return; + var room = hs.GetRoom(RoomId); + Log.Add("Got room object..."); + try { + await room.LeaveAsync(); + Log.Add("Left room!"); + } + catch (Exception e) { + Log.Add(e.ToString()); + } + + try { + await room.ForgetAsync(); + Log.Add("Forgot room!"); + } + catch (Exception e) { + Log.Add(e.ToString()); + } + + Log.Add("Done!"); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor new file mode 100644 index 0000000..6e87926 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor @@ -0,0 +1,109 @@ +@page "/Tools/MediaLocator" +@inject HttpClient Http +

Media locator

+
+ +This is going to expose your IP address to all these homeservers! +
+ Checked homeserver list (@homeservers.Count entries) +
    + @foreach (var hs in homeservers) { +
  • @hs
  • + } +
+
+ +
+MXC URL: + + + +@if (successResults.Count > 0) { +

Successes

+
    + @foreach (var result in successResults) { +
  • @result
  • + } +
+} + +@if (errorResults.Count > 0) { +

Errors

+
    + @foreach (var result in errorResults) { +
  • @result
  • + } +
+} + + +@code { + string mxcUrl { get; set; } + readonly List successResults = new(); + readonly List errorResults = new(); + readonly List homeservers = new(); + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + homeservers.AddRange(new[] { + "matrix.org", + "feline.support", + "rory.gay", + "the-apothecary.club", + "envs.net", + "projectsegfau.lt" + }); + } + + Task executeSearch() { + var sem = new SemaphoreSlim(128, 128); + homeservers.ForEach(async hs => { + await sem.WaitAsync(); + var httpClient = new HttpClient { BaseAddress = new Uri(hs) }; + httpClient.Timeout = TimeSpan.FromSeconds(5); + var rmu = mxcUrl.Replace("mxc://", $"{hs}/_matrix/media/v3/download/"); + try { + var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, rmu)); + if (res.IsSuccessStatusCode) { + successResults.Add($"{hs}: found - {res.Content.Headers.ContentLength} bytes"); + StateHasChanged(); + return; + } + errorResults.Add($"Error: {hs} - {res.StatusCode}\n" + await res.Content.ReadAsStringAsync()); + } + catch (Exception e) { + errorResults.Add($"Error: {e}"); + } + finally { + sem.Release(); + } + StateHasChanged(); + }); + return Task.CompletedTask; + } + + async Task addMoreHomeservers() { + var res = await Http.GetAsync("/homeservers.txt"); + var content = await res.Content.ReadAsStringAsync(); + homeservers.Clear(); + var lines = content.Split("\n"); + + var sem = new SemaphoreSlim(128, 128); + lines.ToList().ForEach(async line => { + await sem.WaitAsync(); + try { + homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client); + StateHasChanged(); + } + catch (Exception e) { + Console.WriteLine(e); + } + finally { + sem.Release(); + } + }); + + StateHasChanged(); + } + +} diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor new file mode 100644 index 0000000..11d35f1 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor @@ -0,0 +1,103 @@ +@page "/Tools/MigrateRoom" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.RoomTypes +

Migrate room

+
+Old room: +
+New room: +
+ +
+ Users: + @foreach (var user in users) { +

@user

+ } +
+ +
+Execute +
+@foreach (var line in Enumerable.Reverse(log)) { +

@line

+} + +@code { + private string _roomId; + private List log { get; set; } = new(); + private List users { get; set; } = new(); + + string roomId { + get => _roomId; + set { + _roomId = value; + TryFetchUsers(); + } + } + + private string newRoomId { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var oldRoom = hs.GetRoom(roomId); + var newRoom = hs.GetRoom(newRoomId); + var members = await oldRoom.GetMembersListAsync(); + var tasks = members.Select(x => ExecuteInvite(hs, newRoom, x.StateKey)).ToAsyncEnumerable(); + // var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + } + + private async Task ExecuteInvite(AuthenticatedHomeserverGeneric hs, GenericRoom newRoom, string mxid) { + try { + var pls = await newRoom.GetPowerLevelsAsync(); + if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + newRoom.RoomId; + await newRoom.InviteUserAsync(mxid); + return $"Invited {mxid} to {newRoom.RoomId}"; + } + catch (MatrixException e) { + log.Add($"Failed to invite {mxid} to {newRoom.RoomId}: {e.Message}"); + if (e is { ErrorCode: "M_LIMIT_EXCEEDED" }) { + log.Add($"Retrying after {e.RetryAfterMs}"); + await Task.Delay(e.RetryAfterMs!.Value); + return await ExecuteInvite(hs, newRoom, mxid); + } + + return ""; + } + catch (Exception e) { + return $"Failed to invite {mxid} to {newRoom.RoomId}: {e.Message}"; + } + + StateHasChanged(); + return ""; + } + + private async Task TryFetchUsers() { + try { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var room = hs.GetRoom(roomId); + var members = await room.GetMembersListAsync(); + users = members.Select(x => x.StateKey).ToList(); + StateHasChanged(); + } + catch { } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor new file mode 100644 index 0000000..263879b --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor @@ -0,0 +1,114 @@ +@page "/Tools/SpaceDebug" +@using LibMatrix.Helpers +@using LibMatrix.Utilities +

SpaceDebug

+
+ +

@Status

+ +Has parent: +
+ +@foreach (var (roomId, parents) in SpaceParents) { +

@roomId's parents

+
    + @foreach (var parent in parents) { +
  • @parent
  • + } +
+} + +Space children: + +@foreach (var (roomId, children) in SpaceChildren) { +

@roomId's children

+
    + @foreach (var child in children) { +
  • @child
  • + } +
+} + +@code { + private string _status = "Loading..."; + + public string Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + public Dictionary> SpaceChildren { get; set; } = new(); + public Dictionary> SpaceParents { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + Status = "Getting homeserver..."; + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + var syncHelper = new SyncHelper(hs) { + // Filter = new SyncFilter() { + // Presence = new(0), + // Room = new() { + // AccountData = new(limit: 0), + // Ephemeral = new(limit: 0), + // State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }), + // Timeline = new(limit: 0) + // }, + // AccountData = new(limit: 0) + // } + NamedFilterName = CommonSyncFilters.GetSpaceRelations + }; + + Status = "Syncing..."; + + var syncs = syncHelper.EnumerateSyncAsync(); + await foreach (var sync in syncs) { + if (sync is null) { + Status = "Sync failed"; + continue; + } + + if (sync.Rooms is null) { + Status = "No rooms in sync..."; + break; + } + + if (sync.Rooms.Join is null) { + Status = "No joined rooms in sync..."; + break; + } + + if (sync.Rooms.Join.Count == 0) { + Status = "Joined rooms list was empty..."; + break; + } + + // nextBatch = sync.NextBatch; + foreach (var (roomId, data) in sync.Rooms!.Join!) { + data.State?.Events?.ForEach(e => { + if (e.Type == "m.space.child") { + if (!SpaceChildren.ContainsKey(roomId)) SpaceChildren[roomId] = new(); + if (e.RawContent is null) e.StateKey += " (null)"; + else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; + SpaceChildren[roomId].Add(e.StateKey); + } + if (e.Type == "m.space.parent") { + if (!SpaceParents.ContainsKey(roomId)) SpaceParents[roomId] = new(); + if (e.RawContent is null) e.StateKey += " (null)"; + else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; + SpaceParents[roomId].Add(e.StateKey); + } + }); + } + Status = $"Synced {sync.Rooms.Join.Count} rooms, found {SpaceChildren.Count} spaces, {SpaceParents.Count} parents"; + } + Status = $"Synced: found {SpaceChildren.Count}->{SpaceChildren.Sum(x => x.Value.Count)} spaces, {SpaceParents.Count}->{SpaceParents.Sum(x => x.Value.Count)} parents!"; + + await base.OnInitializedAsync(); + } + + +} diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor index f1e04a3..3aec2e3 100644 --- a/MatrixUtils.Web/Pages/Tools/Index.razor +++ b/MatrixUtils.Web/Pages/Tools/Index.razor @@ -1,10 +1,31 @@ @page "/Tools" -

Other tools

- -Copy highest powerlevel across all session
-Find all homeservers you share a room with
-Join room across all session
-Locate lost media
-Debug space relationships
-Migrate users from a split room to a new room
-Leave room by ID
+

Index of /Tools

+ +

Information tools

+
+View policy list activity
+Find all homeservers you share a room with
+Show session counts for users in a given room
+ +

User tools

+
+Join room across all session
+Copy highest powerlevel across all session
+View account data
+ +

Moderation tools

+
+Count invites by inviter
+View membership history
+Trace user across rooms
+Mass write policies to Community Moderation Effort
+Find rooms with common users
+Edit Draupnir protected rooms set
+ + +

Debugging tools

+
+Debug space relationships
+Leave room by ID
+Locate lost media
+Migrate users from a split room to a new room
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor.css b/MatrixUtils.Web/Pages/Tools/Index.razor.css new file mode 100644 index 0000000..c9bd995 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Index.razor.css @@ -0,0 +1,6 @@ +.tool-category { + margin-top: 20px; +} +hr{ + margin: unset; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor new file mode 100644 index 0000000..ddd7b15 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor @@ -0,0 +1,51 @@ +@page "/Tools/KnownHomeserverList" +@using ArcaneLibs.Extensions +

Known Homeserver List

+
+ +@if (!IsFinished) { +

+ Loading... +

+} + +@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) { +

@homeserver - @members

+} +
+ +@code { + Dictionary> homeservers { get; set; } = new(); + Dictionary counts { get; set; } = new(); + // List Homeservers = new(); + bool IsFinished { get; set; } + // HomeserverInfoQueryProgress QueryProgress { get; set; } = new(); + AuthenticatedHomeserverGeneric? hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).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(); + // await Task.Delay(250); + } + + foreach (var resHomeserver in homeservers.Keys) { + homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList(); + counts[resHomeserver] = homeservers[resHomeserver].Count; + } + + IsFinished = true; + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor new file mode 100644 index 0000000..c94d0b0 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor @@ -0,0 +1,158 @@ +@page "/Tools/PolicyListActivity" +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Diagnostics +@using LibMatrix.RoomTypes +@using LibMatrix.EventTypes.Common + + +@if (RoomData.Count == 0) +{ +

Loading...

+} +else + foreach (var room in RoomData) + { +

@room.Key

+ @foreach (var year in room.Value.OrderBy(x => x.Key)) + { +
@year.Key
+ + + } + } + + +@code { + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + public List FilteredRooms = new(); + + public Dictionary TestData { get; set; } = new(); + + public ActivityGraph.RGB MaxValue { get; set; } = new() + { + R = 255, G = 255, B = 255 + }; + + public Dictionary>> RoomData { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + var sw = Stopwatch.StartNew(); + await base.OnInitializedAsync(); + Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + if (Homeserver is null) return; + + //random test data + for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(Random.Shared.Next(5))) + { + 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.EventId) is not null) + // { + // Console.WriteLine($"{room.RoomId} is policy list by shortcode"); + // FilteredRooms.Add(room); + // } + // } + var roomFilterTasks = rooms.Select(async room => + { + var type = await room.GetRoomType(); + 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.EventId) is not null) + { + Console.WriteLine($"{room.RoomId} is policy list by shortcode"); + return room; + } + + return null; + }).ToList(); + var filteredRooms = await Task.WhenAll(roomFilterTasks); + FilteredRooms.AddRange(filteredRooms.Where(x => x is not null).Cast()); + Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); + + var roomTasks = FilteredRooms.Select(FetchRoomHistory).ToList(); + await Task.WhenAll(roomTasks); + + Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); + Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); + } + + 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(); + } + + //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; + } + + 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); + } + } + + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css new file mode 100644 index 0000000..443fdb5 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css @@ -0,0 +1,12 @@ +h3 { + font-weight: bold; + margin-top: 3rem; +} + +h3:first-child { + margin-top: 1rem; +} + +h5 { + margin-top: 1.5rem; +} diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor new file mode 100644 index 0000000..3b68bfa --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor @@ -0,0 +1,155 @@ +@page "/Tools/SessionCount" +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes.Spec.State +

User Trace

+
+ +

Users:

+ +
+Import from room (ID) + +
+ Rooms to be searched (@rooms.Count) + @foreach (var room in rooms) { + @room.RoomId +
+ } +
+
+Execute +
+ +
+ Results + @foreach (var (userId, events) in matches) { +

@userId

+
    + @foreach (var eventResponse in events) { +
  • @eventResponse.Room.RoomId
  • + } +
+ } +
+
+ Results text + @{ + var col1Width = matches.Keys.Max(x => x.Length); + } +
+        @foreach (var (userId, events) in matches) {
+            

+ @userId.PadRight(col1Width) + @foreach (var @event in events) { + +} +

+ } +
+
+ +
+@foreach (var line in log.Reverse()) { +
@line
+} + +@code { + private ObservableCollection log { get; set; } = new(); + List hss { get; set; } = new(); + ObservableCollection rooms { get; set; } = new(); + Dictionary> roomMembers { get; set; } = new(); + Dictionary> 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 UserIDs { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + var sessions = await RMUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await RMUStorage.GetSession(userAuth); + if (session is not null) { + var sessionRooms = await session.GetJoinedRooms(); + foreach (var room in sessionRooms) { + rooms.Add(room); + } + + StateHasChanged(); + log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + } + } + + log.Add("Done fetching rooms!"); + + var distinctRooms = rooms.DistinctBy(x => x.RoomId).ToArray(); + Random.Shared.Shuffle(distinctRooms); + rooms = new ObservableCollection(distinctRooms); + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + + var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync(false))).ToAsyncEnumerable(); + + await foreach (var (room, state) in stateTasks) { + roomMembers.Add(room, state); + log.Add($"Got {state.Count} members for {room.RoomId}..."); + } + + log.Add($"Done fetching members!"); + + UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + foreach (var userId in UserIDs) { + matches.Add(userId, new List()); + foreach (var (room, events) in roomMembers) { + if (events.Any(x => x.Type == RoomMemberEventContent.EventId && x.StateKey == userId)) { + matches[userId].Add(new() { + Event = events.First(x => x.StateKey == userId && x.Type == RoomMemberEventContent.EventId), + Room = room, + }); + } + } + } + + 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 Matches { + public GenericRoom Room; + + public StateEventResponse Event; + // public + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor deleted file mode 100644 index 8f4b4dd..0000000 --- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor +++ /dev/null @@ -1,73 +0,0 @@ -@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 -

User Trace

-
- -
-Room ID: - -Execute - -
- -
- Results - @foreach (var (userId, events) in invites.OrderByDescending(x=>x.Value).ToList()) { -

@userId: @events

- } -
- -
-@foreach (var line in log.Reverse()) { -
@line
-} - -@code { - private ObservableCollection log { get; set; } = new(); - private Dictionary 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(); - if (hs is null) return; - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - var room = hs.GetRoom(roomId); - var events = room.GetManyMessagesAsync(limit: int.MaxValue); - await foreach (var resp in events) { - var all = resp.State.Concat(resp.Chunk); - foreach (var evt in all) { - 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]++; - } - - log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline"); - } - - - - StateHasChanged(); - - return ""; - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor deleted file mode 100644 index ddd7b15..0000000 --- a/MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor +++ /dev/null @@ -1,51 +0,0 @@ -@page "/Tools/KnownHomeserverList" -@using ArcaneLibs.Extensions -

Known Homeserver List

-
- -@if (!IsFinished) { -

- Loading... -

-} - -@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) { -

@homeserver - @members

-} -
- -@code { - Dictionary> homeservers { get; set; } = new(); - Dictionary counts { get; set; } = new(); - // List Homeservers = new(); - bool IsFinished { get; set; } - // HomeserverInfoQueryProgress QueryProgress { get; set; } = new(); - AuthenticatedHomeserverGeneric? hs { get; set; } - - protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).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(); - // await Task.Delay(250); - } - - foreach (var resHomeserver in homeservers.Keys) { - homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList(); - counts[resHomeserver] = homeservers[resHomeserver].Count; - } - - IsFinished = true; - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor deleted file mode 100644 index 841552e..0000000 --- a/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor +++ /dev/null @@ -1,52 +0,0 @@ -@page "/Tools/LeaveRoom" -@using System.Collections.ObjectModel -

Leave room

-
-Room ID: - -
-Leave -

-@foreach (var line in Log) { -

@line

-} -@code { - AuthenticatedHomeserverGeneric? hs { get; set; } - ObservableCollection Log { get; set; } = new ObservableCollection(); - [Parameter, SupplyParameterFromQuery(Name = "roomId")] - public string? RoomId { get; set; } - - protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - Log.CollectionChanged += (sender, args) => StateHasChanged(); - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Leave() { - if(string.IsNullOrWhiteSpace(RoomId)) return; - var room = hs.GetRoom(RoomId); - Log.Add("Got room object..."); - try { - await room.LeaveAsync(); - Log.Add("Left room!"); - } - catch (Exception e) { - Log.Add(e.ToString()); - } - - try { - await room.ForgetAsync(); - Log.Add("Forgot room!"); - } - catch (Exception e) { - Log.Add(e.ToString()); - } - - Log.Add("Done!"); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor deleted file mode 100644 index cbbca9e..0000000 --- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor +++ /dev/null @@ -1,75 +0,0 @@ -@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 -

User Trace

-
- -
-Users: - -Execute - -
- -
-@foreach (var line in log.Reverse()) { -
@line
-} - -@code { - // TODO: Properly implement page to be more useful - private ObservableCollection 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(); - if (hs is null) return; - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task 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.EventId, user.Replace('@', '_'))).Entity); - } catch (Exception e) { - log.Add($"Failed to get {user}"); - } - - if (!exists) { - var evt = await room.SendStateEventAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'), new UserPolicyRuleEventContent() { - Entity = user, - Reason = "spam (invite)", - Recommendation = "m.ban" - }); - log.Add($"Sent {evt.EventId} to ban {user}"); - } - else { - log.Add($"User {user} already exists"); - } - } - - - StateHasChanged(); - - return ""; - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor deleted file mode 100644 index a2ad388..0000000 --- a/MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor +++ /dev/null @@ -1,107 +0,0 @@ -@page "/Tools/MassRoomJoin" -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -

Mass join room

-
-

Room:

- - -

Users:

-@foreach (var hs in hss) { -

@hs.WhoAmI.UserId

-} - -
-Execute -
-@foreach (var line in Enumerable.Reverse(log)) { -

@line

-} - -@code { - private List log { get; set; } = new(); - List hss { get; set; } = new(); - string roomId { get; set; } - - protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); - if (session is not null) { - hss.Add(session); - StateHasChanged(); - } - } - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - // foreach (var hs in hss) { - // var rooms = await hs.GetJoinedRooms(); - var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - tasks = hss.Select(ExecuteJoin).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - // } - } - - private async Task ExecuteInvite(AuthenticatedHomeserverGeneric hs) { - var room = hs.GetRoom(roomId); - try { - try { - var joinRule = await room.GetJoinRuleAsync(); - if (joinRule.JoinRule == RoomJoinRulesEventContent.JoinRules.Public) return "Room is public, no invite needed"; - } - catch { } - var pls = await room.GetPowerLevelsAsync(); - if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + room.RoomId; - await room.InviteUsersAsync(hss.Select(x => x.WhoAmI.UserId).ToList()); - log.Add($"Invited to {room.RoomId} to {pls.GetUserPowerLevel(hs.WhoAmI.UserId)}"); - } - catch (MatrixException e) { - return $"Failed to invite in {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to invite in {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - - private async Task ExecuteJoin(AuthenticatedHomeserverGeneric hs) { - var room = hs.GetRoom(roomId); - try { - try { - var mse = await room.GetStateOrNullAsync(RoomMemberEventContent.EventId, hs.WhoAmI.UserId); - if (mse?.Membership == "join") return $"User {hs.WhoAmI.UserId} already in room"; - } - catch { } - await room.JoinAsync(); - } - catch (MatrixException e) { - return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/MediaLocator.razor b/MatrixUtils.Web/Pages/Tools/MediaLocator.razor deleted file mode 100644 index 6e87926..0000000 --- a/MatrixUtils.Web/Pages/Tools/MediaLocator.razor +++ /dev/null @@ -1,109 +0,0 @@ -@page "/Tools/MediaLocator" -@inject HttpClient Http -

Media locator

-
- -This is going to expose your IP address to all these homeservers! -
- Checked homeserver list (@homeservers.Count entries) -
    - @foreach (var hs in homeservers) { -
  • @hs
  • - } -
-
- -
-MXC URL: - - - -@if (successResults.Count > 0) { -

Successes

-
    - @foreach (var result in successResults) { -
  • @result
  • - } -
-} - -@if (errorResults.Count > 0) { -

Errors

-
    - @foreach (var result in errorResults) { -
  • @result
  • - } -
-} - - -@code { - string mxcUrl { get; set; } - readonly List successResults = new(); - readonly List errorResults = new(); - readonly List homeservers = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - homeservers.AddRange(new[] { - "matrix.org", - "feline.support", - "rory.gay", - "the-apothecary.club", - "envs.net", - "projectsegfau.lt" - }); - } - - Task executeSearch() { - var sem = new SemaphoreSlim(128, 128); - homeservers.ForEach(async hs => { - await sem.WaitAsync(); - var httpClient = new HttpClient { BaseAddress = new Uri(hs) }; - httpClient.Timeout = TimeSpan.FromSeconds(5); - var rmu = mxcUrl.Replace("mxc://", $"{hs}/_matrix/media/v3/download/"); - try { - var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, rmu)); - if (res.IsSuccessStatusCode) { - successResults.Add($"{hs}: found - {res.Content.Headers.ContentLength} bytes"); - StateHasChanged(); - return; - } - errorResults.Add($"Error: {hs} - {res.StatusCode}\n" + await res.Content.ReadAsStringAsync()); - } - catch (Exception e) { - errorResults.Add($"Error: {e}"); - } - finally { - sem.Release(); - } - StateHasChanged(); - }); - return Task.CompletedTask; - } - - async Task addMoreHomeservers() { - var res = await Http.GetAsync("/homeservers.txt"); - var content = await res.Content.ReadAsStringAsync(); - homeservers.Clear(); - var lines = content.Split("\n"); - - var sem = new SemaphoreSlim(128, 128); - lines.ToList().ForEach(async line => { - await sem.WaitAsync(); - try { - homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client); - StateHasChanged(); - } - catch (Exception e) { - Console.WriteLine(e); - } - finally { - sem.Release(); - } - }); - - StateHasChanged(); - } - -} diff --git a/MatrixUtils.Web/Pages/Tools/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/MigrateRoom.razor deleted file mode 100644 index 11d35f1..0000000 --- a/MatrixUtils.Web/Pages/Tools/MigrateRoom.razor +++ /dev/null @@ -1,103 +0,0 @@ -@page "/Tools/MigrateRoom" -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.RoomTypes -

Migrate room

-
-Old room: -
-New room: -
- -
- Users: - @foreach (var user in users) { -

@user

- } -
- -
-Execute -
-@foreach (var line in Enumerable.Reverse(log)) { -

@line

-} - -@code { - private string _roomId; - private List log { get; set; } = new(); - private List users { get; set; } = new(); - - string roomId { - get => _roomId; - set { - _roomId = value; - TryFetchUsers(); - } - } - - private string newRoomId { get; set; } - - protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var oldRoom = hs.GetRoom(roomId); - var newRoom = hs.GetRoom(newRoomId); - var members = await oldRoom.GetMembersListAsync(); - var tasks = members.Select(x => ExecuteInvite(hs, newRoom, x.StateKey)).ToAsyncEnumerable(); - // var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - } - - private async Task ExecuteInvite(AuthenticatedHomeserverGeneric hs, GenericRoom newRoom, string mxid) { - try { - var pls = await newRoom.GetPowerLevelsAsync(); - if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + newRoom.RoomId; - await newRoom.InviteUserAsync(mxid); - return $"Invited {mxid} to {newRoom.RoomId}"; - } - catch (MatrixException e) { - log.Add($"Failed to invite {mxid} to {newRoom.RoomId}: {e.Message}"); - if (e is { ErrorCode: "M_LIMIT_EXCEEDED" }) { - log.Add($"Retrying after {e.RetryAfterMs}"); - await Task.Delay(e.RetryAfterMs!.Value); - return await ExecuteInvite(hs, newRoom, mxid); - } - - return ""; - } - catch (Exception e) { - return $"Failed to invite {mxid} to {newRoom.RoomId}: {e.Message}"; - } - - StateHasChanged(); - return ""; - } - - private async Task TryFetchUsers() { - try { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var room = hs.GetRoom(roomId); - var members = await room.GetMembersListAsync(); - users = members.Select(x => x.StateKey).ToList(); - StateHasChanged(); - } - catch { } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor new file mode 100644 index 0000000..805bd40 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor @@ -0,0 +1,102 @@ +@page "/Moderation/DraupnirProtectedRoomsEditor" +@page "/Tools/Moderation/DraupnirProtectedRoomsEditor" +@using System.Text.Json.Serialization +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.RoomTypes +

Edit Draupnir protected rooms

+
+

Note: You will need to restart Draupnir after applying changes!

+

Minor note: This should also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.

+ +@if (data is not null) { +
+
+

Current rooms

+
    + @foreach (var room in data.Rooms) { +
  • @room
  • + } +
+
+

Tickyboxes

+ + + + @* Checkbox column *@ + @* PL > kick *@ + @* PL > ban *@ + @* PL > m.room.server_acls event *@ + + + + + + @foreach (var room in Rooms.OrderBy(x => x.RoomName)) { + + + + + + + + + } + +
Kick?Ban?ACL?Room IDRoom name
+ + @(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")@room.Room.RoomId@room.RoomName
+
+
+} +
+Apply + + +@code { + private DraupnirProtectedRoomsData data { get; set; } = new(); + private List Rooms { get; set; } = new(); + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + data = await hs.GetAccountDataAsync("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); + StateHasChanged(); + } + + private class DraupnirProtectedRoomsData { + [JsonPropertyName("rooms")] + public List 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/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor new file mode 100644 index 0000000..2123d4d --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor @@ -0,0 +1,68 @@ +@page "/Tools/Moderation/InviteCounter" +@using System.Collections.ObjectModel +@using LibMatrix.EventTypes.Spec.State +

Invite counter

+
+ +
+Room ID: + +Execute + +
+ +
+ Results + @foreach (var (userId, events) in invites.OrderByDescending(x=>x.Value).ToList()) { +

@userId: @events

+ } +
+ +
+@foreach (var line in log.Reverse()) { +
@line
+} + +@code { + private ObservableCollection log { get; set; } = new(); + private Dictionary 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(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + var room = hs.GetRoom(roomId); + var events = room.GetManyMessagesAsync(limit: int.MaxValue); + await foreach (var resp in events) { + var all = resp.State.Concat(resp.Chunk); + foreach (var evt in all) { + 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]++; + } + + log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline"); + } + + + + StateHasChanged(); + + return ""; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor new file mode 100644 index 0000000..ea1e5f6 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor @@ -0,0 +1,69 @@ +@page "/Tools/Moderation/MassCMEBan" +@using System.Collections.ObjectModel +@using LibMatrix.EventTypes.Spec.State.Policy +

User Trace

+
+ +
+Users: + +Execute + +
+ +
+@foreach (var line in log.Reverse()) { +
@line
+} + +@code { + // TODO: Properly implement page to be more useful + private ObservableCollection 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(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task 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.EventId, user.Replace('@', '_'))).Entity); + } catch (Exception e) { + log.Add($"Failed to get {user}"); + } + + if (!exists) { + var evt = await room.SendStateEventAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'), new UserPolicyRuleEventContent() { + Entity = user, + Reason = "spam (invite)", + Recommendation = "m.ban" + }); + log.Add($"Sent {evt.EventId} to ban {user}"); + } + else { + log.Add($"User {user} already exists"); + } + } + + + StateHasChanged(); + + return ""; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor new file mode 100644 index 0000000..e5ba004 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor @@ -0,0 +1,276 @@ +@page "/Tools/Moderation/MembershipHistory" +@using System.Collections.ObjectModel +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +

Membership history viewer

+
+ +
+Room ID: + +Execute +

Chronological order

+

+ Show + joins + leaves + profile updates + knocks + invites + kicks + bans +

+

+ Hide all + Show all + Toggle all +

+

+ Sender: + + + @foreach (var sender in Memberships.Select(x => x.Sender).Distinct()) { + + } + +

+

+ User: + + + @foreach (var user in Memberships.Select(x => x.StateKey).Distinct()) { + + } + +

+ + +
+ +
+ Results + @{ + Dictionary 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) { +

@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + + break; + } + case RoomMemberEventContent.MembershipTypes.Ban: { + if (_showBans) { +

@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + + break; + } + case RoomMemberEventContent.MembershipTypes.Leave: { + if (membership.Sender == membership.StateKey) { + if (_showLeaves) { +

@membership.Sender left the room

+ } + } + else { + if (_showKicks) { +

@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + } + + break; + } + case RoomMemberEventContent.MembershipTypes.Knock: { + if (_showKnocks) { +

@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + + break; + } + case RoomMemberEventContent.MembershipTypes.Join: { + if (previousMemberships.TryGetValue(membership.StateKey, out var previous) + && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) { + if (_showUpdates) { +

@membership.Sender changed their profile

+ } + } + else { + if (_showJoins) { +

@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + } + + break; + } + default: { + Unknown membership @content.Membership! + break; + } + } + + previousMemberships[membership.StateKey] = membership; + } + } +
+ +
+
+ Log + @foreach (var line in log.Reverse()) { +
@line
+ } +
+ +@code { + +#region Filter bindings + + private bool _chronologicalOrder = false; + + private bool ChronologicalOrder { + get => _chronologicalOrder; + set { + _chronologicalOrder = value; + StateHasChanged(); + } + } + + private bool _showJoins = true; + + private bool ShowJoins { + get => _showJoins; + set { + _showJoins = value; + StateHasChanged(); + } + } + + private bool _showLeaves = true; + + private bool ShowLeaves { + get => _showLeaves; + set { + _showLeaves = value; + StateHasChanged(); + } + } + + private bool _showUpdates = true; + + private bool ShowUpdates { + get => _showUpdates; + set { + _showUpdates = value; + StateHasChanged(); + } + } + + private bool _showKnocks = true; + + private bool ShowKnocks { + get => _showKnocks; + set { + _showKnocks = value; + StateHasChanged(); + } + } + + private bool _showInvites = true; + + private bool ShowInvites { + get => _showInvites; + set { + _showInvites = value; + StateHasChanged(); + } + } + + private bool _showKicks = true; + + private bool ShowKicks { + get => _showKicks; + set { + _showKicks = value; + StateHasChanged(); + } + } + + private bool _showBans = true; + + private bool ShowBans { + get => _showBans; + set { + _showBans = value; + StateHasChanged(); + } + } + + private string sender = ""; + + private string Sender { + get => sender; + set { + sender = value; + StateHasChanged(); + } + } + + private string user = ""; + + private string User { + get => user; + set { + user = value; + StateHasChanged(); + } + } + +#endregion + + private ObservableCollection log { get; set; } = new(); + private List 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; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + if (!string.IsNullOrWhiteSpace(roomId)) + await Execute(); + } + + 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"); + } + + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor new file mode 100644 index 0000000..b8baeb8 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor @@ -0,0 +1,197 @@ +@page "/Tools/Moderation/RoomIntersections" +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +

Room intersections

+
+ +

Set A:

+ +Append Set A + +

Set B:

+ +Append Set B +
+Execute +
+ +
+ Results +
+        @{
+            var userColWidth = matches.Count == 0 ? 0 : matches.Keys.Max(x => x.Length);
+        }
+        
+            @foreach (var (userId, sets) in matches) {
+                
+                    
+                    
+                    
+                    
+                    
+                    
+                    
+                    
+                    
+                
+                @for (int i = 1; i < Math.Max(sets.Item1.Count, sets.Item2.Count); i++) {
+                    
+                        
+                            
+                            
+                            
+                        }
+                        else {
+                            
+                            
+                            
+                             
+                        }
+                        else {
+                            
+                }
+            }
+            
+        
@userId.PadRight(userColWidth + 5)@sets.Item1[0].Room.RoomId@((sets.Item1[0].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item1[0].Room) ? roomNames[sets.Item1[0].Room] : "")@(roomAliasses.ContainsKey(sets.Item1[0].Room) ? roomAliasses[sets.Item1[0].Room] : "")@sets.Item2[0].Room.RoomId@((sets.Item2[0].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item2[0].Room) ? roomNames[sets.Item2[0].Room] : "")@(roomAliasses.ContainsKey(sets.Item2[0].Room) ? roomAliasses[sets.Item2[0].Room] : "")
+ @if (sets.Item1.Count > i) { + @sets.Item1[i].Room.RoomId@((sets.Item1[i].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item1[i].Room) ? roomNames[sets.Item1[i].Room] : "")@(roomAliasses.ContainsKey(sets.Item1[i].Room) ? roomAliasses[sets.Item1[i].Room] : "") + + + + } + @if (sets.Item2.Count > i) { + @sets.Item2[0].Room.RoomId@((sets.Item2[i].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item2[i].Room) ? roomNames[sets.Item2[i].Room] : "")@(roomAliasses.ContainsKey(sets.Item2[i].Room) ? roomAliasses[sets.Item2[i].Room] : "") + + + + } +
+
+
+
+ +
+@foreach (var line in Log.Reverse()) { +
@line
+} + +@code { + private ObservableCollection Log { get; set; } = new(); + List RoomsA { get; set; } = new(); + List RoomsB { get; set; } = new(); + + [Parameter, SupplyParameterFromQuery(Name = "a")] + public string ImportSetASpaceId { get; set; } = ""; + + [Parameter, SupplyParameterFromQuery(Name = "b")] + public string ImportSetBSpaceId { get; set; } = ""; + + Dictionary> roomMembers { get; set; } = new(); + + Dictionary, List)> matches { get; set; } = new(); + + AuthenticatedHomeserverGeneric hs { get; set; } + + // private string RoomListAString { + // get => string.Join("\n", RoomIdsA); + // set => RoomIdsA = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + // } + // + // private string RoomListBString { + // get => string.Join("\n", RoomIdsB); + // set => RoomIdsB = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + // } + + // private List RoomIdsA { get; set; } = new(); + // private List RoomIdsB { get; set; } = new(); + + // room info + Dictionary roomNames { get; set; } = new(); + Dictionary roomAliasses { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + Log.CollectionChanged += (sender, args) => StateHasChanged(); + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + // get all users which are in any room of both sets of rooms, and which rooms + var setAusers = new Dictionary>(); + var setBusers = new Dictionary>(); + + await Task.WhenAll(GetMembers(RoomsA, setAusers), GetMembers(RoomsB, setBusers)); + + Log.Add($"Got {setAusers.Count} users in set A"); + Log.Add($"Got {setBusers.Count} users in set B"); + Log.Add("Calculating intersections..."); + + // get all users which are in both sets of rooms + // var users = setAusers.Keys.Intersect(setBusers.Keys).ToList(); + // var groups = setAusers.IntersectBy(setBusers, (x,y) => x.Key).ToList(); + matches = setAusers.Keys.Intersect(setBusers.Keys).Select(x => (x, setAusers[x], setBusers[x])).ToDictionary(x => x.x, x => (x.Item2, x.Item3)); + + Log.Add($"Found {matches.Count} users in both sets of rooms"); + StateHasChanged(); + } + + public async Task GetMembers(List rooms, Dictionary> users) { + foreach (var room in rooms) { + Log.Add($"Getting members for {room.RoomId}"); + var members = await room.GetMembersListAsync(false); + foreach (var member in members) { + if (member.RawContent?["membership"]?.ToString() == "ban") continue; + if (member.RawContent?["membership"]?.ToString() == "invite") continue; + if (!users.ContainsKey(member.StateKey)) users[member.StateKey] = new(); + users[member.StateKey].Add(new() { + Room = room, + Member = member + }); + } + } + } + + public async Task AppendSet(string spaceId, List rooms) { + var space = hs.GetRoom(spaceId).AsSpace; + Log.Add($"Found space {spaceId}"); + var roomIdsEnum = space.GetChildrenAsync(true); + List tasks = new(); + await foreach (var room in roomIdsEnum) { + tasks.Add(loadRoomData(room, rooms)); + } + + await Task.WhenAll(tasks); + + async Task loadRoomData(GenericRoom room, List rooms) { + Log.Add($"Found room {room.RoomId}"); + try { + await room.GetPowerLevelsAsync(); + rooms.Add(room); + try { + roomAliasses[room] = (await room.GetCanonicalAliasAsync()).Alias; + } + catch { } + + try { + roomNames[room] = await room.GetNameOrFallbackAsync(); + } + catch { } + } + catch (MatrixException e) { + Log.Add($"Failed to get power levels for {room.RoomId}: {e.Message}"); + } + } + } + + public class Match { + public GenericRoom Room { get; set; } + public StateEventResponse Member { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor new file mode 100644 index 0000000..915f8dc --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor @@ -0,0 +1,194 @@ +@page "/Tools/Moderation/UserTrace" +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +

User Trace

+
+ +

Users:

+ +
+Import from room (ID) + +
+ Rooms to be searched (@rooms.Count) + @foreach (var room in rooms) { + @room.RoomId +
+ } +
+
+Execute +
+ +
+ Results + @foreach (var (userId, events) in matches) { +

@userId

+
    + @foreach (var match in events) { +
  • +
      +
    • @match.RoomName (@match.Room.RoomId)
    • +
    • Membership: @(match.Event.RawContent.ToJson(indent: false))
    • +
    +
  • + } +
+ } +
+ +
+@foreach (var line in log.Reverse()) { +
@line
+} + +@code { + + private ObservableCollection log { get; set; } = new(); + + // List rooms { get; set; } = new(); + List rooms { get; set; } = []; + Dictionary> 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 UserIDs { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + // var sessions = await RMUStorage.GetAllTokens(); + // var baseRooms = new List(); + // 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}"); + } + } + + //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(); + log.Add($"Got {rooms.Count} rooms"); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + foreach (var userId in UserIDs) { + matches.Add(userId, new List()); + + // 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); + } + } + + 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 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; + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor deleted file mode 100644 index c94d0b0..0000000 --- a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor +++ /dev/null @@ -1,158 +0,0 @@ -@page "/Tools/PolicyListActivity" -@using LibMatrix.EventTypes.Spec.State.Policy -@using System.Diagnostics -@using LibMatrix.RoomTypes -@using LibMatrix.EventTypes.Common - - -@if (RoomData.Count == 0) -{ -

Loading...

-} -else - foreach (var room in RoomData) - { -

@room.Key

- @foreach (var year in room.Value.OrderBy(x => x.Key)) - { -
@year.Key
- - - } - } - - -@code { - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - public List FilteredRooms = new(); - - public Dictionary TestData { get; set; } = new(); - - public ActivityGraph.RGB MaxValue { get; set; } = new() - { - R = 255, G = 255, B = 255 - }; - - public Dictionary>> RoomData { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - var sw = Stopwatch.StartNew(); - await base.OnInitializedAsync(); - Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; - if (Homeserver is null) return; - - //random test data - for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(Random.Shared.Next(5))) - { - 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.EventId) is not null) - // { - // Console.WriteLine($"{room.RoomId} is policy list by shortcode"); - // FilteredRooms.Add(room); - // } - // } - var roomFilterTasks = rooms.Select(async room => - { - var type = await room.GetRoomType(); - 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.EventId) is not null) - { - Console.WriteLine($"{room.RoomId} is policy list by shortcode"); - return room; - } - - return null; - }).ToList(); - var filteredRooms = await Task.WhenAll(roomFilterTasks); - FilteredRooms.AddRange(filteredRooms.Where(x => x is not null).Cast()); - Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); - - var roomTasks = FilteredRooms.Select(FetchRoomHistory).ToList(); - await Task.WhenAll(roomTasks); - - Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); - Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); - } - - 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(); - } - - //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; - } - - 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); - } - } - - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css b/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css deleted file mode 100644 index 443fdb5..0000000 --- a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css +++ /dev/null @@ -1,12 +0,0 @@ -h3 { - font-weight: bold; - margin-top: 3rem; -} - -h3:first-child { - margin-top: 1rem; -} - -h5 { - margin-top: 1.5rem; -} diff --git a/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor deleted file mode 100644 index 173ff01..0000000 --- a/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor +++ /dev/null @@ -1,197 +0,0 @@ -@page "/Tools/RoomIntersections" -@using LibMatrix.RoomTypes -@using System.Collections.ObjectModel -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -

Room intersections

-
- -

Set A:

- -Append Set A - -

Set B:

- -Append Set B -
-Execute -
- -
- Results -
-        @{
-            var userColWidth = matches.Count == 0 ? 0 : matches.Keys.Max(x => x.Length);
-        }
-        
-            @foreach (var (userId, sets) in matches) {
-                
-                    
-                    
-                    
-                    
-                    
-                    
-                    
-                    
-                    
-                
-                @for (int i = 1; i < Math.Max(sets.Item1.Count, sets.Item2.Count); i++) {
-                    
-                        
-                            
-                            
-                            
-                        }
-                        else {
-                            
-                            
-                            
-                             
-                        }
-                        else {
-                            
-                }
-            }
-            
-        
@userId.PadRight(userColWidth + 5)@sets.Item1[0].Room.RoomId@((sets.Item1[0].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item1[0].Room) ? roomNames[sets.Item1[0].Room] : "")@(roomAliasses.ContainsKey(sets.Item1[0].Room) ? roomAliasses[sets.Item1[0].Room] : "")@sets.Item2[0].Room.RoomId@((sets.Item2[0].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item2[0].Room) ? roomNames[sets.Item2[0].Room] : "")@(roomAliasses.ContainsKey(sets.Item2[0].Room) ? roomAliasses[sets.Item2[0].Room] : "")
- @if (sets.Item1.Count > i) { - @sets.Item1[i].Room.RoomId@((sets.Item1[i].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item1[i].Room) ? roomNames[sets.Item1[i].Room] : "")@(roomAliasses.ContainsKey(sets.Item1[i].Room) ? roomAliasses[sets.Item1[i].Room] : "") - - - - } - @if (sets.Item2.Count > i) { - @sets.Item2[0].Room.RoomId@((sets.Item2[i].Member.TypedContent as RoomMemberEventContent).Membership)@(roomNames.ContainsKey(sets.Item2[i].Room) ? roomNames[sets.Item2[i].Room] : "")@(roomAliasses.ContainsKey(sets.Item2[i].Room) ? roomAliasses[sets.Item2[i].Room] : "") - - - - } -
-
-
-
- -
-@foreach (var line in Log.Reverse()) { -
@line
-} - -@code { - private ObservableCollection Log { get; set; } = new(); - List RoomsA { get; set; } = new(); - List RoomsB { get; set; } = new(); - - [Parameter, SupplyParameterFromQuery(Name = "a")] - public string ImportSetASpaceId { get; set; } = ""; - - [Parameter, SupplyParameterFromQuery(Name = "b")] - public string ImportSetBSpaceId { get; set; } = ""; - - Dictionary> roomMembers { get; set; } = new(); - - Dictionary, List)> matches { get; set; } = new(); - - AuthenticatedHomeserverGeneric hs { get; set; } - - // private string RoomListAString { - // get => string.Join("\n", RoomIdsA); - // set => RoomIdsA = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); - // } - // - // private string RoomListBString { - // get => string.Join("\n", RoomIdsB); - // set => RoomIdsB = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); - // } - - // private List RoomIdsA { get; set; } = new(); - // private List RoomIdsB { get; set; } = new(); - - // room info - Dictionary roomNames { get; set; } = new(); - Dictionary roomAliasses { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - Log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - // get all users which are in any room of both sets of rooms, and which rooms - var setAusers = new Dictionary>(); - var setBusers = new Dictionary>(); - - await Task.WhenAll(GetMembers(RoomsA, setAusers), GetMembers(RoomsB, setBusers)); - - Log.Add($"Got {setAusers.Count} users in set A"); - Log.Add($"Got {setBusers.Count} users in set B"); - Log.Add("Calculating intersections..."); - - // get all users which are in both sets of rooms - // var users = setAusers.Keys.Intersect(setBusers.Keys).ToList(); - // var groups = setAusers.IntersectBy(setBusers, (x,y) => x.Key).ToList(); - matches = setAusers.Keys.Intersect(setBusers.Keys).Select(x => (x, setAusers[x], setBusers[x])).ToDictionary(x => x.x, x => (x.Item2, x.Item3)); - - Log.Add($"Found {matches.Count} users in both sets of rooms"); - StateHasChanged(); - } - - public async Task GetMembers(List rooms, Dictionary> users) { - foreach (var room in rooms) { - Log.Add($"Getting members for {room.RoomId}"); - var members = await room.GetMembersListAsync(false); - foreach (var member in members) { - if (member.RawContent?["membership"]?.ToString() == "ban") continue; - if (member.RawContent?["membership"]?.ToString() == "invite") continue; - if (!users.ContainsKey(member.StateKey)) users[member.StateKey] = new(); - users[member.StateKey].Add(new() { - Room = room, - Member = member - }); - } - } - } - - public async Task AppendSet(string spaceId, List rooms) { - var space = hs.GetRoom(spaceId).AsSpace; - Log.Add($"Found space {spaceId}"); - var roomIdsEnum = space.GetChildrenAsync(true); - List tasks = new(); - await foreach (var room in roomIdsEnum) { - tasks.Add(loadRoomData(room, rooms)); - } - - await Task.WhenAll(tasks); - - async Task loadRoomData(GenericRoom room, List rooms) { - Log.Add($"Found room {room.RoomId}"); - try { - await room.GetPowerLevelsAsync(); - rooms.Add(room); - try { - roomAliasses[room] = (await room.GetCanonicalAliasAsync()).Alias; - } - catch { } - - try { - roomNames[room] = await room.GetNameOrFallbackAsync(); - } - catch { } - } - catch (MatrixException e) { - Log.Add($"Failed to get power levels for {room.RoomId}: {e.Message}"); - } - } - } - - public class Match { - public GenericRoom Room { get; set; } - public StateEventResponse Member { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/SessionCount.razor deleted file mode 100644 index 3b68bfa..0000000 --- a/MatrixUtils.Web/Pages/Tools/SessionCount.razor +++ /dev/null @@ -1,155 +0,0 @@ -@page "/Tools/SessionCount" -@using ArcaneLibs.Extensions -@using LibMatrix.RoomTypes -@using System.Collections.ObjectModel -@using LibMatrix -@using System.Collections.Frozen -@using LibMatrix.EventTypes.Spec.State -

User Trace

-
- -

Users:

- -
-Import from room (ID) - -
- Rooms to be searched (@rooms.Count) - @foreach (var room in rooms) { - @room.RoomId -
- } -
-
-Execute -
- -
- Results - @foreach (var (userId, events) in matches) { -

@userId

-
    - @foreach (var eventResponse in events) { -
  • @eventResponse.Room.RoomId
  • - } -
- } -
-
- Results text - @{ - var col1Width = matches.Keys.Max(x => x.Length); - } -
-        @foreach (var (userId, events) in matches) {
-            

- @userId.PadRight(col1Width) - @foreach (var @event in events) { - -} -

- } -
-
- -
-@foreach (var line in log.Reverse()) { -
@line
-} - -@code { - private ObservableCollection log { get; set; } = new(); - List hss { get; set; } = new(); - ObservableCollection rooms { get; set; } = new(); - Dictionary> roomMembers { get; set; } = new(); - Dictionary> 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 UserIDs { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - log.CollectionChanged += (sender, args) => StateHasChanged(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - rooms.CollectionChanged += (sender, args) => StateHasChanged(); - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); - if (session is not null) { - var sessionRooms = await session.GetJoinedRooms(); - foreach (var room in sessionRooms) { - rooms.Add(room); - } - - StateHasChanged(); - log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); - } - } - - log.Add("Done fetching rooms!"); - - var distinctRooms = rooms.DistinctBy(x => x.RoomId).ToArray(); - Random.Shared.Shuffle(distinctRooms); - rooms = new ObservableCollection(distinctRooms); - rooms.CollectionChanged += (sender, args) => StateHasChanged(); - - var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync(false))).ToAsyncEnumerable(); - - await foreach (var (room, state) in stateTasks) { - roomMembers.Add(room, state); - log.Add($"Got {state.Count} members for {room.RoomId}..."); - } - - log.Add($"Done fetching members!"); - - UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - foreach (var userId in UserIDs) { - matches.Add(userId, new List()); - foreach (var (room, events) in roomMembers) { - if (events.Any(x => x.Type == RoomMemberEventContent.EventId && x.StateKey == userId)) { - matches[userId].Add(new() { - Event = events.First(x => x.StateKey == userId && x.Type == RoomMemberEventContent.EventId), - Room = room, - }); - } - } - } - - 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 Matches { - public GenericRoom Room; - - public StateEventResponse Event; - // public - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor deleted file mode 100644 index 263879b..0000000 --- a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor +++ /dev/null @@ -1,114 +0,0 @@ -@page "/Tools/SpaceDebug" -@using LibMatrix.Helpers -@using LibMatrix.Utilities -

SpaceDebug

-
- -

@Status

- -Has parent: -
- -@foreach (var (roomId, parents) in SpaceParents) { -

@roomId's parents

-
    - @foreach (var parent in parents) { -
  • @parent
  • - } -
-} - -Space children: - -@foreach (var (roomId, children) in SpaceChildren) { -

@roomId's children

-
    - @foreach (var child in children) { -
  • @child
  • - } -
-} - -@code { - private string _status = "Loading..."; - - public string Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - public Dictionary> SpaceChildren { get; set; } = new(); - public Dictionary> SpaceParents { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - Status = "Getting homeserver..."; - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - var syncHelper = new SyncHelper(hs) { - // Filter = new SyncFilter() { - // Presence = new(0), - // Room = new() { - // AccountData = new(limit: 0), - // Ephemeral = new(limit: 0), - // State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }), - // Timeline = new(limit: 0) - // }, - // AccountData = new(limit: 0) - // } - NamedFilterName = CommonSyncFilters.GetSpaceRelations - }; - - Status = "Syncing..."; - - var syncs = syncHelper.EnumerateSyncAsync(); - await foreach (var sync in syncs) { - if (sync is null) { - Status = "Sync failed"; - continue; - } - - if (sync.Rooms is null) { - Status = "No rooms in sync..."; - break; - } - - if (sync.Rooms.Join is null) { - Status = "No joined rooms in sync..."; - break; - } - - if (sync.Rooms.Join.Count == 0) { - Status = "Joined rooms list was empty..."; - break; - } - - // nextBatch = sync.NextBatch; - foreach (var (roomId, data) in sync.Rooms!.Join!) { - data.State?.Events?.ForEach(e => { - if (e.Type == "m.space.child") { - if (!SpaceChildren.ContainsKey(roomId)) SpaceChildren[roomId] = new(); - if (e.RawContent is null) e.StateKey += " (null)"; - else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; - SpaceChildren[roomId].Add(e.StateKey); - } - if (e.Type == "m.space.parent") { - if (!SpaceParents.ContainsKey(roomId)) SpaceParents[roomId] = new(); - if (e.RawContent is null) e.StateKey += " (null)"; - else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; - SpaceParents[roomId].Add(e.StateKey); - } - }); - } - Status = $"Synced {sync.Rooms.Join.Count} rooms, found {SpaceChildren.Count} spaces, {SpaceParents.Count} parents"; - } - Status = $"Synced: found {SpaceChildren.Count}->{SpaceChildren.Sum(x => x.Value.Count)} spaces, {SpaceParents.Count}->{SpaceParents.Sum(x => x.Value.Count)} parents!"; - - await base.OnInitializedAsync(); - } - - -} diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor new file mode 100644 index 0000000..667b518 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor @@ -0,0 +1,82 @@ +@page "/Tools/CopyPowerlevel" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.RoomTypes +

Copy powerlevel

+
+ +

Users:

+@foreach (var hs in hss) { +

@hs.WhoAmI.UserId

+} + +
+Execute +
+@foreach (var line in Enumerable.Reverse(log)) { +

@line

+} + +@code { + private List log { get; set; } = new(); + List hss { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var sessions = await RMUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await RMUStorage.GetSession(userAuth); + if (session is not null) { + hss.Add(session); + StateHasChanged(); + } + } + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + foreach (var hs in hss) { + var rooms = await hs.GetJoinedRooms(); + var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + } + } + + private async Task Execute(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; + if (!pls.UserHasStatePermission(hs.WhoAmI.UserId, RoomPowerLevelEventContent.EventId)) return "I do not have permission to send PL in " + room.RoomId; + foreach (var ahs in hss) { + if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.GetUserPowerLevel(ahs.WhoAmI.UserId)) { + log.Add("I am same PL in " + room.RoomId); + continue; + } + + pls.SetUserPowerLevel(ahs.WhoAmI.UserId, pls.GetUserPowerLevel(hs.WhoAmI.UserId)); + await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls); + log.Add($"Updated powerlevel of {room.RoomId} to {pls.GetUserPowerLevel(ahs.WhoAmI.UserId)}"); + } + + } + catch (MatrixException e) { + return $"Failed to update PLs in {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to update PLs in {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor new file mode 100644 index 0000000..a2ad388 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor @@ -0,0 +1,107 @@ +@page "/Tools/MassRoomJoin" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +

Mass join room

+
+

Room:

+ + +

Users:

+@foreach (var hs in hss) { +

@hs.WhoAmI.UserId

+} + +
+Execute +
+@foreach (var line in Enumerable.Reverse(log)) { +

@line

+} + +@code { + private List log { get; set; } = new(); + List hss { get; set; } = new(); + string roomId { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var sessions = await RMUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await RMUStorage.GetSession(userAuth); + if (session is not null) { + hss.Add(session); + StateHasChanged(); + } + } + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + // foreach (var hs in hss) { + // var rooms = await hs.GetJoinedRooms(); + var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + tasks = hss.Select(ExecuteJoin).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + // } + } + + private async Task ExecuteInvite(AuthenticatedHomeserverGeneric hs) { + var room = hs.GetRoom(roomId); + try { + try { + var joinRule = await room.GetJoinRuleAsync(); + if (joinRule.JoinRule == RoomJoinRulesEventContent.JoinRules.Public) return "Room is public, no invite needed"; + } + catch { } + var pls = await room.GetPowerLevelsAsync(); + if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + room.RoomId; + await room.InviteUsersAsync(hss.Select(x => x.WhoAmI.UserId).ToList()); + log.Add($"Invited to {room.RoomId} to {pls.GetUserPowerLevel(hs.WhoAmI.UserId)}"); + } + catch (MatrixException e) { + return $"Failed to invite in {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to invite in {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + + private async Task ExecuteJoin(AuthenticatedHomeserverGeneric hs) { + var room = hs.GetRoom(roomId); + try { + try { + var mse = await room.GetStateOrNullAsync(RoomMemberEventContent.EventId, hs.WhoAmI.UserId); + if (mse?.Membership == "join") return $"User {hs.WhoAmI.UserId} already in room"; + } + catch { } + await room.JoinAsync(); + } + catch (MatrixException e) { + return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor new file mode 100644 index 0000000..d8b02bb --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor @@ -0,0 +1,27 @@ +@page "/Tools/ViewAccountData" +@using ArcaneLibs.Extensions +@using LibMatrix +

View account data

+
+
@globalAccountData?.Events.ToJson(ignoreNull: true)
+
+ +@foreach (var (key, value) in perRoomAccountData) { + @key

+
@value?.Events.ToJson(ignoreNull: true)
+} + +@code { + EventList? globalAccountData; + Dictionary perRoomAccountData = new(); + + protected override async Task OnInitializedAsync() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + perRoomAccountData = await hs.EnumerateAccountDataPerRoom(); + globalAccountData = await hs.EnumerateAccountData(); + + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/UserTrace.razor deleted file mode 100644 index 95fe02b..0000000 --- a/MatrixUtils.Web/Pages/Tools/UserTrace.razor +++ /dev/null @@ -1,198 +0,0 @@ -@page "/Tools/UserTrace" -@using ArcaneLibs.Extensions -@using LibMatrix.RoomTypes -@using System.Collections.ObjectModel -@using LibMatrix -@using System.Collections.Frozen -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Filters -@using MatrixUtils.Abstractions -

User Trace

-
- -

Users:

- -
-Import from room (ID) - -
- Rooms to be searched (@rooms.Count) - @foreach (var room in rooms) { - @room.RoomId -
- } -
-
-Execute -
- -
- Results - @foreach (var (userId, events) in matches) { -

@userId

-
    - @foreach (var match in events) { -
  • -
      -
    • @match.RoomName (@match.Room.RoomId)
    • -
    • Membership: @(match.Event.RawContent.ToJson(indent: false))
    • -
    -
  • - } -
- } -
- -
-@foreach (var line in log.Reverse()) { -
@line
-} - -@code { - - private ObservableCollection log { get; set; } = new(); - - // List rooms { get; set; } = new(); - List rooms { get; set; } = []; - Dictionary> 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 UserIDs { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - log.CollectionChanged += (sender, args) => StateHasChanged(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - // var sessions = await RMUStorage.GetAllTokens(); - // var baseRooms = new List(); - // 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}"); - } - } - - //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(); - log.Add($"Got {rooms.Count} rooms"); - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - foreach (var userId in UserIDs) { - matches.Add(userId, new List()); - - // 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); - } - } - - 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 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; - } - } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor deleted file mode 100644 index d8b02bb..0000000 --- a/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor +++ /dev/null @@ -1,27 +0,0 @@ -@page "/Tools/ViewAccountData" -@using ArcaneLibs.Extensions -@using LibMatrix -

View account data

-
-
@globalAccountData?.Events.ToJson(ignoreNull: true)
-
- -@foreach (var (key, value) in perRoomAccountData) { - @key

-
@value?.Events.ToJson(ignoreNull: true)
-} - -@code { - EventList? globalAccountData; - Dictionary perRoomAccountData = new(); - - protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - perRoomAccountData = await hs.EnumerateAccountDataPerRoom(); - globalAccountData = await hs.EnumerateAccountData(); - - StateHasChanged(); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpace.razor b/MatrixUtils.Web/Pages/User/DMSpace.razor deleted file mode 100644 index e3dba30..0000000 --- a/MatrixUtils.Web/Pages/User/DMSpace.razor +++ /dev/null @@ -1,104 +0,0 @@ -@page "/User/DMSpace/Setup" -@using LibMatrix -@using LibMatrix.Responses -@using MatrixUtils.Abstractions -@using MatrixUtils.LibDMSpace -@using MatrixUtils.LibDMSpace.StateEvents -@using MatrixUtils.Web.Pages.User.DMSpaceStages -@using System.Text.Json.Serialization -

DM Space Management

-
- - @switch (Stage) { - case -1: -

Initialising...

- break; - case 0: - - break; - case 1: - - break; - case 2: - - break; - case 3: - - break; - default: -

Stage is unknown value: @Stage!

- break; - } -
- -@code { - private int _stage = -1; - - [Parameter, SupplyParameterFromQuery(Name = "stage")] - public int Stage { - get => _stage; - set { - _stage = value; - Console.WriteLine($"Stage is now {value}"); - StateHasChanged(); - } - } - - - public DMSpace? DMSpaceRootPage { get; set; } - - protected override async Task OnInitializedAsync() { - if (NavigationManager.Uri.Contains("?stage=")) { - NavigationManager.NavigateTo("/User/DMSpace/Setup", true); - } - DMSpaceRootPage = this; - SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); - if (SetupData.Homeserver is null) return; - try { - SetupData.DmSpaceConfiguration = await SetupData.Homeserver.GetAccountDataAsync("gay.rory.dm_space"); - var room = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); - await room.GetStateAsync(DMSpaceInfo.EventId); - Stage = 1; - } - catch (MatrixException e) { - if (e.ErrorCode is "M_NOT_FOUND" or "M_FORBIDDEN") { - Stage = 0; - SetupData.DmSpaceConfiguration = new(); - } - else throw; - } - finally { - StateHasChanged(); - } - await base.OnInitializedAsync(); - } - - protected override async Task OnParametersSetAsync() { - StateHasChanged(); - await base.OnParametersSetAsync(); - } - - public DMSpaceSetupData SetupData { get; set; } = new(); - - public class DMSpaceSetupData { - - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } - - public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); - - public Dictionary? Spaces; - - public Dictionary>? DMRooms; - - public RoomInfo? DMSpaceRoomInfo { get; set; } - - - public class UserProfileWithId : UserProfileResponse { - [JsonIgnore] - public string Id { get; set; } - } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor deleted file mode 100644 index 5f6508c..0000000 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor +++ /dev/null @@ -1,11 +0,0 @@ - - Welcome to the DM Space tool! - -

This wizard will help you set up a DM space.

-

This is useful for eg. sharing DM rooms across multiple accounts.

-
-Get started - -@code { - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor deleted file mode 100644 index 2176467..0000000 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor +++ /dev/null @@ -1,151 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.Responses -@using MatrixUtils.LibDMSpace -@using MatrixUtils.LibDMSpace.StateEvents -@using Microsoft.Extensions.Primitives -@using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State -@using MatrixUtils.Abstractions - - DM Space setup tool - stage 1: Configure space - -

You will need a space to use for DM rooms.

-@if (SetupData is not null) { - if (SetupData.Spaces is not null) { -

- Selected space: - - - @foreach (var (id, roomInfo) in SetupData.Spaces) { - - } - -

-

- - Create sub-spaces per user -

- -
- Disband - Next - } - else { -

Discovering spaces, please wait...

- } -} -else { - Error: Setup data is null! -} - - -@if (!string.IsNullOrWhiteSpace(Status)) { -

@Status

-} - -@code { - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string? _status; - - [CascadingParameter] - public DMSpace.DMSpaceSetupData SetupData { get; set; } - - SemaphoreSlim _semaphoreSlim = new(1, 1); - - protected override async Task OnInitializedAsync() { - if (SetupData is null) - return; - - await _semaphoreSlim.WaitAsync(); - - Dictionary spaces = []; - SetupData.DmSpaceConfiguration ??= new(); - - Status = "Looking for spaces..."; - var userRoomsEnum = SetupData.Homeserver!.GetJoinedRoomsByType("m.space"); - - List userRooms = new(); - await foreach (var room in userRoomsEnum) { - userRooms.Add(room); - } - - var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); - await foreach (var room in roomChecks) - if (room.HasValue) - spaces.TryAdd(room.Value.id, room.Value.roomInfo); - - SetupData.Spaces = spaces; - - Status = "Done!"; - _semaphoreSlim.Release(); - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - if (string.IsNullOrWhiteSpace(SetupData!.DmSpaceConfiguration!.DMSpaceId)) { - var createRoomRequest = CreateRoomRequest.CreatePrivate(SetupData.Homeserver!, "Direct Messages"); - createRoomRequest.CreationContentBaseType.Type = "m.space"; - SetupData.DmSpaceConfiguration.DMSpaceId = (await SetupData.Homeserver!.CreateRoom(createRoomRequest)).RoomId; - } - - await SetupData.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, SetupData.DmSpaceConfiguration); - var space = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); - await space.SendStateEventAsync(DMSpaceInfo.EventId, SetupData.DmSpaceInfo); - SetupData.DMSpaceRoomInfo = new RoomInfo(space); - await SetupData.DMSpaceRoomInfo.FetchAllStateAsync(); - - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); - } - - public async Task<(string id, RoomInfo roomInfo)?> GetFeasibleSpaces(GenericRoom room) { - try { - var ri = new RoomInfo(room); - - await foreach(var evt in room.GetFullStateAsync()) - ri.StateEvents.Add(evt); - - var powerLevels = (await ri.GetStateEvent(RoomPowerLevelEventContent.EventId)).TypedContent as RoomPowerLevelEventContent; - if (!powerLevels.UserHasStatePermission(SetupData.Homeserver.WhoAmI.UserId, SpaceChildEventContent.EventId)) { - Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); - return null; - } - - Status = $"Found viable space: {ri.RoomName}"; - if (!string.IsNullOrWhiteSpace(SetupData.DmSpaceConfiguration!.DMSpaceId)) { - if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is { } dsi) { - SetupData.DmSpaceConfiguration.DMSpaceId = room.RoomId; - SetupData.DmSpaceInfo = dsi; - Console.WriteLine(dsi.ToJson(ignoreNull: true)); - } - } - - if (ri.RoomName == room.RoomId) - ri.RoomName = await room.GetNameOrFallbackAsync(); - - return (room.RoomId, ri); - } - catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); - else throw; - } - - return null; - } - - private async Task Disband() { - var space = new DMSpaceRoom(SetupData.Homeserver, SetupData.DmSpaceConfiguration.DMSpaceId); - await space.DisbandDMSpace(); - NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor deleted file mode 100644 index a70e9c5..0000000 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor +++ /dev/null @@ -1,240 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Responses -@using MatrixUtils.LibDMSpace -@using MatrixUtils.LibDMSpace.StateEvents -@using ArcaneLibs.Extensions -@using System.Text.Json.Serialization -@using MatrixUtils.Abstractions - - DM Space setup tool - stage 2: Fix DM room attribution - -

This is just to make sure that your DMs are attributed to the right person!

- -@if (!string.IsNullOrWhiteSpace(Status)) { -

@Status

-} - -@if (SetupData is not null) { - if (SetupData.DMRooms is { Count: > 0 }) { - @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.Id)) { - - @foreach (var roomInfo in room) { - - Reassign - - } - } - } - else { -

DM room list is loading, please wait...

- } -} -else { - Error: DMSpaceRootPage is null! -} - -
-Next - -@{ - var _offset = 0; -} -@foreach (var (room, usersList) in duplicateDmRooms) { - -

Found room assigned to multiple users:

-

Users:

- @foreach (var userProfileResponse in usersList) { - - Assign to - - -
- } -
-} - -@if (DmToReassign is not null) { - - - @foreach (var userProfileResponse in roomMembers[DmToReassign]) { - - Assign to - - -
- } -
-} - -@code { - - private string newMxid { get; set; } = ""; - - private RoomInfo? DmToReassign { - get => _dmToReassign; - set { - _dmToReassign = value; - StateHasChanged(); - } - } - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string? _status; - private RoomInfo? _dmToReassign; - - [CascadingParameter] - public DMSpace.DMSpaceSetupData SetupData { get; set; } - - private Dictionary> duplicateDmRooms { get; set; } = new(); - private Dictionary> roomMembers { get; set; } = new(); - - SemaphoreSlim _semaphore = new(1, 1); - - protected override async Task OnInitializedAsync() { - if (SetupData is null) - return; - await _semaphore.WaitAsync(); - DmToReassign = null; - var hs = SetupData.Homeserver; - Status = "Loading DM list from account data..."; - var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); - Status = "Optimising DM list from account data..."; - var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); - foreach (var (user, rooms) in dms) { - for (var i = rooms.Count - 1; i >= 0; i--) { - var roomId = rooms[i]; - if (!joinedRooms.Contains(roomId)) - rooms.RemoveAt(i); - } - - dms[user] = rooms.Distinct().ToList(); - } - - dms.RemoveAll((x, y) => y is { Count: 0 }); - await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); - - Status = "DM list optimised, fetching info..."; - - SetupData.DMRooms = new Dictionary>(); - - var results = dms.Select(async x => { - var (userId, rooms) = x; - DMSpace.DMSpaceSetupData.UserProfileWithId userProfile; - try { - var profile = await SetupData.Homeserver.GetProfileAsync(userId); - userProfile = new() { - AvatarUrl = profile.AvatarUrl, - Id = userId, - DisplayName = profile.DisplayName - }; - } - catch { - userProfile = new() { - AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - DisplayName = userId, - Id = userId - }; - } - - var roomList = new List(); - var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); - await foreach (var result in tasks) - roomList.Add(result); - return (userProfile, roomList); - // StateHasChanged(); - }).ToAsyncEnumerable(); - await foreach (var res in results) { - SetupData.DMRooms.Add(res.userProfile, res.roomList); - // Status = $"Listed {dmRooms.Count} users"; - } - - _semaphore.Release(); - var duplicateDmRoomIds = new Dictionary>(); - foreach (var (user, rooms) in SetupData.DMRooms) { - foreach (var roomInfo in rooms) { - if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) - duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); - duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); - } - } - - duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); - foreach (var (roomId, users) in duplicateDmRoomIds) { - duplicateDmRooms.Add(SetupData.DMRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); - } - - // StateHasChanged(); - Status = null; - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); - } - - private async Task GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo(room); - await roomInfo.FetchAllStateAsync(); - roomMembers[roomInfo] = new(); - // roomInfo.CreationEventContent = await room.GetCreateEventAsync(); - - if(roomInfo.RoomName == room.RoomId) - try { - roomInfo.RoomName = await room.GetNameOrFallbackAsync(); - } - catch { } - - var membersEnum = room.GetMembersEnumerableAsync(true); - await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent) - roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); - - try { - string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; - if (room is not null) - roomInfo.RoomIcon = roomIcon; - } - catch { } - - return roomInfo; - } - - private async Task> GetRoomInfoForRooms(List rooms) { - var tasks = rooms.Select(GetRoomInfo).ToList(); - await Task.WhenAll(tasks); - return tasks.Select(x => x.Result).ToList(); - } - - private async Task SetRoomAssignment(string roomId, string userId) { - var hs = SetupData.Homeserver; - Status = "Loading DM list from account data..."; - var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); - Status = "Updating DM list from account data..."; - - foreach (var (user, rooms) in dms) { - rooms.RemoveAll(x => x == roomId); - dms[user] = rooms.Distinct().ToList(); - } - - if (!dms.ContainsKey(userId)) - dms.Add(userId, new()); - dms[userId].Add(roomId); - dms.RemoveAll((x, y) => y is { Count: 0 }); - await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); - - duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); - StateHasChanged(); - if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); - } - -} diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor deleted file mode 100644 index 865e956..0000000 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor +++ /dev/null @@ -1,191 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Responses -@using MatrixUtils.LibDMSpace -@using MatrixUtils.LibDMSpace.StateEvents -@using ArcaneLibs.Extensions -@using System.Text.Json.Serialization -@using MatrixUtils.Abstractions - - - DM Space setup tool - stage 3: Preview space layout - -

This gives you a preview of how your settings would impact layout!

- -@if (!string.IsNullOrWhiteSpace(Status)) { -

@Status

-} - -@if (SetupData is not null) { - @if (SetupData.DMSpaceRoomInfo is not null) { -

- - Create sub-spaces per user -

- @if (!SetupData.DmSpaceInfo.LayerByUser) { - - @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { - @foreach (var roomInfo in room) { -
- -
- } - } - } - else { - - @foreach (var (user, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { -
- @{ - RoomInfo fakeRoom = new(SetupData.DMSpaceRoomInfo.Room) { - RoomName = user.DisplayName ?? user.Id, - RoomIcon = user.AvatarUrl - }; - } - -
- @foreach (var roomInfo in room) { -
- -
- } - } - } - } - else { - Error: SetupData.DMSpaceRoomInfo is null! - } -} -else { - Error: DMSpaceRootPageConfiguration is null! -} - -
-Next - -@code { - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string? _status; - - [CascadingParameter] - public DMSpace.DMSpaceSetupData SetupData { get; set; } - - SemaphoreSlim _semaphore = new(1, 1); - - protected override async Task OnInitializedAsync() { - if (SetupData is null) - return; - await _semaphore.WaitAsync(); - var hs = SetupData.Homeserver; - // var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration.DMSpaceId); - // SetupData. - // dmSpaceRoomInfo = new() { - // RoomName = await dmSpaceRoom.GetNameAsync(), - // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), - // RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - // Room = dmSpaceRoom - // }; - // dmSpaceInfo = await dmSpaceRoom.GetDMSpaceInfo(); - // Status = "Loading DM list from account data..."; - // var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); - - Status = "DM list optimised, fetching info..."; - // var results = dms.Select(async x => { - // var (userId, rooms) = x; - // UserProfileWithId userProfile; - // try { - // var profile = await SetupData.Homeserver.GetProfileAsync(userId); - // userProfile = new() { - // AvatarUrl = profile.AvatarUrl, - // Id = userId, - // DisplayName = profile.DisplayName - // }; - // } - // catch { - // userProfile = new() { - // AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - // DisplayName = userId, - // Id = userId - // }; - // } - // var roomList = new List(); - // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); - // await foreach (var result in tasks) - // roomList.Add(result); - // return (userProfile, roomList); - // }).ToAsyncEnumerable(); - // await foreach (var res in results) { - // dmRooms.Add(new RoomInfo() { - // Room = dmSpaceRoom, - // RoomIcon = res.userProfile.AvatarUrl, - // RoomName = res.userProfile.DisplayName, - // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() - // }, res.roomList); - // } - await SetupData.DMSpaceRoomInfo!.FetchAllStateAsync(); - _semaphore.Release(); - Status = null; - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - var hs = SetupData.Homeserver; - var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration!.DMSpaceId!); - await dmSpaceRoom.ImportNativeDMs(); - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); - } - - private async Task GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo(room); - var roomMembers = new List(); - roomInfo.CreationEventContent = await room.GetCreateEventAsync(); - try { - roomInfo.RoomName = await room.GetNameAsync(); - } - catch { } - - var membersEnum = room.GetMembersEnumerableAsync(true); - await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent) - roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); - - if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { - List displayNames = new List(); - foreach (var member in roomMembers) - if (!string.IsNullOrWhiteSpace(member.DisplayName)) - displayNames.Add(member.DisplayName); - roomInfo.RoomName = string.Join(", ", displayNames); - } - - try { - string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; - if (room is not null) - roomInfo.RoomIcon = roomIcon; - } - catch { } - - return roomInfo; - } - - private async Task> GetRoomInfoForRooms(List rooms) { - var tasks = rooms.Select(GetRoomInfo).ToList(); - await Task.WhenAll(tasks); - return tasks.Select(x => x.Result).ToList(); - } - - private class UserProfileWithId : UserProfileResponse { - [JsonIgnore] - public string Id { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/NavMenu.razor b/MatrixUtils.Web/Shared/NavMenu.razor index 43e2237..770a246 100644 --- a/MatrixUtils.Web/Shared/NavMenu.razor +++ b/MatrixUtils.Web/Shared/NavMenu.razor @@ -7,6 +7,8 @@ +@* icons: https://www.appstudio.dev/app/OpenIconic.html *@ +