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 --- 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 -- 34 files changed, 1912 insertions(+), 1522 deletions(-) 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 (limited to 'MatrixUtils.Web/Pages/Tools') 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 -- cgit 1.5.1