MatrixRoomUtils

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | LICENSE

commit 5ad6fcf24b37fed7340ff1a4a8b7707902ef743d
parent df9031c47f8e97d8e2df3177093271a458f27267
Author: TheArcaneBrony <myrainbowdash949@gmail.com>
Date:   Mon,  1 May 2023 16:51:57 +0200

Add policy room discovery ,add room state viewer

Diffstat:
AMatrixRoomUtils.Core/StateEventStruct.cs | 14++++++++++++++
MMatrixRoomUtils.Web/Pages/PolicyListRoomList.razor | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
AMatrixRoomUtils.Web/Pages/RoomStateRoomList.razor | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MMatrixRoomUtils.Web/Shared/NavMenu.razor | 5+++++
5 files changed, 365 insertions(+), 14 deletions(-)

diff --git a/MatrixRoomUtils.Core/StateEventStruct.cs b/MatrixRoomUtils.Core/StateEventStruct.cs @@ -0,0 +1,13 @@ +namespace MatrixRoomUtils; + +public struct StateEventStruct +{ + public object content { get; set; } + public long origin_server_ts { get; set; } + public string sender { get; set; } + public string state_key { get; set; } + public string type { get; set; } + public string event_id { get; set; } + public string user_id { get; set; } + public string replaces_state { get; set; } +} +\ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor b/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor @@ -4,6 +4,7 @@ @using Blazored.LocalStorage @using System.Net.Http.Headers @using System.Text.Json +@using System.Xml.Schema @using MatrixRoomUtils.Extensions @using MatrixRoomUtils.StateEventTypes @inject ILocalStorageService LocalStorage @@ -15,12 +16,17 @@ @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.Replace('.', '~'))">@s</a> + <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">@s.Name</a> <br/> } <div style="margin-bottom: 4em;"></div> @@ -34,14 +40,16 @@ else // type = support.feline.msc3784 //support.feline.policy.lists.msc.v1 - public List<string> PolicyRoomList { get; set; } = new(); - public List<StateEvent<PolicyRuleStateEventData>> PolicyEvents { get; set; } = new(); + 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 (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage); await base.OnInitializedAsync(); - if(RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) + if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) { NavigationManager.NavigateTo("/Login"); return; @@ -58,27 +66,72 @@ else //get room list //temporary hack until rooms get enumerated... string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" }; + var _rooms = await wc.GetAsync($"{RuntimeStorage.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(128); + var tasks = new List<Task<PolicyRoomInfo?>>(); foreach (string room in rooms) { - Console.WriteLine($"Checking if {room} is a policy room..."); + 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 + { + await semaphore.WaitAsync(); + using HttpClient wc = new(); + wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken); var sk = await wc.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/org.matrix.mjolnir.shortcode"); if (sk.IsSuccessStatusCode) { - Console.WriteLine($"Got success..."); var sko = await sk.Content.ReadFromJsonAsync<JsonElement>(); if (sko.TryGetProperty("shortcode", out JsonElement shortcode)) { Console.WriteLine($"Room {room} has a shortcode: {shortcode.GetString()}!"); - PolicyRoomList.Add(room); - StateHasChanged(); + return new PolicyRoomInfo() { Name = room, Shortcode = shortcode.GetString(), RoomId = room }; } else Console.WriteLine("No record found..."); } - else Console.WriteLine($"Got failure {sk.StatusCode}..."); + else if (sk.StatusCode == System.Net.HttpStatusCode.NotFound) + { + } + else Console.WriteLine($"Got failure while checking {room}: {sk.StatusCode} ({await sk.Content.ReadAsStringAsync()})..."); + return null; } - - //print to console - Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}"); + 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 +} +\ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor b/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor @@ -0,0 +1,103 @@ +@page "/RoomStateViewer" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@using System.Net.Http.Headers +@using System.Text.Json +@using System.Xml.Schema +@using MatrixRoomUtils.Extensions +@using MatrixRoomUtils.StateEventTypes +@inject ILocalStorageService LocalStorage +@inject NavigationManager NavigationManager +<h3>Policy list editor</h3> + +<h5>Room list</h5> +<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.Name</a> + <br/> + } + <div style="margin-bottom: 4em;"></div> +} + +<LogView></LogView> + +@code { + + 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 (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage); + await base.OnInitializedAsync(); + if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) + { + NavigationManager.NavigateTo("/Login"); + return; + } + await EnumeratePolicyRooms(); + Console.WriteLine("Policy list editor initialized!"); + } + + private async Task EnumeratePolicyRooms() + { + using HttpClient wc = new(); + wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken); + + //get room list + //temporary hack until rooms get enumerated... + string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" }; + var _rooms = await wc.GetAsync($"{RuntimeStorage.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(128); + // 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)); + PolicyRoomList.AddRange(rooms.Select(x=>new PolicyRoomInfo() { Name = x, RoomId = x, Shortcode = "N/A" })); + + + //print to console + Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}"); + } + + + public struct PolicyRoomInfo + { + public string RoomId { get; set; } + public string Shortcode { get; set; } + public string Name { get; set; } + } +} +\ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor b/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor @@ -0,0 +1,174 @@ +@page "/RoomStateViewer/{RoomId}" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@using System.Net.Http.Headers +@using System.Text.Json +@using System.Xml.Schema +@using MatrixRoomUtils.Extensions +@using MatrixRoomUtils.StateEventTypes +@using MatrixRoomUtils.Web.Shared.IndexComponents +@inject ILocalStorageService LocalStorage +@inject NavigationManager NavigationManager +<h3>Room state viewer</h3> +<p>Room ID: @RoomId</p> + +<p>@status</p> + +<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events + +<table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col">Type</th> + <th scope="col">Content</th> + </tr> + </thead> + <tbody> + @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key == "").OrderBy(x => x.origin_server_ts)) + { + <tr> + <td>@stateEvent.type</td> + <td style="max-width: fit-content;"> + <pre>@stateEvent.content</pre> + </td> + </tr> + } + </tbody> +</table> + +@foreach (var group in FilteredEvents.GroupBy(x => x.state_key).OrderBy(x => x.Key).Where(x => x.Key != "")) +{ + <details> + <summary>@group.Key</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col">Type</th> + <th scope="col">Content</th> + </tr> + </thead> + <tbody> + @foreach (var stateEvent in group.OrderBy(x => x.origin_server_ts)) + { + <tr> + <td>@stateEvent.type</td> + <td style="max-width: fit-content;"> + <pre>@stateEvent.content</pre> + </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<PreRenderedStateEvent> FilteredEvents { get; set; } = new(); + public List<PreRenderedStateEvent> Events { get; set; } = new(); + public string status = ""; + + protected override async Task OnInitializedAsync() + { + if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage); + await base.OnInitializedAsync(); + if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) + { + NavigationManager.NavigateTo("/Login"); + return; + } + RoomId = RoomId.Replace('~', '.'); + await LoadStatesAsync(); + Console.WriteLine("Policy list editor initialized!"); + } + private DateTime _lastUpdate = DateTime.Now; + + private async Task LoadStatesAsync() + { + int StateLoaded = 0; + using var client = new HttpClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken); + var response = await client.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state"); + // var response = await client.GetAsync($"http://localhost:5117/matrix-hq-state.json"); + //var _events = await response.Content.ReadFromJsonAsync<Queue<StateEventStruct>>(); + var _data = await response.Content.ReadAsStreamAsync(); + var __events = JsonSerializer.DeserializeAsyncEnumerable<StateEventStruct>(_data); + await foreach (var _ev in __events) + { + var e = new PreRenderedStateEvent() + { + type = _ev.type, + state_key = _ev.state_key, + origin_server_ts = _ev.origin_server_ts, + content = _ev.content.ToJson(indent: true, ignoreNull: true), + }; + Events.Add(e); + if (string.IsNullOrEmpty(e.state_key)) + { + FilteredEvents.Add(e); + } + StateLoaded++; + if ((DateTime.Now - _lastUpdate).TotalMilliseconds > 100) + { + _lastUpdate = DateTime.Now; + status = $"Loaded {StateLoaded} state events"; + StateHasChanged(); + await Task.Delay(0); + } + + } + + StateHasChanged(); + } + + private async Task RebuildFilteredData() + { + status = "Rebuilding filtered data..."; + StateHasChanged(); + await Task.Delay(1); + var _FilteredEvents = Events; + if (!ShowMembershipEvents) + _FilteredEvents = _FilteredEvents.Where(x => x.type != "m.room.member").ToList(); + + status = "Done, rerendering!"; + StateHasChanged(); + await Task.Delay(1); + FilteredEvents = _FilteredEvents; + StateHasChanged(); + } + + + public struct PreRenderedStateEvent + { + public string content { get; set; } + public long origin_server_ts { get; set; } + public string state_key { get; set; } + public string type { get; set; } + // public string sender { get; set; } + // public string event_id { get; set; } + // public string user_id { get; set; } + // public string replaces_state { get; set; } + } + + public bool ShowMembershipEvents + { + get => _showMembershipEvents; + set + { + _showMembershipEvents = value; + RebuildFilteredData(); + } + } + + private bool _showMembershipEvents; +} +\ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/NavMenu.razor b/MatrixRoomUtils.Web/Shared/NavMenu.razor @@ -24,6 +24,11 @@ <span class="oi oi-plus" aria-hidden="true"></span> Policy list editor </NavLink> </div> + <div class="nav-item px-3"> + <NavLink class="nav-link" href="RoomStateViewer"> + <span class="oi oi-plus" aria-hidden="true"></span> Room state viewer + </NavLink> + </div> </nav> </div>