diff --git a/LibMatrix b/LibMatrix
-Subproject 16e314ed714f8b3e298c0ecf2ebfe67b48e5f69
+Subproject ecddd026574fac2b22495acef70a079661f4992
diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor
index 01ab1c4..bec0a72 100644
--- a/MatrixUtils.Web/Pages/Rooms/Space.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Space.razor
@@ -6,15 +6,15 @@
<button onclick="@JoinAllRooms">Join all rooms</button>
@foreach (var room in Rooms) {
- <RoomListItem Room="room" ShowOwnProfile="true"></RoomListItem>
+ @* <RoomListItem Room="room" ShowOwnProfile="true"></RoomListItem> *@
}
<br/>
<details style="background: #0002;">
<summary style="background: #fff1;">State list</summary>
- @foreach (var stateEvent in States.OrderBy(x => x.StateKey).ThenBy(x => x.Type)) {
- <p>@stateEvent.StateKey/@stateEvent.Type:</p>
+ @foreach (var stateEvent in States.OrderBy(x => x.Type).ThenBy(x => x.StateKey)) {
+ <p>@stateEvent.Type/@stateEvent.StateKey:</p>
<pre>@stateEvent.RawContent.ToJson()</pre>
}
</details>
@@ -26,13 +26,15 @@
private GenericRoom? Room { get; set; }
- private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>();
+ private List<StateEventResponse> States { get; set; } = [];
private List<GenericRoom> Rooms { get; } = new();
private List<string> ServersInSpace { get; } = new();
+ private AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!;
protected override async Task OnInitializedAsync() {
var hs = await RMUStorage.GetCurrentSessionOrNavigate();
if (hs is null) return;
+ Homeserver = hs;
Room = hs.GetRoom(RoomId.Replace('~', '.'));
@@ -55,49 +57,54 @@
break;
}
}
+ States.Add(stateEvent);
}
- await base.OnInitializedAsync();
+
+ StateHasChanged();
- // var state = await Room.GetStateAsync("");
- // if (state is not null) {
- // // Console.WriteLine(state.Value.ToJson());
- // States = state.Value.Deserialize<StateEventResponse[]>()!;
- //
- // foreach (var stateEvent in States) {
- // if (stateEvent.Type == "m.space.child") {
- // // if (stateEvent.Content.ToJson().Length < 5) return;
- // var roomId = stateEvent.StateKey;
- // var room = hs.GetRoom(roomId);
- // if (room is not null) {
- // Rooms.Add(room);
- // }
- // }
- // else if (stateEvent.Type == "m.room.member") {
- // var serverName = stateEvent.StateKey.Split(':').Last();
- // if (!ServersInSpace.Contains(serverName)) {
- // ServersInSpace.Add(serverName);
- // }
- // }
- // }
+ // var state = await Room.GetStateAsync("");
+ // if (state is not null) {
+ // // Console.WriteLine(state.Value.ToJson());
+ // States = state.Value.Deserialize<StateEventResponse[]>()!;
+ //
+ // foreach (var stateEvent in States) {
+ // if (stateEvent.Type == "m.space.child") {
+ // // if (stateEvent.Content.ToJson().Length < 5) return;
+ // var roomId = stateEvent.StateKey;
+ // var room = hs.GetRoom(roomId);
+ // if (room is not null) {
+ // Rooms.Add(room);
+ // }
+ // }
+ // else if (stateEvent.Type == "m.room.member") {
+ // var serverName = stateEvent.StateKey.Split(':').Last();
+ // if (!ServersInSpace.Contains(serverName)) {
+ // ServersInSpace.Add(serverName);
+ // }
+ // }
+ // }
- // if(state.Value.TryGetProperty("Type", out var Type))
- // {
- // }
- // else
- // {
- // //this is fine, apprently...
- // //Console.WriteLine($"Room {room.RoomId} has no Content.Type in m.room.create!");
- // }
+ // if(state.Value.TryGetProperty("Type", out var Type))
+ // {
+ // }
+ // else
+ // {
+ // //this is fine, apprently...
+ // //Console.WriteLine($"Room {room.RoomId} has no Content.Type in m.room.create!");
+ // }
- // await base.OnInitializedAsync();
+ // await base.OnInitializedAsync();
}
private async Task JoinAllRooms() {
- // List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList();
- // await Task.WhenAll(tasks);
- foreach (var room in Rooms) {
- await room.JoinAsync(ServersInSpace.ToArray());
- }
+ var joinedRooms = await Homeserver.GetJoinedRooms();
+ List<Task<RoomIdResponse>> tasks = Rooms
+ .Where(x=>!joinedRooms.Any(y=>y.RoomId == x.RoomId))
+ .Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList();
+ await Task.WhenAll(tasks);
+ // foreach (var room in Rooms) {
+ // await room.JoinAsync(ServersInSpace.ToArray());
+ // }
}
}
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor
index e68bb9a..7c73b27 100644
--- a/MatrixUtils.Web/Pages/Tools/Index.razor
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor
@@ -15,7 +15,8 @@
<h4 class="tool-category">Room tools</h4>
<hr/>
-<a href="/Tools/Moderation/SpaceRestrictedJoins">Change space children join access rules</a><br/>
+<a href="/Tools/Room/SpaceRestrictedJoins">Change space children join access rules</a><br/>
+<a href="/Tools/Room/FixCanonicalParentSpace">Replace canonical parent space in rooms after upgrading space</a><br/>
<h4 class="tool-category">Moderation tools</h4>
<hr/>
diff --git a/MatrixUtils.Web/Pages/Tools/Room/FixCanonicalParentSpace.razor b/MatrixUtils.Web/Pages/Tools/Room/FixCanonicalParentSpace.razor
new file mode 100644
index 0000000..8222729
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Room/FixCanonicalParentSpace.razor
@@ -0,0 +1,139 @@
+@page "/Tools/Room/FixCanonicalParentSpace"
+@using System.Collections.ObjectModel
+@using System.Text.Json.Serialization
+@using ArcaneLibs.Extensions
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.RoomTypes
+<h3>Fix canonical parent space</h3>
+<hr/>
+<p>Note: This requires relevant privileges in space children.</p>
+<p>You <b>MUST</b> click "check" before executing, otherwise nothing will happen!</p>
+<p>Also, worth noting that canonical parent will be set if none or multiple were set previously!</p>
+<br/>
+
+<span>Old space ID: </span>
+<InputText @bind-Value="@OldSpaceId"></InputText>
+<br/>
+<span>New space ID: </span>
+<InputText @bind-Value="@NewSpaceId"></InputText>
+<br/>
+
+<br/>
+<LinkButton OnClick="Check">Check</LinkButton>
+<LinkButton OnClick="Execute">Execute</LinkButton>
+<br/>
+
+<br/>
+@foreach (var line in log.Reverse()) {
+ <pre>@line</pre>
+}
+
+@code {
+ private ObservableCollection<string> log { get; set; } = new();
+
+ [Parameter, SupplyParameterFromQuery(Name = "OldSpaceId")]
+ public string OldSpaceId { get; set; }
+
+ [Parameter, SupplyParameterFromQuery(Name = "NewSpaceId")]
+ public string NewSpaceId { get; set; }
+
+ private AuthenticatedHomeserverGeneric hs { get; set; }
+ private Dictionary<GenericRoom, CurrentStatus> _children = new();
+ private string[] NewVias = [];
+
+ protected override async Task OnInitializedAsync() {
+ log.CollectionChanged += (sender, args) => StateHasChanged();
+ hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+
+ log.Add($"Signed in as {hs.UserId}... Ready for input!");
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ }
+
+ private async Task Check() {
+ _children.Clear();
+ var newSpace = hs.GetRoom(NewSpaceId).AsSpace;
+
+ await foreach (var room in newSpace.GetChildrenAsync()) {
+ try {
+ var status = new CurrentStatus();
+ status.PowerLevels = await room.GetPowerLevelsAsync();
+ status.HasPermission = status.PowerLevels.UserHasStatePermission(hs.UserId, SpaceParentEventContent.EventId);
+
+ status.RoomState = await room.GetFullStateAsListAsync();
+ var parentEvents = status.RoomState.Where(x => x.Type == SpaceParentEventContent.EventId);
+
+ status.CurrentCanonicalParents = parentEvents
+ .Where(x => x.RawContent?["canonical"]?.GetValue<bool?>() == true)
+ .Select(x => x.StateKey)
+ .ToList()!;
+
+ // Just for end user usage:
+ status.Name = await room.GetNameOrFallbackAsync(maxMemberNames: 4);
+
+ log.Add($"{room.RoomId}: {status.ToJson()}");
+ _children.Add(room, status);
+ }
+ catch (MatrixException e) {
+ if (e is { ErrorCode: "M_FORBIDDEN" }) {
+ log.Add($"Warning: not in room {room.RoomId}!");
+ }
+ else {
+ log.Add($"Error checking {room.RoomId}: {e.Message}");
+ }
+ }
+ }
+
+ log.Add("Calculating new Via's...");
+ // Not compliant with spec recommendations, but selecting top 10 servers by member count is a fairly safe bet
+ var newSpaceMembers = await newSpace.GetMembersByHomeserverAsync();
+ NewVias = newSpaceMembers
+ .OrderByDescending(x => x.Value.Count)
+ .Select(x=>x.Key)
+ .Take(10)
+ .ToArray();
+ log.Add("New vias: " + NewVias.ToJson());
+ log.Add("Done! Verify that everything looks okay, and then click Execute!");
+ }
+
+ private async Task Execute() {
+ foreach (var (room, status) in _children) {
+ if (!status.HasPermission) {
+ log.Add($"Skipping {room.RoomId} due to lack of permissions!");
+ continue;
+ }
+
+ log.Add($"Updating parent spaces for {room.RoomId}");
+ foreach (var parentId in status.CurrentCanonicalParents) {
+ var oldParentEvent = await room.GetStateEventAsync(SpaceParentEventContent.EventId, parentId);
+ oldParentEvent.RawContent!["canonical"] = false;
+ await room.SendStateEventAsync(SpaceParentEventContent.EventId, parentId, oldParentEvent.RawContent);
+ }
+
+ var newParentEvent = new SpaceParentEventContent() {
+ Canonical = true,
+ Via = NewVias
+ };
+
+ await room.SendStateEventAsync(SpaceParentEventContent.EventId, NewSpaceId, newParentEvent);
+ }
+
+ log.Add("Done!");
+ }
+
+ private class CurrentStatus {
+ public string Name { get; set; } = "";
+ public bool HasPermission { get; set; }
+ public List<string> CurrentCanonicalParents { get; set; } = [];
+
+ [JsonIgnore]
+ public List<StateEventResponse> RoomState { get; set; } = [];
+
+ [JsonIgnore]
+ public RoomPowerLevelEventContent PowerLevels { get; set; } = new();
+ }
+
+}
\ No newline at end of file
|