diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
index 932748d..d88d5b2 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -1,5 +1,6 @@
@page "/Rooms"
@using MatrixRoomUtils.Core.StateEventTypes
+@using MatrixRoomUtils.Core.StateEventTypes.Spec
<h3>Room list</h3>
@if (Rooms is not null) {
@@ -9,14 +10,14 @@
@code {
- private List<GenericRoom> Rooms { get; set; }
- private ProfileResponse GlobalProfile { get; set; }
-
+ private List<RoomInfo> Rooms { get; set; }
+ private ProfileResponseEventData GlobalProfile { get; set; }
+
protected override async Task OnInitializedAsync() {
var hs = await MRUStorage.GetCurrentSessionOrNavigate();
if (hs is null) return;
GlobalProfile = await hs.GetProfile(hs.WhoAmI.UserId);
- Rooms = await hs.GetJoinedRooms();
+ Rooms = (await hs.GetJoinedRooms()).Select(x => new RoomInfo() { Room = x }).ToList();
await base.OnInitializedAsync();
}
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
new file mode 100644
index 0000000..4cb16b8
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
@@ -0,0 +1,250 @@
+@page "/Rooms/{RoomId}/Policies"
+@using MatrixRoomUtils.Core.StateEventTypes
+@using System.Text.Json
+@using MatrixRoomUtils.Core.Helpers
+@using MatrixRoomUtils.Core.Responses
+@using MatrixRoomUtils.Core.StateEventTypes.Spec
+<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>
+<InputCheckbox @bind-Value="_enableAvatars" @oninput="GetAllAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label>
+
+
+@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.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
+ var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+ <tr>
+ <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button>
+ @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Redacted 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.TypedContent as PolicyRuleStateEventData).Entity == null)) {
+ var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent.ToJson(false, 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.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
+ var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+ <tr>
+ <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Redacted 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.TypedContent as PolicyRuleStateEventData).Entity == null)) {
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent.ToJson(false, 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>
+ @if (_enableAvatars) {
+ <th scope="col"></th>
+ }
+ <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.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
+ var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+ <tr>
+ @if (_enableAvatars) {
+ <td scope="col">
+ <img style="width: 48px; height: 48px; aspect-ratio: unset; border-radius: 50%;" src="@(avatars.ContainsKey(policyData.Entity) ? avatars[policyData.Entity] : "")"/>
+ </td>
+ }
+ <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>Redacted 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.TypedContent as PolicyRuleStateEventData).Entity == null)) {
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent.ToJson(false, 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; }
+
+ private bool _enableAvatars;
+
+ static readonly Dictionary<string, string?> avatars = new();
+ static readonly Dictionary<string, RemoteHomeServer> servers = new();
+
+ public static List<StateEventResponse> PolicyEvents { get; set; } = new();
+
+ protected override async Task OnInitializedAsync() {
+ await base.OnInitializedAsync();
+ var hs = await MRUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ RoomId = RoomId.Replace('~', '.');
+ await LoadStatesAsync();
+ Console.WriteLine("Policy list editor initialized!");
+ }
+
+ private async Task LoadStatesAsync() {
+ var hs = await MRUStorage.GetCurrentSession();
+ var room = await hs.GetRoom(RoomId);
+
+ var states = room.GetFullStateAsync();
+ await foreach (var state in states) {
+ if (!state.Type.StartsWith("m.policy.rule")) continue;
+ PolicyEvents.Add(state);
+ }
+
+
+ // var stateEventsQuery = await room.GetStateAsync("");
+ // var stateEvents = stateEventsQuery.Value.Deserialize<List<StateEventResponse>>();
+ // PolicyEvents = stateEvents.Where(x => x.Type.StartsWith("m.policy.rule"))
+ // .Select(x => JsonSerializer.Deserialize<StateEventResponse>(JsonSerializer.Serialize(x))).ToList();
+ StateHasChanged();
+ }
+
+ private async Task GetAvatar(string userId) {
+ try {
+ if (avatars.ContainsKey(userId)) return;
+ var hs = userId.Split(':')[1];
+ var server = servers.ContainsKey(hs) ? servers[hs] : new RemoteHomeServer(userId.Split(':')[1]);
+ if (!servers.ContainsKey(hs)) servers.Add(hs, server);
+ var profile = await server.GetProfile(userId);
+ avatars.Add(userId, MediaResolver.ResolveMediaUri(server.FullHomeServerDomain, profile.AvatarUrl));
+ servers.Add(userId, server);
+ StateHasChanged();
+ }
+ catch {
+ // ignored
+ }
+ }
+
+ private async Task GetAllAvatars() {
+ foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
+ await GetAvatar((policyEvent.TypedContent as PolicyRuleStateEventData).Entity);
+ }
+ StateHasChanged();
+ }
+
+}
\ No newline at end of file
|