diff --git a/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListEditorPage.razor b/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListEditorPage.razor
new file mode 100644
index 0000000..5dfb2d6
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListEditorPage.razor
@@ -0,0 +1,228 @@
+@page "/PolicyListEditor/{RoomId}"
+@using System.Text.Json
+@using MatrixRoomUtils.Core.Extensions
+@using MatrixRoomUtils.Core.StateEventTypes
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Policy list editor - Editing @RoomId</h3>
+<hr/>
+
+<p>
+ This policy list contains @PolicyEvents.Count(x => x.type == "m.policy.rule.server") server bans,
+ @PolicyEvents.Count(x => x.type == "m.policy.rule.room") room bans and
+ @PolicyEvents.Count(x => x.type == "m.policy.rule.user") user bans.
+</p>
+
+
+@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.server"))
+{
+ <p>No server policies</p>
+}
+else
+{
+ <h3>Server policies</h3>
+ <hr/>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col" style="max-width: 50vw;">Server</th>
+ <th scope="col">Reason</th>
+ <th scope="col">Expires</th>
+ <th scope="col">Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.server" && x.content.Entity != null))
+ {
+ <tr>
+ <td>Entity: @policyEvent.content.Entity<br/>State: @policyEvent.state_key</td>
+ <td>@policyEvent.content.Reason</td>
+ <td>
+ @policyEvent.content.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col" style="max-width: 50vw;">State key</th>
+ <th scope="col">Serialised contents</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.server" && x.content.Entity == null))
+ {
+ <tr>
+ <td>@policyEvent.state_key</td>
+ <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+}
+@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.room"))
+{
+ <p>No room policies</p>
+}
+else
+{
+ <h3>Room policies</h3>
+ <hr/>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col" style="max-width: 50vw;">Room</th>
+ <th scope="col">Reason</th>
+ <th scope="col">Expires</th>
+ <th scope="col">Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.room" && x.content.Entity != null))
+ {
+ <tr>
+ <td>Entity: @policyEvent.content.Entity<br/>State: @policyEvent.state_key</td>
+ <td>@policyEvent.content.Reason</td>
+ <td>
+ @policyEvent.content.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col" style="max-width: 50vw;">State key</th>
+ <th scope="col">Serialised contents</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.room" && x.content.Entity == null))
+ {
+ <tr>
+ <td>@policyEvent.state_key</td>
+ <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+}
+@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.user"))
+{
+ <p>No user policies</p>
+}
+else
+{
+ <h3>User policies</h3>
+ <hr/>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col" style="max-width: 0.2vw; word-wrap: anywhere;">User</th>
+ <th scope="col">Reason</th>
+ <th scope="col">Expires</th>
+ <th scope="col">Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.user" && x.content.Entity != null))
+ {
+ <tr>
+ <td style="word-wrap: anywhere;">Entity: @string.Join("", policyEvent.content.Entity.Take(64))<br/>State: @string.Join("", policyEvent.state_key.Take(64))</td>
+ <td>@policyEvent.content.Reason</td>
+ <td>
+ @policyEvent.content.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col">State key</th>
+ <th scope="col">Serialised contents</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.user" && x.content.Entity == null))
+ {
+ <tr>
+ <td>@policyEvent.state_key</td>
+ <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+}
+
+<LogView></LogView>
+
+@code {
+ //get room list
+ // - sync withroom list filter
+ // type = support.feline.msc3784
+ //support.feline.policy.lists.msc.v1
+
+ [Parameter]
+ public string? RoomId { get; set; }
+
+ public List<StateEvent<PolicyRuleStateEventData>> PolicyEvents { get; set; } = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (!RuntimeCache.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+ await base.OnInitializedAsync();
+ // if(RuntimeCache.AccessToken == null || RuntimeCache.CurrentHomeserver == null)
+ if (RuntimeCache.CurrentHomeServer == null)
+ {
+ NavigationManager.NavigateTo("/Login");
+ return;
+ }
+ RoomId = RoomId.Replace('~', '.');
+ await LoadStatesAsync();
+ Console.WriteLine("Policy list editor initialized!");
+ }
+
+ private async Task LoadStatesAsync()
+ {
+ // using var client = new HttpClient();
+ // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+ // var response = await client.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
+ // var content = await response.Content.ReadAsStringAsync();
+ // Console.WriteLine(JsonSerializer.Deserialize<object>(content).ToJson());
+ // var stateEvents = JsonSerializer.Deserialize<List<StateEvent>>(content);
+ var room = await RuntimeCache.CurrentHomeServer.GetRoom(RoomId);
+ var stateEventsQuery = await room.GetStateAsync("");
+ if (stateEventsQuery == null)
+ {
+ Console.WriteLine("state events query is null!!!");
+ }
+ var stateEvents = stateEventsQuery.Value.Deserialize<List<StateEvent>>();
+ PolicyEvents = stateEvents.Where(x => x.type.StartsWith("m.policy.rule"))
+ .Select(x => JsonSerializer.Deserialize<StateEvent<PolicyRuleStateEventData>>(JsonSerializer.Serialize(x))).ToList();
+ StateHasChanged();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListRoomList.razor b/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListRoomList.razor
new file mode 100644
index 0000000..5656af9
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/PolicyList/PolicyListRoomList.razor
@@ -0,0 +1,158 @@
+@page "/PolicyListEditor"
+@using System.Text.Json
+@using MatrixRoomUtils.Core.Extensions
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Policy list editor - Room list</h3>
+<hr/>
+
+@if (PolicyRoomList.Count == 0)
+{
+ <p>No policy rooms found.</p>
+ <p>Loading progress: @checkedRoomCount/@totalRoomCount</p>
+}
+else
+{
+ @if (checkedRoomCount != totalRoomCount)
+ {
+ <p>Loading progress: @checkedRoomCount/@totalRoomCount</p>
+ }
+ foreach (var s in PolicyRoomList)
+ {
+ <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">[@s.Shortcode] @s.Name (@s.RoomId)</a>
+ <br/>
+ }
+}
+
+<div style="margin-bottom: 4em;"></div>
+<LogView></LogView>
+
+@code {
+ //get room list
+ // - sync withroom list filter
+ // type = support.feline.msc3784
+ //support.feline.policy.lists.msc.v1
+
+ public List<PolicyRoomInfo> PolicyRoomList { get; set; } = new();
+
+ private int checkedRoomCount { get; set; } = 0;
+ private int totalRoomCount { get; set; } = 0;
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (!RuntimeCache.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+ await base.OnInitializedAsync();
+ if (RuntimeCache.CurrentHomeServer == null)
+ {
+ NavigationManager.NavigateTo("/Login");
+ return;
+ }
+ await EnumeratePolicyRooms();
+ Console.WriteLine("Policy list editor initialized!");
+ }
+
+ private async Task EnumeratePolicyRooms()
+ {
+ var xxxrooms = await RuntimeCache.CurrentHomeServer.GetJoinedRooms();
+ totalRoomCount = xxxrooms.Count;
+ StateHasChanged();
+
+ var xxxsemaphore = new SemaphoreSlim(256);
+ var xxxtasks = new List<Task<PolicyRoomInfo?>>();
+ foreach (var room in xxxrooms)
+ {
+ xxxtasks.Add(GetPolicyRoomInfo(room.RoomId, xxxsemaphore));
+ }
+ var xxxresults = await Task.WhenAll(xxxtasks);
+ PolicyRoomList.AddRange(xxxresults.Where(x => x != null).Select(x => x.Value));
+
+ Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}");
+ return;
+ /*
+ using HttpClient wc = new();
+ wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+
+
+
+ //get room list
+ //temporary hack until rooms get enumerated...
+ string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" };
+ var _rooms = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/joined_rooms");
+ Console.WriteLine($"Got {_rooms.StatusCode}...");
+ if (!_rooms.IsSuccessStatusCode)
+ {
+ Console.WriteLine($"Failed to get rooms: {await _rooms.Content.ReadAsStringAsync()}");
+ return;
+ }
+ var _rooms_o = await _rooms.Content.ReadFromJsonAsync<JsonElement>();
+ if (_rooms_o.TryGetProperty("joined_rooms", out JsonElement _rooms_j))
+ {
+ rooms = _rooms_j.EnumerateArray().Select(x => x.GetString()).ToArray();
+ }
+
+ totalRoomCount = rooms.Length;
+ StateHasChanged();
+
+ var semaphore = new SemaphoreSlim(256);
+ var tasks = new List<Task<PolicyRoomInfo?>>();
+ foreach (string room in rooms)
+ {
+ tasks.Add(GetPolicyRoomInfo(room, semaphore));
+ }
+ var results = await Task.WhenAll(tasks);
+ PolicyRoomList.AddRange(results.Where(x => x != null).Select(x => x.Value));
+
+
+ //print to console
+ Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}");
+ */
+ }
+
+ private async Task<PolicyRoomInfo?> GetPolicyRoomInfo(string room, SemaphoreSlim semaphore)
+ {
+ try
+ {
+ //TODO: refactor!!!!!
+ await semaphore.WaitAsync();
+ PolicyRoomInfo roomInfo = new()
+ {
+ RoomId = room
+ };
+
+
+ // --- //
+ var r = await RuntimeCache.CurrentHomeServer.GetRoom(room);
+ var shortcodeState = await r.GetStateAsync("org.matrix.mjolnir.shortcode");
+ if(!shortcodeState.HasValue) return null;
+ roomInfo.Shortcode = shortcodeState.Value.TryGetProperty("shortcode", out JsonElement shortcode) ? shortcode.GetString() : null;
+
+ if (roomInfo.Shortcode != null)
+ {
+ roomInfo.Name = await r.GetNameAsync();
+ return roomInfo;
+ }
+
+ return null;
+ }
+ finally
+
+ {
+ checkedRoomCount++;
+ StateHasChanged();
+ semaphore.Release();
+ }
+ }
+
+ public struct PolicyRoomInfo
+
+ {
+ public
+ string RoomId { get; set; }
+
+ public
+ string? Shortcode { get; set; }
+
+ public
+ string? Name { get; set; }
+ }
+ }
\ No newline at end of file
|