From f4657d2b522b19fd32f0fd0a55944c770bfecb84 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 18 Apr 2025 00:13:22 +0200 Subject: Add join room debug tool, finish room state resync --- LibMatrix | 2 +- .../Synapse/SubTools/SynapseRoomStateResync.razor | 190 +++++++++++++++++---- MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor | 59 +++++++ MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor | 2 +- MatrixUtils.Web/Pages/Tools/Index.razor | 1 + 5 files changed, 223 insertions(+), 31 deletions(-) create mode 100644 MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor diff --git a/LibMatrix b/LibMatrix index 5121c35..6ed3618 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit 5121c355fa62a394b1090be67d48f972bcd5d4e4 +Subproject commit 6ed36188c9887cf1e49f2dab78edbfced5375a6e diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor index 05a4bbc..03e441e 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor @@ -1,41 +1,67 @@ @page "/HSAdmin/Synapse/ResyncState" @using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests

Resync room state with other server


-Room ID: -
-Via: -
-Execute + +@if (!Executing) { +

WARNING: Will likely not work on invite-only/knock rooms! May also mess with history visibility!

+ Room ID: + +
+ Via: + +
+ Execute +} @if (Executing) {

Execution in progress. DO NOT CLOSE THIS PAGE!

- @* stage 1 *@ +} +@* stage 1 *@ +@if (Stage >= 1) { @if (Members is null) {

Loading members...

} else {

Got @Members.Count local members

} - - @* stage 2 *@ - @if (DeleteStatus is not null) { -

Purging room, please wait...

-
@DeleteStatus.ToJson(ignoreNull: true)
- } - else { -

Purging room...

-
@DeleteStatus!.ToJson(ignoreNull: true)
+} + +@* stage 2 *@ +@if (Stage == 2) { +

Purging room, please wait...

+
@DeleteStatus.ToJson(ignoreNull: true)
+} + +@* stage 3 *@ + +@if (Stage == 3) { +

Rejoining room, please wait...

+

Members left to restore:

+ string members = ""; + foreach (var member in Members) { + members += $"{member.StateKey} ({member.ContentAs()?.ToJson(indent: false, ignoreNull: true)})\n"; } - @* stage 3 *@ +
+        @members
+    
} -@if (Done) { + +@if (Stage == 4) {

Execution finished. You may now close the page :)

} +@if (Error is not null) { +

Error: @Error.Message

+
+        @Error.ToString()
+    
+} @code { @@ -46,13 +72,18 @@ [Parameter] [SupplyParameterFromQuery(Name = "via")] public string? Via { get; set; } - - private bool Executing { get; set; } - private bool Done { get; set; } - - private List? Members { get; set; } private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + + // Execution flow + private int Stage { get; set; } + private bool Executing { get; set; } + private Exception? Error { get; set; } + + // Stage 1 + private List? Members { get; set; } + + // Stage 2 private SynapseAdminRoomDeleteStatus? DeleteStatus { get; set; } protected override async Task OnInitializedAsync() { @@ -62,18 +93,119 @@ StateHasChanged(); } - private async Task Execute() { + private Task Execute() => Execute(0); + + private async Task Execute(int startStage) { if (string.IsNullOrWhiteSpace(RoomId)) return; if (string.IsNullOrWhiteSpace(Via)) return; Executing = true; StateHasChanged(); - - Members = (await Homeserver.Admin.GetRoomMembersAsync(RoomId)) - .Members.Where(m => m.EndsWith(Homeserver.ServerName)) - .ToList(); + + await ExecuteStages(startStage); + StateHasChanged(); - - + } + + private async Task ExecuteStages(int startStage) { + if (startStage <= 1) + if (!await TryGetRoomMembers()) + return; + if (startStage <= 2) + if (!await TryPurgeRoom()) + return; + if (startStage <= 3) + if (!await TryRestoreRoom()) + return; + + Stage = 4; + Executing = false; + StateHasChanged(); + } + + private async Task TryGetRoomMembers() { + Stage = 1; + try { + Members = (await Homeserver.Admin.GetRoomStateAsync(RoomId, type: RoomMemberEventContent.EventId)) + .Events.Where(m => (m.StateKey?.EndsWith(':' + Homeserver.ServerName) ?? false) && m.ContentAs()!.Membership == "join") + .ToList(); + Console.WriteLine(Members.ToJson(ignoreNull: true)); + StateHasChanged(); + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } + } + + private async Task TryPurgeRoom() { + Stage = 2; + + try { + var resp = await Homeserver.Admin.DeleteRoom(RoomId, new SynapseAdminRoomDeleteRequest { + Block = true, + Purge = true, + ForcePurge = true + }, waitForCompletion: false); + + while (true) { + // we dont want API failure to break this step + try { + DeleteStatus = await Homeserver.Admin.GetRoomDeleteStatus(resp.DeleteId); + StateHasChanged(); + if (DeleteStatus.Status == SynapseAdminRoomDeleteStatus.Complete) { + return true; + } + + if (DeleteStatus.Status == SynapseAdminRoomDeleteStatus.Failed) { + Error = new Exception("Failed to delete room: " + DeleteStatus.ToJson()); + return Executing = false; + } + + await Task.Delay(1000); + } + catch { } + } + + StateHasChanged(); + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } + } + + private async Task TryRestoreRoom() { + Stage = 3; + try { + await Homeserver.Admin.BlockRoom(RoomId, block: false); + Members = Random.Shared.GetItems(Members.ToArray(), Members.Count).ToList(); + StateHasChanged(); + foreach (var member in Members) { + while (true) { + try { + var hs = member.StateKey == Homeserver.WhoAmI.UserId + ? Homeserver + : await Homeserver.Admin.GetHomeserverForUserAsync(member.StateKey!, TimeSpan.FromMinutes(120)); + await hs.GetRoom(RoomId).JoinAsync([Via], reason: "Reconciling state with " + Via, false); + await hs.GetRoom(RoomId).SendStateEventAsync(RoomMemberEventContent.EventId, member.StateKey, member.RawContent); + Members = Members.Skip(1).ToList(); + StateHasChanged(); + break; + } + catch (Exception e) { + Error = new Exception($"{DateTime.Now:u} Failed to join room: {member.StateKey}, retrying\n", e); + } + } + } + + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor new file mode 100644 index 0000000..319c9e7 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor @@ -0,0 +1,59 @@ +@page "/Tools/Debug/JoinRoom" +@using System.Collections.ObjectModel +

Join room

+
+Room ID: + +
+Via server: + +
+Join +

+@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; } + + [Parameter, SupplyParameterFromQuery(Name = "via")] + public string? Server { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (hs is null) return; + Log.CollectionChanged += (sender, args) => StateHasChanged(); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Join() { + if (string.IsNullOrWhiteSpace(RoomId)) return; + var room = hs.GetRoom(RoomId); + Log.Add("Got room object..."); + + if (hs is AuthenticatedHomeserverSynapse synapse) { + await synapse.Admin.BlockRoom(RoomId, false); + Log.Add($"Synapse: unblocked room"); + } + + try { + await room.JoinAsync([Server], checkIfAlreadyMember: false); + Log.Add("Joined 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/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor index 9a56fc0..7844331 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor @@ -1,4 +1,4 @@ -@page "/Tools/LeaveRoom" +@page "/Tools/Debug/LeaveRoom" @using System.Collections.ObjectModel

Leave room


diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor index f99e932..4a44753 100644 --- a/MatrixUtils.Web/Pages/Tools/Index.razor +++ b/MatrixUtils.Web/Pages/Tools/Index.razor @@ -30,6 +30,7 @@

Debugging tools


Debug space relationships
+Join room by ID
Leave room by ID
Locate lost media
Migrate users from a split room to a new room
-- cgit 1.5.1