diff --git a/LibMatrix b/LibMatrix
-Subproject 77650d16a9cc66fdfe393320164cd8248cdff38
+Subproject bf1664b254bfc224f0087eb82fdba5235fbd162
diff --git a/MatrixUtils.Web/Pages/About.razor b/MatrixUtils.Web/Pages/About.razor
index 330d1c2..9f83991 100644
--- a/MatrixUtils.Web/Pages/About.razor
+++ b/MatrixUtils.Web/Pages/About.razor
@@ -8,5 +8,5 @@
<p>These range from joining rooms on dead homeservers, to managing your accounts and rooms, and creating rooms based on templates.</p>
<br/>
-<p>You can find the source code on <a href="https://cgit.rory.gay/matrix/MatrixRoomUtils.git/">cgit.rory.gay</a>.<br/></p>
+<p>You can find the source code on <a href="https://cgit.rory.gay/matrix/tools/MatrixUtils.git/about/">cgit.rory.gay</a>.<br/></p>
<p>You can also join the <a href="https://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support">Matrix room</a> for this project.</p>
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
new file mode 100644
index 0000000..63dc206
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
@@ -0,0 +1,177 @@
+@page "/PolicyLists"
+@using System.Collections.ObjectModel
+@using ArcaneLibs.Extensions
+@using LibMatrix
+@using LibMatrix.EventTypes
+@using LibMatrix.EventTypes.Common
+@using LibMatrix.EventTypes.Spec.State.Policy
+@using LibMatrix.RoomTypes
+@inject ILogger<Index> logger
+<h3>Policy lists </h3> @* <LinkButton href="/Rooms/Create">Create new policy list</LinkButton> *@
+
+@if (!string.IsNullOrWhiteSpace(Status)) {
+ <p>@Status</p>
+}
+@if (!string.IsNullOrWhiteSpace(Status2)) {
+ <p>@Status2</p>
+}
+<hr/>
+
+<table>
+ <thead>
+ <tr>
+ <th/>
+ <th>Room name</th>
+ <th>Policies</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) {
+ <tr>
+ <td>
+ <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
+ <span class="oi oi-pencil" aria-hidden="true"></span>
+ </LinkButton>
+ </td>
+ <td style="padding-right: 24px;">
+ <span>@room.RoomName</span>
+ @if (room.IsLegacy) {
+ <span style="color: red;"> (legacy)</span>
+ }
+ <br/>
+ @if (!string.IsNullOrWhiteSpace(room.Shortcode)) {
+ <span style="font-size: 0.8em;">@room.Shortcode</span>
+ }
+ else {
+ <span style="color: red;">(no shortcode)</span>
+ }
+ </td>
+ <td>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.User) ?? 0) user policies</span><br/>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies</span><br/>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies</span><br/>
+ </td>
+ </tr>
+ }
+ </tbody>
+</table>
+
+@code {
+
+ private List<RoomInfo> Rooms { get; } = [];
+
+ private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ Homeserver = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (Homeserver is null) return;
+
+ Status = "Fetching rooms...";
+
+ var userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
+ var serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
+ var roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
+ var knownPolicyTypes = (List<string>) [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
+
+ List<GenericRoom> roomsByType = [];
+ await foreach (var room in Homeserver.GetJoinedRoomsByType("support.feline.policy.lists.msc.v1")) {
+ roomsByType.Add(room);
+ Status2 = $"Found {room.RoomId} (MSC3784)...";
+ }
+
+ List<Task<RoomInfo>> tasks = roomsByType.Select(async room => {
+ Status2 = $"Fetching room {room.RoomId}...";
+ return await RoomInfo.FromRoom(room);
+ }).ToList();
+
+ var results = tasks.ToAsyncEnumerable();
+ await foreach (var result in results) {
+ Rooms.Add(result);
+ StateHasChanged();
+ }
+
+ Status = "Searching for legacy lists...";
+
+ var rooms = (await Homeserver.GetJoinedRooms())
+ .Where(x => !Rooms.Any(y => y.Room.RoomId == x.RoomId))
+ .Select(async room => {
+ var state = await room.GetFullStateAsListAsync();
+ var policies = state
+ .Where(x => knownPolicyTypes.Contains(x.Type))
+ .ToList();
+ if (policies.Count == 0) return null;
+ Status2 = $"Found legacy list {room.RoomId}...";
+ return await RoomInfo.FromRoom(room, state, true);
+ })
+ .ToAsyncEnumerable();
+
+ await foreach (var room in rooms) {
+ if (room is not null) {
+ Rooms.Add(room);
+ StateHasChanged();
+ }
+ }
+
+ Status = "";
+ Status2 = "";
+ await base.OnInitializedAsync();
+ }
+
+ private string _status;
+
+ public string Status {
+ get => _status;
+ set {
+ _status = value;
+ StateHasChanged();
+ }
+ }
+
+ private string _status2;
+
+ public string Status2 {
+ get => _status2;
+ set {
+ _status2 = value;
+ StateHasChanged();
+ }
+ }
+
+ private class RoomInfo {
+ public GenericRoom Room { get; set; }
+ public string RoomName { get; set; }
+ public string? Shortcode { get; set; }
+ public Dictionary<PolicyType, int?> PolicyCounts { get; set; }
+ public bool IsLegacy { get; set; }
+
+ public enum PolicyType {
+ User,
+ Room,
+ Server
+ }
+
+ private static readonly List<string> userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
+ private static readonly List<string> serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
+ private static readonly List<string> roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
+ private static readonly List<string> allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
+
+ public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) {
+ state ??= await room.GetFullStateAsListAsync();
+ return new RoomInfo() {
+ Room = room,
+ IsLegacy = legacy,
+ RoomName = await room.GetNameAsync()
+ ?? (await room.GetCanonicalAliasAsync())?.Alias
+ ?? (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode
+ ?? room.RoomId,
+ Shortcode = (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode,
+ PolicyCounts = new() {
+ { PolicyType.User, state.Count(x => userEventTypes.Contains(x.Type)) },
+ { PolicyType.Server, state.Count(x => serverEventTypes.Contains(x.Type)) },
+ { PolicyType.Room, state.Count(x => roomEventTypes.Contains(x.Type)) }
+ }
+ };
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
new file mode 100644
index 0000000..f9b5b3f
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
@@ -0,0 +1,6 @@
+table, th, td {
+ border-width: 1px;
+}
+td {
+ padding: 8px;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor
index 8153224..088fdcd 100644
--- a/MatrixUtils.Web/Pages/Rooms/Space.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Space.razor
@@ -6,6 +6,9 @@
@using MatrixUtils.Abstractions
<h3>Room manager - Viewing Space</h3>
+<span>Add new room to space: </span>
+<FancyTextBox @bind-Value="@NewRoomId"></FancyTextBox>
+<button onclick="@AddNewRoom">Add</button>
<button onclick="@JoinAllRooms">Join all rooms</button>
@foreach (var room in Rooms) {
<RoomListItem RoomInfo="room" ShowOwnProfile="true"></RoomListItem>
@@ -31,6 +34,7 @@
private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>();
private List<RoomInfo> Rooms { get; } = new();
private List<string> ServersInSpace { get; } = new();
+ private string? NewRoomId { get; set; }
protected override async Task OnInitializedAsync() {
var hs = await RMUStorage.GetCurrentSessionOrNavigate();
@@ -123,7 +127,7 @@
var ce = await room.GetCreateEventAsync();
if(ce is null) continue;
if (ce.Type == "m.space") {
- var children = room.AsSpace.GetChildrenAsync(true);
+ var children = room.AsSpace.GetChildrenAsync(false);
await foreach (var child in children) {
JoinRecursive(child.RoomId);
}
@@ -137,4 +141,9 @@
}
+ private async Task AddNewRoom() {
+ if (string.IsNullOrWhiteSpace(NewRoomId)) return;
+ await Room.AsSpace.AddChildByIdAsync(NewRoomId);
+ }
+
}
diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
index f2af9b5..e093db2 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
@@ -140,7 +140,7 @@ else
var rgb = RoomData[roomName][date.Year][date];
if (message.RawContent?.Count == 0) rgb.R++;
- else if (string.IsNullOrWhiteSpace(message.Unsigned?.ReplacesState)) rgb.G++;
+ else if (message.Unsigned?.ContainsKey("replaces_state") ?? false) rgb.G++;
else rgb.B++;
RoomData[roomName][date.Year][date] = rgb;
}
diff --git a/MatrixUtils.Web/Shared/NavMenu.razor b/MatrixUtils.Web/Shared/NavMenu.razor
index 770a246..7371e66 100644
--- a/MatrixUtils.Web/Shared/NavMenu.razor
+++ b/MatrixUtils.Web/Shared/NavMenu.razor
@@ -37,6 +37,12 @@
</div>
<div class="nav-item px-3">
+ <NavLink class="nav-link" href="PolicyLists">
+ <span class="oi oi-ban" aria-hidden="true"></span> Manage policy lists
+ </NavLink>
+ </div>
+
+ <div class="nav-item px-3">
<NavLink class="nav-link" href="User/Profile">
<span class="oi oi-person" aria-hidden="true"></span> Manage profile
</NavLink>
|