about summary refs log tree commit diff
path: root/MatrixRoomUtils.Web/Pages
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-01-24 02:31:56 +0100
committerRory& <root@rory.gay>2024-01-24 17:05:25 +0100
commit03313562d21d5db9bf6a14ebbeab80e06c883d3a (patch)
treee000546a2ee8e6a886a7ed9fd01ad674178fb7cb /MatrixRoomUtils.Web/Pages
parentMake RMU installable (diff)
downloadMatrixUtils-03313562d21d5db9bf6a14ebbeab80e06c883d3a.tar.xz
MRU->RMU, fixes, cleanup
Diffstat (limited to 'MatrixRoomUtils.Web/Pages')
-rw-r--r--MatrixRoomUtils.Web/Pages/About.razor12
-rw-r--r--MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor71
-rw-r--r--MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor81
-rw-r--r--MatrixRoomUtils.Web/Pages/Dev/ModalTest.razor88
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor34
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor200
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css0
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor191
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor.css25
-rw-r--r--MatrixRoomUtils.Web/Pages/InvalidSession.razor100
-rw-r--r--MatrixRoomUtils.Web/Pages/LoginPage.razor160
-rw-r--r--MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor115
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Create.razor338
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Index.razor250
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor267
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Space.razor100
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor144
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor127
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor60
-rw-r--r--MatrixRoomUtils.Web/Pages/ServerInfo.razor235
-rw-r--r--MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor84
-rw-r--r--MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor54
-rw-r--r--MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor110
-rw-r--r--MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor111
-rw-r--r--MatrixRoomUtils.Web/Pages/Tools/SpaceDebug.razor113
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMManager.razor62
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMSpace.razor86
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor11
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor128
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor242
-rw-r--r--MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor191
-rw-r--r--MatrixRoomUtils.Web/Pages/User/Profile.razor134
32 files changed, 0 insertions, 3924 deletions
diff --git a/MatrixRoomUtils.Web/Pages/About.razor b/MatrixRoomUtils.Web/Pages/About.razor
deleted file mode 100644

index 18d7c3f..0000000 --- a/MatrixRoomUtils.Web/Pages/About.razor +++ /dev/null
@@ -1,12 +0,0 @@ -@page "/About" - -<PageTitle>About</PageTitle> - -<h3>Rory&::MatrixUtils - About</h3> -<hr/> -<p>Rory&::MatrixUtils is a "small" collection of tools to do not-so-everyday things.</p> -<p>These range from joining rooms on dead homeservers, to managing your accounts and rooms, and creating rooms based on templates.</p> - -<br/><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 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/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor b/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor deleted file mode 100644
index a1e928f..0000000 --- a/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor +++ /dev/null
@@ -1,71 +0,0 @@ -@page "/Dev/Options" -@using ArcaneLibs.Extensions -@using System.Text.Unicode -@using System.Text -@using System.Text.Json -@inject NavigationManager NavigationManager -@inject ILocalStorageService LocalStorage - -<PageTitle>Developer options</PageTitle> - -<h3>Rory&::MatrixUtils - Developer options</h3> -<hr/> - -<p> - <span>Import local storage: </span> - <InputFile OnChange="ImportLocalStorage"></InputFile> -</p> -<p> - <span>Export local storage: </span> - <button @onclick="@ExportLocalStorage">Export</button> -</p> - -@if (userSettings is not null) { - <InputCheckbox @bind-Value="@userSettings.DeveloperSettings.EnableLogViewers" @oninput="@LogStuff"></InputCheckbox> - <label> Enable log views</label> - <br/> - <InputCheckbox @bind-Value="@userSettings.DeveloperSettings.EnableConsoleLogging" @oninput="@LogStuff"></InputCheckbox> - <label> Enable console logging</label> - <br/> - <InputCheckbox @bind-Value="@userSettings.DeveloperSettings.EnablePortableDevtools" @oninput="@LogStuff"></InputCheckbox> - <label> Enable portable devtools</label> - <br/> -} -<br/> - -@code { - - private MRUStorageWrapper.Settings? userSettings { get; set; } - protected override async Task OnInitializedAsync() { - // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync<MRUStorageWrapper.Settings>("mru.settings"); - - await base.OnInitializedAsync(); - } - - private async Task LogStuff() { - await Task.Delay(100); - Console.WriteLine($"Settings: {userSettings.ToJson()}"); - await TieredStorage.DataStorageProvider.SaveObjectAsync("mru.settings", userSettings); - } - - private async Task ExportLocalStorage() { - var keys = await TieredStorage.DataStorageProvider.GetAllKeysAsync(); - var data = new Dictionary<string, object>(); - foreach (var key in keys) { - data.Add(key, await TieredStorage.DataStorageProvider.LoadObjectAsync<object>(key)); - } - var dataUri = "data:application/json;base64,"; - dataUri += Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data))); - await JSRuntime.InvokeVoidAsync("window.open", dataUri, "_blank"); - } - - private async Task ImportLocalStorage(InputFileChangeEventArgs obj) { - if (obj.FileCount != 1) return; - var data = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(obj.File.OpenReadStream()); - foreach (var (key, value) in data) { - await TieredStorage.DataStorageProvider.SaveObjectAsync(key, value); - } - NavigationManager.NavigateTo(NavigationManager.Uri, true, true); - } - -} diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor deleted file mode 100644
index 27fe35e..0000000 --- a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor +++ /dev/null
@@ -1,81 +0,0 @@ -@page "/Dev/Utilities" -@using System.Reflection -@using ArcaneLibs.Extensions -@using LibMatrix.Extensions -@using LibMatrix.Homeservers -@using MatrixRoomUtils.Abstractions -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Debug Tools</h3> -<hr/> -@if (Rooms.Count == 0) { - <p>You are not in any rooms!</p> - @* <p>Loading progress: @checkedRoomCount/@totalRoomCount</p> *@ -} -else { - <details> - <summary>Room List</summary> - @foreach (var room in Rooms) { - <a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@room.Replace('.', '~')"> - <RoomListItem RoomInfo="@(new RoomInfo() { Room = hs.GetRoom(room) })" LoadData="true"></RoomListItem> - </a> - } - </details> -} - -<details open> - <summary>Send GET request to URL</summary> - <div class="input-group"> - <input type="text" class="form-control" @bind-value="get_request_url" placeholder="URL"> - <button class="btn btn-outline-secondary" type="button" @onclick="SendGetRequest">Send</button> - </div> - <br/> - <pre>@get_request_result</pre> -</details> - -<div style="margin-bottom: 4em;"></div> -<LogView></LogView> - -@code { - public List<string> Rooms { get; set; } = new(); - public AuthenticatedHomeserverGeneric? hs { get; set; } - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs == null) return; - Rooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); - Console.WriteLine("Fetched joined rooms!"); - } - - //send req - string get_request_url { get; set; } = ""; - string get_request_result { get; set; } = ""; - - private async Task SendGetRequest() { - var field = typeof(RemoteHomeserver).GetRuntimeFields().First(x => x.ToString().Contains("<_httpClient>k__BackingField")); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs == null) return; - var httpClient = field.GetValue(hs) as MatrixHttpClient; - try { - var res = await httpClient.GetAsync(get_request_url); - if (res.IsSuccessStatusCode) { - if (res.Content.Headers.ContentType.MediaType == "application/json") - get_request_result = (await res.Content.ReadFromJsonAsync<object>()).ToJson(); - else - get_request_result = await res.Content.ReadAsStringAsync(); - StateHasChanged(); - return; - } - if (res.Content.Headers.ContentType.MediaType == "application/json") - get_request_result = $"Error: {res.StatusCode}\n" + (await res.Content.ReadFromJsonAsync<object>()).ToJson(); - else - get_request_result = $"Error: {res.StatusCode}\n" + await res.Content.ReadAsStringAsync(); - } - catch (Exception e) { - get_request_result = $"Error: {e}"; - } - StateHasChanged(); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Dev/ModalTest.razor b/MatrixRoomUtils.Web/Pages/Dev/ModalTest.razor deleted file mode 100644
index 4a0487f..0000000 --- a/MatrixRoomUtils.Web/Pages/Dev/ModalTest.razor +++ /dev/null
@@ -1,88 +0,0 @@ -@page "/Dev/ModalTest" -@inject IJSRuntime JsRuntime -<h3>ModalTest</h3> - -@foreach (var (key, value) in _windowInfos) { - @* <ModalWindow X="@value.X" Y="@value.Y" Title="@value.Title">@value.Content</ModalWindow> *@ -} -@for (var i = 0; i < 5; i++) { - var i1 = i; - <ModalWindow X="@Random.Shared.Next(1400)" Y="@Random.Shared.Next(1000)" Title="@("Window " + i1)" OnCloseClicked="() => OnCloseClicked(i1)"> - @for (var j = 0; j < i1; j++) { - <h1>@j</h1> - } - </ModalWindow> -} - -@code { - - private Dictionary<int, WindowInfo> _windowInfos = new(); - - private class WindowInfo { - public double X; - public double Y; - public string Title; - public RenderFragment Content; - } - - protected override async Task OnInitializedAsync() { - double _x = 2; - double _xv = 20; - double _y = 0; - double multiplier = 1; - - for (var i = 0; i < 200; i++) { - var i1 = i; - _windowInfos.Add(_windowInfos.Count, new WindowInfo { - X = _x, - Y = _y, - Title = "Win" + i1, - Content = builder => { - builder.OpenComponent<ModalWindow>(0); - builder.AddAttribute(1, "X", _x); - builder.AddAttribute(2, "Y", _y); - builder.AddAttribute(3, "Title", "Win" + i1); - builder.AddAttribute(4, "ChildContent", (RenderFragment)(builder2 => { - builder2.OpenElement(0, "h1"); - builder2.AddContent(1, "Hello " + i1); - builder2.CloseElement(); - })); - builder.CloseComponent(); - } - }); - //_x += _xv /= 1000/System.Math.Sqrt((double)_windowInfos.Count)*_windowInfos.Count.ToString().Length*multiplier; - _y += 20; - _x += 20; - var dimension = await JsRuntime.InvokeAsync<WindowDimension>("getWindowDimensions"); - if (_x > dimension.Width - 100) _x %= dimension.Width - 100; - if (_y > dimension.Height - 50) { - _y %= dimension.Height - 50; - _xv = 20; - } - if ( - (_windowInfos.Count < 10 && _windowInfos.Count % 2 == 0) || - (_windowInfos.Count < 100 && _windowInfos.Count % 10 == 0) || - (_windowInfos.Count < 1000 && _windowInfos.Count % 50 == 0) || - (_windowInfos.Count < 10000 && _windowInfos.Count % 100 == 0) - ) { - StateHasChanged(); - await Task.Delay(25); - } - if(_windowInfos.Count > 750) multiplier = 2; - if(_windowInfos.Count > 1500) multiplier = 3; - - } - - await base.OnInitializedAsync(); - } - - private void OnCloseClicked(int i1) { - Console.WriteLine("Close clicked on " + i1); - } - - public class WindowDimension { - public int Width { get; set; } - public int Height { get; set; } - } - -} diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor deleted file mode 100644
index d1a2df5..0000000 --- a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ /dev/null
@@ -1,34 +0,0 @@ -@page "/HSAdmin" -@using LibMatrix.Homeservers -@using ArcaneLibs.Extensions -<h3>Homeserver Admininistration</h3> -<hr/> - -@if (Homeserver is null) { - <p>Homeserver is null...</p> -} -else { - @if (Homeserver is AuthenticatedHomeserverSynapse) { - <h4>Synapse tools</h4> - <hr/> - <a href="/HSAdmin/RoomQuery">Query rooms</a> - } - else { - <p>Homeserver type @Homeserver.GetType().Name does not have any administration tools in MRU.</p> - <p>Server info:</p> - <pre>@ServerVersionResponse?.ToJson(ignoreNull: true)</pre> - } -} - -@code { - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - public ServerVersionResponse? ServerVersionResponse { get; set; } - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - ServerVersionResponse = await (Homeserver.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)); - await base.OnInitializedAsync(); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor deleted file mode 100644
index 7e4058b..0000000 --- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor +++ /dev/null
@@ -1,200 +0,0 @@ -@page "/HSAdmin/RoomQuery" -@using LibMatrix.Responses.Admin -@using LibMatrix.Filters -@using LibMatrix.Homeservers -@using ArcaneLibs.Extensions - -<h3>Homeserver Administration - Room Query</h3> - -<label>Search name: </label> -<InputText @bind-Value="SearchTerm"/><br/> -<label>Order by: </label> -<select @bind="OrderBy"> - @foreach (var item in validOrderBy) { - <option value="@item.Key">@item.Value</option> - } -</select><br/> -<label>Ascending: </label> -<InputCheckbox @bind-Value="Ascending"/><br/> -<details> - <summary> - <span>Local filtering (slow)</span> - - </summary> - <div style="margin-left: 8px; margin-bottom: 8px;"> - <u style="display: block;">String contains</u> - <span class="tile tile280">Room ID: <FancyTextBox @bind-Value="@Filter.RoomIdContains"></FancyTextBox></span> - <span class="tile tile280">Room name: <FancyTextBox @bind-Value="@Filter.NameContains"></FancyTextBox></span> - <span class="tile tile280">Canonical alias: <FancyTextBox @bind-Value="@Filter.CanonicalAliasContains"></FancyTextBox></span> - <span class="tile tile280">Creator: <FancyTextBox @bind-Value="@Filter.CreatorContains"></FancyTextBox></span> - <span class="tile tile280">Room version: <FancyTextBox @bind-Value="@Filter.VersionContains"></FancyTextBox></span> - <span class="tile tile280">Encryption algorithm: <FancyTextBox @bind-Value="@Filter.EncryptionContains"></FancyTextBox></span> - <span class="tile tile280">Join rules: <FancyTextBox @bind-Value="@Filter.JoinRulesContains"></FancyTextBox></span> - <span class="tile tile280">Guest access: <FancyTextBox @bind-Value="@Filter.GuestAccessContains"></FancyTextBox></span> - <span class="tile tile280">History visibility: <FancyTextBox @bind-Value="@Filter.HistoryVisibilityContains"></FancyTextBox></span> - - <u style="display: block;">Optional checks</u> - <span class="tile tile150"> - <InputCheckbox @bind-Value="@Filter.CheckFederation"></InputCheckbox> Is federated: - @if (Filter.CheckFederation) { - <InputCheckbox @bind-Value="@Filter.Federatable"></InputCheckbox> - } - </span> - <span class="tile tile150"> - <InputCheckbox @bind-Value="@Filter.CheckPublic"></InputCheckbox> Is public: - @if (Filter.CheckPublic) { - <InputCheckbox @bind-Value="@Filter.Public"></InputCheckbox> - } - </span> - - <u style="display: block;">Ranges</u> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsGreaterThan"></InputNumber><span class="range-sep">state events</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsLessThan"></InputNumber> - </span> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersGreaterThan"></InputNumber><span class="range-sep">members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersLessThan"></InputNumber> - </span> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersGreaterThan"></InputNumber><span class="range-sep">local members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersLessThan"></InputNumber> - </span> - </div> -</details> -<button class="btn btn-primary" @onclick="Search">Search</button> -<br/> - -@if (Results.Count > 0) { - <p>Found @Results.Count rooms</p> - <details> - <summary>TSV data (copy/paste)</summary> - <pre style="font-size: 0.6em;"> - <table> - @foreach (var res in Results) { - <tr> - <td style="padding: 8px;">@res.RoomId@("\t")</td> - <td style="padding: 8px;">@res.CanonicalAlias@("\t")</td> - <td style="padding: 8px;">@res.Creator@("\t")</td> - <td style="padding: 8px;">@res.Name</td> - </tr> - } - </table> - </pre> - </details> -} - -@foreach (var res in Results) { - <div style="background-color: #ffffff11; border-radius: 0.5em; display: block; margin-top: 4px; padding: 4px;"> - <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> - <p> - @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) { - <span>@res.CanonicalAlias (@res.RoomId)</span> - <br/> - } - else { - <span>@res.RoomId</span> - <br/> - } - @if (!string.IsNullOrWhiteSpace(res.Creator)) { - <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span> - <br/> - } - </p> - <span>@res.StateEvents state events</span><br/> - <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span> - <details> - <summary>Full result data</summary> - <pre>@res.ToJson(ignoreNull: true)</pre> - </details> - </div> -} - -<style> - .int-input { - width: 128px; - } - .tile { - display: inline-block; - padding: 4px; - border: 1px solid #ffffff22; - } - .tile280 { - min-width: 280px; - } - .tile150 { - min-width: 150px; - } - .range-sep { - display: inline-block; - padding: 4px; - width: 150px; - } - .range-sep::before { - content: "@("<") "; - } - .range-sep::after { - content: " @("<")"; - } - .center-children { - text-align: center; - } -</style> - -@code { - - [Parameter] - [SupplyParameterFromQuery(Name = "order_by")] - public string? OrderBy { get; set; } - - [Parameter] - [SupplyParameterFromQuery(Name = "name_search")] - public string SearchTerm { get; set; } - - [Parameter] - [SupplyParameterFromQuery(Name = "ascending")] - public bool Ascending { get; set; } - - public List<AdminRoomListingResult.AdminRoomListingResultRoom> Results { get; set; } = new(); - - private string Status { get; set; } - - public LocalRoomQueryFilter Filter { get; set; } = new(); - - protected override Task OnParametersSetAsync() { - if (Ascending == null) - Ascending = true; - OrderBy ??= "name"; - return Task.CompletedTask; - } - - private async Task Search() { - Results.Clear(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is AuthenticatedHomeserverSynapse synapse) { - var searchRooms = synapse.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator(); - while (await searchRooms.MoveNextAsync()) { - var room = searchRooms.Current; - Console.WriteLine("Hit: " + room.ToJson(false)); - Results.Add(room); - if (Results.Count % 10 == 0) - StateHasChanged(); - } - } - - } - - private readonly Dictionary<string, string> validOrderBy = new() { - { "name", "Room name" }, - { "canonical_alias", "Main alias address" }, - { "joined_members", "Number of members (reversed)" }, - { "joined_local_members", "Number of local members (reversed)" }, - { "version", "Room version" }, - { "creator", "Creator of the room" }, - { "encryption", "End-to-end encryption algorithm" }, - { "federatable", "Is room federated" }, - { "public", "Visibility in room list" }, - { "join_rules", "Join rules" }, - { "guest_access", "Guest access" }, - { "history_visibility", "Visibility of history" }, - { "state_events", "Number of state events" } - }; - -} diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css deleted file mode 100644
index e69de29..0000000 --- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css +++ /dev/null
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor deleted file mode 100644
index 250dc2d..0000000 --- a/MatrixRoomUtils.Web/Pages/Index.razor +++ /dev/null
@@ -1,191 +0,0 @@ -@page "/" -@inject ILogger<Index> logger -@using LibMatrix.Responses -@using LibMatrix -@using LibMatrix.Homeservers -@using ArcaneLibs.Extensions -@using MatrixRoomUtils.Web.Pages.Dev - -<PageTitle>Index</PageTitle> - -<h3>Rory&::MatrixUtils</h3> -Small collection of tools to do not-so-everyday things. - -<br/><br/> -<h5>Signed in accounts - <a href="/Login">Add new account</a></h5> -<hr/> -<form> - <table> - @foreach (var session in _sessions.OrderByDescending(x => x.UserInfo.RoomCount)) { - var _auth = session.UserAuth; - <tr class="user-entry"> - <td> - <img class="avatar" src="@session.UserInfo.AvatarUrl"/> - </td> - <td class="user-info"> - <p> - <input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(() => SwitchSession(_auth))" style="text-decoration-line: unset;"/> - <b>@session.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/> - </p> - <span style="display: inline-block; width: 128px;">@session.UserInfo.RoomCount rooms</span> - <a style="color: #888888" href="@("/ServerInfo/" + session.Homeserver.ServerName + "/")">@session.ServerVersion.Server.Name @session.ServerVersion.Server.Version</a> - @if (_auth.Proxy != null) { - <span class="badge badge-info"> (proxied via @_auth.Proxy)</span> - } - else { - <p>Not proxied</p> - } - @if (_debug) { - <p>T=@session.Homeserver.GetType().FullName</p> - <p>D=@session.Homeserver.WhoAmI.DeviceId</p> - <p>U=@session.Homeserver.WhoAmI.UserId</p> - } - </td> - <td> - <p> - <LinkButton OnClick="@(() => ManageUser(_auth))">Manage</LinkButton> - <LinkButton OnClick="@(() => RemoveUser(_auth))">Remove</LinkButton> - <LinkButton OnClick="@(() => RemoveUser(_auth, true))">Log out</LinkButton> - </p> - </td> - </tr> - } - </table> -</form> - -@if (_offlineSessions.Count > 0) { - <br/> - <br/> - <h5>Sessions on unreachable servers</h5> - <hr/> - <form> - <table> - @foreach (var session in _offlineSessions) { - <tr class="user-entry"> - <td> - <p> - @{ - string[] parts = session.UserId.Split(':'); - } - <span>@parts[0][1..]</span> on <span>@parts[1]</span> - @if (!string.IsNullOrWhiteSpace(session.Proxy)) { - <span class="badge badge-info"> (proxied via @session.Proxy)</span> - } - </p> - </td> - <td> - <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton> - </td> - </tr> - } - </table> - </form> -} - -@code -{ -#if DEBUG - private const bool _debug = true; -#else - private const bool _debug = false; -#endif - - private class AuthInfo { - public UserAuth UserAuth { get; set; } - public UserInfo UserInfo { get; set; } - public ServerVersionResponse ServerVersion { get; set; } - public AuthenticatedHomeserverGeneric Homeserver { get; set; } - } - - // private Dictionary<UserAuth, UserInfo> _users = new(); - private readonly List<AuthInfo> _sessions = []; - private readonly List<UserAuth> _offlineSessions = []; - private LoginResponse? _currentSession; - - protected override async Task OnInitializedAsync() { - Console.WriteLine("Index.OnInitializedAsync"); - _currentSession = await MRUStorage.GetCurrentToken(); - _sessions.Clear(); - _offlineSessions.Clear(); - var tokens = await MRUStorage.GetAllTokens(); - var profileTasks = tokens.Select(async token => { - UserInfo userInfo = new(); - AuthenticatedHomeserverGeneric hs; - Console.WriteLine($"Getting hs for {token.ToJson()}"); - try { - hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); - } - catch (MatrixException e) { - if (e.ErrorCode != "M_UNKNOWN_TOKEN") throw; - NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); - return; - - } - catch (Exception e) { - logger.LogError(e, $"Failed to instantiate AuthenticatedHomeserver for {token.ToJson()}, homeserver may be offline?", token.UserId); - _offlineSessions.Add(token); - return; - } - - Console.WriteLine($"Got hs for {token.ToJson()}"); - - var roomCountTask = hs.GetJoinedRooms(); - var profile = await hs.GetProfileAsync(hs.WhoAmI.UserId); - userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId; - Console.WriteLine(profile.ToJson()); - _sessions.Add(new() { - UserInfo = new() { - AvatarUrl = string.IsNullOrWhiteSpace(profile.AvatarUrl) ? "https://api.dicebear.com/6.x/identicon/svg?seed=" + hs.WhoAmI.UserId : hs.ResolveMediaUri(profile.AvatarUrl), - RoomCount = (await roomCountTask).Count, - DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId - }, - UserAuth = token, - ServerVersion = await (hs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)), - Homeserver = hs - }); - }); - Console.WriteLine("Waiting for profile tasks"); - await Task.WhenAll(profileTasks); - Console.WriteLine("Done waiting for profile tasks"); - await base.OnInitializedAsync(); - } - - private class UserInfo { - internal string AvatarUrl { get; set; } - internal string DisplayName { get; set; } - internal int RoomCount { get; set; } - } - - private async Task RemoveUser(UserAuth auth, bool logout = false) { - try { - if (logout) { - await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); - } - } - catch (Exception e) { - if (e is MatrixException { ErrorCode: "M_UNKNOWN_TOKEN" }) { - //todo: handle this - return; - } - - Console.WriteLine(e); - } - - await MRUStorage.RemoveToken(auth); - if ((await MRUStorage.GetCurrentToken())?.AccessToken == auth.AccessToken) - await MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens() ?? throw new InvalidOperationException()).FirstOrDefault()); - await OnInitializedAsync(); - } - - - private async Task SwitchSession(UserAuth auth) { - Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}"); - await MRUStorage.SetCurrentToken(auth); - await OnInitializedAsync(); - } - - private async Task ManageUser(UserAuth auth) { - await SwitchSession(auth); - NavigationManager.NavigateTo("/User/Profile"); - } -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Index.razor.css b/MatrixRoomUtils.Web/Pages/Index.razor.css deleted file mode 100644
index c6b7bd7..0000000 --- a/MatrixRoomUtils.Web/Pages/Index.razor.css +++ /dev/null
@@ -1,25 +0,0 @@ -.user-entry { - margin-bottom: 1em; -} - -.avatar { - width: 4em; - height: 4em; - border-radius: 50%; - margin-right: 0.5em; - vertical-align: middle; -} - -.user-entry > td { - margin-right: 0.5em; - vertical-align: middle; -} - -.user-info { - margin-bottom: 0.5em; - display: inline-block; - vertical-align: middle; -} -.user-info > p { - margin: 0; -} diff --git a/MatrixRoomUtils.Web/Pages/InvalidSession.razor b/MatrixRoomUtils.Web/Pages/InvalidSession.razor deleted file mode 100644
index 7d4769c..0000000 --- a/MatrixRoomUtils.Web/Pages/InvalidSession.razor +++ /dev/null
@@ -1,100 +0,0 @@ -@page "/InvalidSession" -@using LibMatrix - -<PageTitle>Invalid session</PageTitle> - -<h3>Rory&::MatrixUtils - Invalid session encountered</h3> -<p>A session was encountered that is no longer valid. This can happen if you have logged out of the account on another device, or if the access token has expired.</p> - -@if (_login is not null) { - <p>It appears that the affected user is @_login.UserId (@_login.DeviceId) on @_login.Homeserver!</p> - <LinkButton OnClick="@(OpenRefreshDialog)">Refresh token</LinkButton> - <LinkButton OnClick="@(RemoveUser)">Remove</LinkButton> - - @if (_showRefreshDialog) { - <ModalWindow MinWidth="300" X="275" Y="300" Title="@($"Password for {_login.UserId}")"> - <FancyTextBox IsPassword="true" @bind-Value="@_password"></FancyTextBox><br/> - <LinkButton OnClick="TryLogin">Log in</LinkButton> - @if (_loginException is not null) { - <pre style="color: red;">@_loginException.RawContent</pre> - } - </ModalWindow> - } -} -else { - <b>Something has gone wrong and the login was not passed along!</b> -} - -@code -{ - [Parameter] - [SupplyParameterFromQuery(Name = "ctx")] - public string Context { get; set; } - - private UserAuth? _login { get; set; } - - private bool _showRefreshDialog { get; set; } - - private string _password { get; set; } = ""; - - private MatrixException? _loginException { get; set; } - - protected override async Task OnInitializedAsync() { - var tokens = await MRUStorage.GetAllTokens(); - if (tokens is null || tokens.Count == 0) { - NavigationManager.NavigateTo("/Login"); - return; - } - - _login = tokens.FirstOrDefault(x => x.AccessToken == Context); - - if (_login is null) { - Console.WriteLine($"Could not find {_login} in stored tokens!"); - } - - await base.OnInitializedAsync(); - } - - private async Task RemoveUser() { - await MRUStorage.RemoveToken(_login!); - if ((await MRUStorage.GetCurrentToken())!.AccessToken == _login!.AccessToken) - await MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens())?.FirstOrDefault()); - await OnInitializedAsync(); - } - - private async Task OpenRefreshDialog() { - _showRefreshDialog = true; - StateHasChanged(); - await Task.CompletedTask; - } - - private async Task SwitchSession(UserAuth auth) { - Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}"); - await MRUStorage.SetCurrentToken(auth); - await OnInitializedAsync(); - } - - private async Task TryLogin() { - if(_login is null) throw new NullReferenceException("Login is null!"); - try { - var result = new UserAuth(await hsProvider.Login(_login.Homeserver, _login.UserId, _password)); - if (result is null) { - Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); - return; - } - Console.WriteLine($"Obtained access token for {result.UserId}!"); - - await RemoveUser(); - await MRUStorage.AddToken(result); - if (result.UserId == (await MRUStorage.GetCurrentToken())?.UserId) - await MRUStorage.SetCurrentToken(result); - NavigationManager.NavigateTo("/"); - } - catch (MatrixException e) { - Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); - Console.WriteLine(e); - _loginException = e; - StateHasChanged(); - } - } -} diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor deleted file mode 100644
index b6f244d..0000000 --- a/MatrixRoomUtils.Web/Pages/LoginPage.razor +++ /dev/null
@@ -1,160 +0,0 @@ -@page "/Login" -@using System.Text.Json -@using System.Text.Json.Serialization -@using LibMatrix -@inject ILocalStorageService LocalStorage -@inject IJSRuntime JsRuntime -<h3>Login</h3> -<hr/> - -<span style="display: block;"> - <label>User ID:</label> - <span>@@</span><!-- - --><FancyTextBox @bind-Value="@newRecordInput.Username"></FancyTextBox><!-- - --><span>:</span><!-- - --><FancyTextBox @bind-Value="@newRecordInput.Homeserver"></FancyTextBox> -</span> -<span style="display: block;"> - <label>Password:</label> - <FancyTextBox @bind-Value="@newRecordInput.Password" IsPassword="true"></FancyTextBox> -</span> -<span style="display: block"> - <label>Proxy (<a href="https://cgit.rory.gay/matrix/MxApiExtensions.git">MxApiExtensions</a> or similar):</label> - <FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox> -</span> -<br/> -<LinkButton OnClick="@AddRecord">Add account to queue</LinkButton> -<LinkButton OnClick="@(() => Login(newRecordInput))">Log in</LinkButton> -<br/> -<br/> - -<h4>Import from TSV</h4> -<hr/> -<span>Import credentials from a TSV (Tab Separated Values) file</span><br/> -<span>Columns: username, homeserver, password, proxy</span><br/> -<span>Keep in mind there is no column header!</span><br/> -<br/> -<InputFile OnChange="@FileChanged" accept=".tsv"></InputFile> -<br/> -<br/> - -<table border="1"> - <thead style="border-bottom: 1px solid white;"> - <th style="min-width: 150px; text-align: center; border-right: 1px solid white;">Username</th> - <th style="min-width: 150px; text-align: center; border-right: 1px solid white;">Homeserver</th> - <th style="min-width: 150px; text-align: center; border-right: 1px solid white;">Password</th> - <th style="min-width: 150px; text-align: center; border-right: 1px solid white;">Proxy</th> - <th style="min-width: 150px; text-align: center;">Actions</th> - </thead> - @foreach (var record in records) { - var r = record; - <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{r.Username}:{r.Homeserver}" && x.Proxy == r.Proxy) ? "green" : "unset")"> - <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.Username"></FancyTextBox> - </td> - <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.Homeserver"></FancyTextBox> - </td> - <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.Password" IsPassword="true"></FancyTextBox> - </td> - <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.Proxy"></FancyTextBox> - </td> - <td style="border-width: 1px;"> - <a role="button" @onclick="() => records.Remove(r)">Remove</a> - </td> - </tr> - @if (r.Exception is MatrixException me) { - <tr> - <td style="border-width: 1px;">Exception:</td> - <td style="border-width: 1px;">@me.ErrorCode</td> - <td style="border-width: 1px;" colspan="3">@me.Error</td> - </tr> - } - else if (r.Exception is { } e) { - <tr> - <td style="border-width: 1px;">Exception:</td> - <td style="border-width: 1px;" colspan="4">@e.Message</td> - </tr> - } - } -</table> -<br/> -<LinkButton OnClick="@LoginAll">Log in</LinkButton> - - -@code { - readonly List<LoginStruct> records = new(); - private LoginStruct newRecordInput = new(); - - List<UserAuth>? LoggedInSessions { get; set; } = new(); - - async Task LoginAll() { - var loginTasks = records.Select(Login); - await Task.WhenAll(loginTasks); - } - - async Task Login(LoginStruct record) { - if (!records.Contains(record)) - records.Add(record); - if (LoggedInSessions.Any(x => x.UserId == $"@{record.Username}:{record.Homeserver}" && x.Proxy == record.Proxy)) return; - StateHasChanged(); - try { - var result = new UserAuth(await hsProvider.Login(record.Homeserver, record.Username, record.Password, record.Proxy)) { - Proxy = record.Proxy - }; - if (result == null) { - Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); - return; - } - - Console.WriteLine($"Obtained access token for {result.UserId}!"); - - await MRUStorage.AddToken(result); - LoggedInSessions = await MRUStorage.GetAllTokens(); - } - catch (Exception e) { - Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); - Console.WriteLine(e); - record.Exception = e; - } - - StateHasChanged(); - } - - private async Task FileChanged(InputFileChangeEventArgs obj) { - LoggedInSessions = await MRUStorage.GetAllTokens(); - Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { - WriteIndented = true - })); - await using var rs = obj.File.OpenReadStream(); - using var sr = new StreamReader(rs); - var tsvData = await sr.ReadToEndAsync(); - records.Clear(); - foreach (var line in tsvData.Split('\n')) { - string?[] parts = line.Split('\t'); - if (parts.Length < 3) - continue; - string? via = parts.Length > 3 ? parts[3] : null; - records.Add(new() { Homeserver = parts[0], Username = parts[1], Password = parts[2], Proxy = via }); - } - } - - private async Task AddRecord() { - LoggedInSessions = await MRUStorage.GetAllTokens(); - records.Add(newRecordInput); - newRecordInput = new(); - } - - private class LoginStruct { - public string? Homeserver { get; set; } = ""; - public string? Username { get; set; } = ""; - public string? Password { get; set; } = ""; - public string? Proxy { get; set; } - - [JsonIgnore] - internal Exception? Exception { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor deleted file mode 100644
index 506a8a1..0000000 --- a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor +++ /dev/null
@@ -1,115 +0,0 @@ -@page "/UserRoomHistory/{UserId}" -@using LibMatrix.Homeservers -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.RoomTypes -@using ArcaneLibs.Extensions -@using MatrixRoomUtils.Abstractions -<h3>UserRoomHistory</h3> - -<span>Enter mxid: </span> -<FancyTextBox @bind-Value="@UserId"></FancyTextBox> - -@if (string.IsNullOrWhiteSpace(UserId)) { - <p>UserId is null!</p> -} -else { - <p>Checked @checkedRooms.Count so far...</p> - @if (currentHs is not null) { - <p>Checking rooms from @currentHs.UserId's perspective</p> - } - else if (checkedRooms.Count > 1) { - <p>Done!</p> - } - @foreach (var (state, rooms) in matchingStates) { - <u>@state</u> - <br/> - @foreach (var roomInfo in rooms) { - <RoomListItem RoomInfo="roomInfo" LoadData="true"></RoomListItem> - } - } -} - -@code { - private string? _userId; - - [Parameter] - public string? UserId { - get => _userId; - set { - _userId = value; - FindMember(value); - } - } - - private List<AuthenticatedHomeserverGeneric> hss = new(); - private AuthenticatedHomeserverGeneric? currentHs { get; set; } - - protected override async Task OnInitializedAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var sessions = await MRUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await MRUStorage.GetSession(userAuth); - if (session is not null) { - hss.Add(session); - StateHasChanged(); - } - } - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - if (!string.IsNullOrWhiteSpace(UserId)) FindMember(UserId); - } - - public Dictionary<string, List<RoomInfo>> matchingStates = new(); - public List<string> checkedRooms = new(); - private SemaphoreSlim _semaphoreSlim = new(1, 1); - - public async Task FindMember(string mxid) { - await _semaphoreSlim.WaitAsync(); - if (mxid != UserId) { - _semaphoreSlim.Release(); - return; //abort if changed - } - matchingStates.Clear(); - foreach (var homeserver in hss) { - currentHs = homeserver; - var rooms = await homeserver.GetJoinedRooms(); - rooms.RemoveAll(x => checkedRooms.Contains(x.RoomId)); - checkedRooms.AddRange(rooms.Select(x => x.RoomId)); - var tasks = rooms.Select(x => GetMembershipAsync(x, mxid)).ToAsyncEnumerable(); - await foreach (var (room, state) in tasks) { - if (state is null) continue; - if (!matchingStates.ContainsKey(state.Membership)) - matchingStates.Add(state.Membership, new()); - var roomInfo = new RoomInfo() { - Room = room - }; - matchingStates[state.Membership].Add(roomInfo); - roomInfo.StateEvents.Add(new() { - Type = RoomNameEventContent.EventId, - TypedContent = new RoomNameEventContent() { - Name = await room.GetNameOrFallbackAsync(4) - }, - RoomId = null, Sender = null, EventId = null //TODO implement - }); - StateHasChanged(); - if (mxid != UserId) { - _semaphoreSlim.Release(); - return; //abort if changed - } - } - StateHasChanged(); - } - currentHs = null; - StateHasChanged(); - _semaphoreSlim.Release(); - } - - public async Task<(GenericRoom roomId, RoomMemberEventContent? content)> GetMembershipAsync(GenericRoom room, string mxid) { - return (room, await room.GetStateOrNullAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, mxid)); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor deleted file mode 100644
index e477f4c..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor +++ /dev/null
@@ -1,338 +0,0 @@ -@page "/Rooms/Create" -@using System.Text.Json -@using System.Reflection -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.EventTypes.Spec.State.RoomInfo -@using LibMatrix.Homeservers -@using LibMatrix.Responses -@using MatrixRoomUtils.Web.Classes.RoomCreationTemplates -@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@ - -<h3>Room Manager - Create Room</h3> - -@* <pre Contenteditable="true" @onkeypress="@JsonChanged" content="JsonString">@JsonString</pre> *@ -<style> - table.table-top-first-tr tr td:first-child { - vertical-align: top; - } - </style> -<table class="table-top-first-tr"> - <tr style="padding-bottom: 16px;"> - <td>Preset:</td> - <td> - @if (Presets is null) { - <p style="color: red;">Presets is null!</p> - } - else { - <InputSelect @bind-Value="@RoomPreset"> - @foreach (var createRoomRequest in Presets) { - <option value="@createRoomRequest.Key">@createRoomRequest.Key</option> - } - </InputSelect> - } - </td> - </tr> - @if (creationEvent is not null) { - <tr> - <td>Room name:</td> - <td> - @if (creationEvent.Name is null) { - <p style="color: red;">creationEvent.Name is null!</p> - } - else { - <FancyTextBox @bind-Value="@creationEvent.Name"></FancyTextBox> - <p>(#<FancyTextBox @bind-Value="@creationEvent.RoomAliasName"></FancyTextBox>:@Homeserver.WhoAmI.UserId.Split(':').Last())</p> - } - </td> - </tr> - <tr> - <td>Room type:</td> - <td> - @if (creationEvent.CreationContentBaseType is null) { - <p style="color: red;">creationEvent._creationContentBaseType is null!</p> - } - else { - <InputSelect @bind-Value="@creationEvent.CreationContentBaseType.Type"> - <option value="">Room</option> - <option value="m.space">Space</option> - </InputSelect> - <FancyTextBox @bind-Value="@creationEvent.CreationContentBaseType.Type"></FancyTextBox> - } - </td> - </tr> - <tr> - <td style="padding-top: 16px;">History visibility:</td> - <td style="padding-top: 16px;"> - <InputSelect @bind-Value="@historyVisibility.HistoryVisibility"> - <option value="invited">Invited</option> - <option value="joined">Joined</option> - <option value="shared">Shared</option> - <option value="world_readable">World readable</option> - </InputSelect> - </td> - </tr> - <tr> - <td>Guest access:</td> - <td> - <ToggleSlider @bind-Value="guestAccessEvent.IsGuestAccessEnabled"> - @(guestAccessEvent.IsGuestAccessEnabled ? "Guests can join" : "Guests cannot join") (@guestAccessEvent.GuestAccess) - </ToggleSlider> - <InputSelect @bind-Value="@guestAccessEvent.GuestAccess"> - <option value="can_join">Can join</option> - <option value="forbidden">Forbidden</option> - </InputSelect> - </td> - </tr> - - <tr> - <td>Room icon:</td> - <td> - <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> - <div style="display: inline-block; vertical-align: middle;"> - <FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/> - <InputFile OnChange="RoomIconFilePicked"></InputFile> - </div> - </td> - </tr> - <tr> - <td>Permissions:</td> - <details> - <summary>@creationEvent.PowerLevelContentOverride.Users.Count members</summary> - @foreach (var user in creationEvent.PowerLevelContentOverride.Events.Keys) { - var _event = user; - <tr> - <td> - <FancyTextBox Formatter="@GetPermissionFriendlyName" - Value="@_event" - ValueChanged="val => { creationEvent.PowerLevelContentOverride.Events.ChangeKey(_event, val); }"> - </FancyTextBox>: - </td> - <td> - <input type="number" value="@creationEvent.PowerLevelContentOverride.Events[_event]" @oninput="val => { creationEvent.PowerLevelContentOverride.Events[_event] = int.Parse(val.Value.ToString()); }" @onfocusout="() => { creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"/> - </td> - </tr> - } - @foreach (var user in creationEvent.PowerLevelContentOverride.Users.Keys) { - var _user = user; - <tr> - <td><FancyTextBox Value="@_user" ValueChanged="val => { creationEvent.PowerLevelContentOverride.Users.ChangeKey(_user, val); creationEvent.PowerLevelContentOverride.Users = creationEvent.PowerLevelContentOverride.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"></FancyTextBox>:</td> - <td> - <input type="number" value="@creationEvent.PowerLevelContentOverride.Users[_user]" @oninput="val => { creationEvent.PowerLevelContentOverride.Users[_user] = int.Parse(val.Value.ToString()); }"/> - </td> - </tr> - } - </details> - </tr> - <tr> - <td>Server ACLs:</td> - <td> - @if (serverAcl?.Allow is null) { - <p>No allow rules exist!</p> - <button @onclick="@(() => { serverAcl.Allow = new List<string> { "*" }; })">Create sane defaults</button> - } - else { - <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Allow.Count) allow rules</summary> - @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ - </details> - } - @if (serverAcl?.Deny is null) { - <p>No deny rules exist!</p> - <button @onclick="@(() => { serverAcl.Allow = new List<string>(); })">Create sane defaults</button> - } - else { - <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Deny.Count) deny rules</summary> - @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ - </details> - } - </td> - </tr> - - <tr> - <td>Invited members:</td> - <td> - <details> - <summary>@creationEvent.InitialState.Count(x => x.Type == "m.room.member") members</summary> - @* <button @onclick="() => { RuntimeCache.LoginSessions.Select(x => x.Value.LoginResponse.UserId).ToList().ForEach(InviteMember); }">Invite all logged in accounts</button> *@ - @foreach (var member in creationEvent.InitialState.Where(x => x.Type == "m.room.member" && x.StateKey != Homeserver.UserId)) { - <UserListItem UserId="@member.StateKey"></UserListItem> - } - </details> - </td> - </tr> - @* Initial states, should remain at bottom *@ - <tr> - <td style="vertical-align: top;">Initial states:</td> - <td> - <details> - - @code - { - private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" }; - } - - <summary> @creationEvent.InitialState.Count(x => !ImplementedStates.Contains(x.Type)) custom states</summary> - <table> - @foreach (var initialState in creationEvent.InitialState.Where(x => !ImplementedStates.Contains(x.Type))) { - <tr> - <td style="vertical-align: top;"> - @(initialState.Type): - @if (!string.IsNullOrEmpty(initialState.StateKey)) { - <br/> - <span>(@initialState.StateKey)</span> - } - - </td> - <td> - <pre>@JsonSerializer.Serialize(initialState.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre> - </td> - </tr> - } - </table> - </details> - <details> - <summary> @creationEvent.InitialState.Count initial states</summary> - <table> - @foreach (var initialState in creationEvent.InitialState) { - var _state = initialState; - <tr> - <td style="vertical-align: top;"> - <span>@(_state.Type):</span><br/> - <button @onclick="() => { creationEvent.InitialState.Remove(_state); StateHasChanged(); }">Remove</button> - </td> - - <td> - <pre>@JsonSerializer.Serialize(_state.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre> - </td> - </tr> - } - </table> - </details> - </td> - </tr> - } -</table> -<button @onclick="CreateRoom">Create room</button> -<br/> -<ModalWindow Title="Creation JSON"> - <pre> - @creationEvent.ToJson(ignoreNull: true) - </pre> -</ModalWindow> -<ModalWindow Title="Creation JSON (with null values)"> - <pre> - @creationEvent.ToJson() - </pre> -</ModalWindow> - -@if (_matrixException is not null) { - <ModalWindow Title="@("Matrix exception: " + _matrixException.ErrorCode)"> - <pre> - @_matrixException.Message - </pre> - </ModalWindow> -} - -@code { - - private string RoomPreset { - get => Presets.ContainsValue(creationEvent) ? Presets.First(x => x.Value == creationEvent).Key : "Not a preset"; - set { - creationEvent = Presets[value]; - JsonChanged(); - StateHasChanged(); - } - } - - private CreateRoomRequest? creationEvent { get; set; } - - private Dictionary<string, CreateRoomRequest>? Presets { get; set; } = new(); - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - private MatrixException? _matrixException { get; set; } - - private RoomHistoryVisibilityEventContent? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as RoomHistoryVisibilityEventContent; - private RoomGuestAccessEventContent? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as RoomGuestAccessEventContent; - private RoomServerACLEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerACLEventContent; - private RoomAvatarEventContent? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventContent; - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - - foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) { - Console.WriteLine($"Found room creation template in class: {x.FullName}"); - var instance = (IRoomCreationTemplate)Activator.CreateInstance(x); - Presets[instance.Name] = instance.CreateRoomRequest; - } - Presets = Presets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); - - if (!Presets.ContainsKey("Default")) { - Console.WriteLine($"No default room found in {Presets.Count} presets: {string.Join(", ", Presets.Keys)}"); - } - else RoomPreset = "Default"; - - await base.OnInitializedAsync(); - } - - private void JsonChanged() => Console.WriteLine(creationEvent.ToJson()); - - private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) { - var res = await Homeserver.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType); - Console.WriteLine(res); - (creationEvent["m.room.avatar"].TypedContent as RoomAvatarEventContent).Url = res; - StateHasChanged(); - } - - private async Task CreateRoom() { - Console.WriteLine("Create room"); - Console.WriteLine(creationEvent.ToJson()); - creationEvent.CreationContent.Add("rory.gay.created_using", "Rory&::MatrixRoomUtils (https://mru.rory.gay)"); - try { - var id = await Homeserver.CreateRoom(creationEvent); - } - catch (MatrixException e) { - _matrixException = e; - } - } - - private void InviteMember(string mxid) { - if (!creationEvent.InitialState.Any(x => x.Type == "m.room.member" && x.StateKey == mxid) && Homeserver.UserId != mxid) - creationEvent.InitialState.Add(new StateEvent { - Type = "m.room.member", - StateKey = mxid, - TypedContent = new RoomMemberEventContent { - Membership = "invite", - Reason = "Automatically invited at room creation time." - } - }); - } - - private string GetStateFriendlyName(string key) => key switch { - "m.room.history_visibility" => "History visibility", - "m.room.guest_access" => "Guest access", - "m.room.join_rules" => "Join rules", - "m.room.server_acl" => "Server ACL", - "m.room.avatar" => "Avatar", - _ => key - }; - - private string GetPermissionFriendlyName(string key) => key switch { - "m.reaction" => "Send reaction", - "m.room.avatar" => "Change room icon", - "m.room.canonical_alias" => "Change room alias", - "m.room.encryption" => "Enable encryption", - "m.room.history_visibility" => "Change history visibility", - "m.room.name" => "Change room name", - "m.room.power_levels" => "Change power levels", - "m.room.tombstone" => "Upgrade room", - "m.room.topic" => "Change room topic", - "m.room.pinned_events" => "Pin events", - "m.room.server_acl" => "Change server ACLs", - _ => key - }; - -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor deleted file mode 100644
index 6cabe82..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ /dev/null
@@ -1,250 +0,0 @@ -@page "/Rooms" -@using LibMatrix.Filters -@using LibMatrix.Helpers -@using LibMatrix.Extensions -@using LibMatrix.Responses -@using System.Collections.ObjectModel -@using System.Diagnostics -@using ArcaneLibs.Extensions -@using MatrixRoomUtils.Abstractions -@inject ILogger<Index> logger -<h3>Room list</h3> - -<p>@Status</p> -<p>@Status2</p> - -<LinkButton href="/Rooms/Create">Create new room</LinkButton> - -<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList> - -@code { - private ObservableCollection<RoomInfo> Rooms { get; } = new(); - private UserProfileResponse GlobalProfile { get; set; } - - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - private static SyncFilter filter = new() { - AccountData = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Presence = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Room = new SyncFilter.RoomFilter { - AccountData = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Ephemeral = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - State = new SyncFilter.RoomFilter.StateFilter { - Types = new List<string> { - "m.room.create", - "m.room.name", - "m.room.avatar", - "org.matrix.mjolnir.shortcode", - "m.room.power_levels", - } - }, - Timeline = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - } - } - }; - - // private static SyncFilter profileUpdateFilter = new() { - // AccountData = new SyncFilter.EventFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Presence = new SyncFilter.EventFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Room = new SyncFilter.RoomFilter { - // AccountData = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Ephemeral = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // State = new SyncFilter.RoomFilter.StateFilter { - // Types = new List<string> { - // "m.room.member" - // }, - // Senders = new() - // }, - // Timeline = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // } - // } - // }; - - private SyncHelper syncHelper; - - // SyncHelper profileSyncHelper; - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - var rooms = await Homeserver.GetJoinedRooms(); - // SemaphoreSlim _semaphore = new(160, 160); - - var roomTasks = rooms.Select(async room => { - RoomInfo ri; - // await _semaphore.WaitAsync(); - ri = new() { Room = room }; - await Task.WhenAll((filter.Room?.State?.Types ?? []).Select(x => ri.GetStateEvent(x))); - return ri; - }).ToAsyncEnumerable(); - - await foreach (var room in roomTasks) { - Rooms.Add(room); - StateHasChanged(); - // await Task.Delay(50); - // _semaphore.Release(); - } - - if (rooms.Count >= 150) RenderContents = true; - - GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); - syncHelper = new SyncHelper(Homeserver, logger) { - Timeout = 30000, - Filter = filter, - MinimumDelay = TimeSpan.FromMilliseconds(5000) - }; - // profileSyncHelper = new SyncHelper(Homeserver, logger) { - // Timeout = 10000, - // Filter = profileUpdateFilter, - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - // }; - // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); - - RunSyncLoop(syncHelper); - // RunSyncLoop(profileSyncHelper); - RunQueueProcessor(); - - await base.OnInitializedAsync(); - } - - private async Task RunQueueProcessor() { - var renderTimeSw = Stopwatch.StartNew(); - var isInitialSync = true; - while (true) { - try { - while (queue.Count == 0) { - Console.WriteLine("Queue is empty, waiting..."); - await Task.Delay(isInitialSync ? 100 : 2500); - } - - Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!"); - - int maxUpdates = 10; - isInitialSync = false; - while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { - var (roomId, roomData) = queueEntry; - Console.WriteLine($"Dequeued room {roomId}"); - RoomInfo room; - - if (Rooms.Any(x => x.Room.RoomId == roomId)) { - room = Rooms.First(x => x.Room.RoomId == roomId); - Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events"); - } - else { - Console.WriteLine($"QueueWorker: encountered new room {roomId}!"); - room = new RoomInfo() { - Room = Homeserver.GetRoom(roomId) - }; - Rooms.Add(room); - } - - if (room.StateEvents is null) { - Console.WriteLine($"QueueWorker: {roomId} does not have state events on record?"); - throw new InvalidDataException("Somehow this is null???"); - } - - if (roomData.State?.Events is { Count: > 0 }) - room.StateEvents.MergeStateEventLists(roomData.State.Events); - else { - Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!"); - } - } - Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; - - RenderContents |= queue.Count == 0; - await Task.Delay(Rooms.Count); - } - catch (Exception e) { - Console.WriteLine("QueueWorker exception: " + e); - } - } - } - - private bool RenderContents { get; set; } = false; - - 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 Queue<KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>> queue = new(); - - private async Task RunSyncLoop(SyncHelper syncHelper) { - Status = "Initial syncing..."; - Console.WriteLine("starting sync"); - - var syncs = syncHelper.EnumerateSyncAsync(); - await foreach (var sync in syncs) { - Console.WriteLine("trying sync"); - if (sync is null) continue; - - Status = $"Got sync with {sync.Rooms?.Join?.Count ?? 0} room updates, next batch: {sync.NextBatch}!"; - if (sync?.Rooms?.Join != null) - foreach (var joinedRoom in sync.Rooms.Join) - if ( /*joinedRoom.Value.AccountData?.Events?.Count > 0 ||*/ joinedRoom.Value.State?.Events?.Count > 0) { - joinedRoom.Value.State.Events.RemoveAll(x => x.Type == "m.room.member" && x.StateKey != Homeserver.WhoAmI?.UserId); - // We can't trust servers to give us what we ask for, and this ruins performance - // Thanks, Conduit. - joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.Types?.Contains(x.Type) == false); - if (filter.Room?.State?.NotSenders?.Any() ?? false) - joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.NotSenders?.Contains(x.Sender) ?? false); - - queue.Enqueue(joinedRoom); - } - if (sync.Rooms.Leave is {Count: > 0}) - foreach (var leftRoom in sync.Rooms.Leave) - if (Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) - Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key)); - - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue... " + - $"{sync?.Rooms?.Join?.Count ?? 0} new updates!"; - - Status2 = $"Next batch: {sync.NextBatch}"; - } - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor deleted file mode 100644
index b89d59c..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor +++ /dev/null
@@ -1,267 +0,0 @@ -@page "/Rooms/{RoomId}/Policies" -@using LibMatrix -@using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.EventTypes.Spec.State.Policy -@using System.Diagnostics -@using LibMatrix.RoomTypes -@using System.Collections.Frozen -@using System.Reflection -@using ArcaneLibs.Attributes -@using LibMatrix.EventTypes - -@using MatrixRoomUtils.Web.Shared.PolicyEditorComponents - -<h3>Policy list editor - Editing @RoomId</h3> -<hr/> -@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@ -<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton> - -@if (Loading) { - <p>Loading...</p> -} -else if (PolicyEventsByType is not { Count: > 0 }) { - <p>No policies yet</p> -} -else { - @foreach (var (type, value) in PolicyEventsByType) { - <p> - @(GetValidPolicyEventsByType(type).Count) active, - @(GetInvalidPolicyEventsByType(type).Count) invalid - (@value.Count total) - @(GetPolicyTypeName(type).ToLower()) - </p> - } - - @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { - <details> - <summary> - <span> - @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") - </span> - <hr style="margin: revert;"/> - </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> - @{ - var policies = GetValidPolicyEventsByType(type); - var invalidPolicies = GetInvalidPolicyEventsByType(type); - // enumerate all properties with friendly name - var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) - .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null) - .ToFrozenSet(); - var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); - } - <thead> - <tr> - @foreach (var name in propNames) { - <th style="border-width: 1px">@name</th> - } - <th style="border-width: 1px">Actions</th> - </tr> - </thead> - <tbody style="border-width: 1px;"> - @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { - <tr> - @{ - var typedContent = policy.TypedContent!; - var proxySafeProps = typedContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => props.Any(y => y.Name == x.Name)) - .ToFrozenSet(); - Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); - } - @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) { - <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td> - } - <td> - <div style="display: ruby;"> - @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { - <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton> - <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton> - @if (policy.IsLegacyType) { - <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton> - } - } - </div> - </td> - </tr> - } - </tbody> - </table> - <details> - <summary> - <u> - @("Invalid " + GetPolicyTypeName(type).ToLower()) - </u> - </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> - <thead> - <tr> - <th style="border-width: 1px">State key</th> - <th style="border-width: 1px">Json contents</th> - </tr> - </thead> - <tbody> - @foreach (var policy in invalidPolicies) { - <tr> - <td>@policy.StateKey</td> - <td> - <pre>@policy.RawContent.ToJson(true, false)</pre> - </td> - </tr> - } - </tbody> - </table> - </details> - </details> - } -} - -@if (CurrentlyEditingEvent is not null) { - <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal> -} - -@code { - -#if DEBUG - private const bool Debug = true; -#else - private const bool Debug = false; -#endif - - private bool Loading { get; set; } = true; - //get room list - // - sync withroom list filter - // Type = support.feline.msc3784 - //support.feline.policy.lists.msc.v1 - - [Parameter] - public string RoomId { get; set; } = null!; - - private bool _enableAvatars; - private StateEventResponse? _currentlyEditingEvent; - - // static readonly Dictionary<string, string?> Avatars = new(); - // static readonly Dictionary<string, RemoteHomeserver> Servers = new(); - - // private static List<StateEventResponse> PolicyEvents { get; set; } = new(); - private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); - - private StateEventResponse? CurrentlyEditingEvent { - get => _currentlyEditingEvent; - set { - _currentlyEditingEvent = value; - StateHasChanged(); - } - } - - // public bool EnableAvatars { - // get => _enableAvatars; - // set { - // _enableAvatars = value; - // if (value) GetAllAvatars(); - // } - // } - - private AuthenticatedHomeserverGeneric Homeserver { get; set; } - private GenericRoom Room { get; set; } - private RoomPowerLevelEventContent PowerLevels { get; set; } - - protected override async Task OnInitializedAsync() { - var sw = Stopwatch.StartNew(); - await base.OnInitializedAsync(); - Homeserver = (await MRUStorage.GetCurrentSessionOrNavigate())!; - if (Homeserver is null) return; - Room = Homeserver.GetRoom(RoomId!); - PowerLevels = (await Room.GetPowerLevelsAsync())!; - await LoadStatesAsync(); - Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); - } - - private async Task LoadStatesAsync() { - Loading = true; - var states = Room.GetFullStateAsync(); - PolicyEventsByType.Clear(); - await foreach (var state in states) { - if (state is null) continue; - if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; - if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new()); - PolicyEventsByType[state.MappedType].Add(state); - } - - Loading = false; - StateHasChanged(); - } - - // private async Task GetAllAvatars() { - // // if (!_enableAvatars) return; - // Console.WriteLine("Getting avatars..."); - // var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue<string>()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); - // Console.WriteLine($"Got {users.Count} users!"); - // var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); - // Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); - // var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); - // await foreach (var server in homeserverTasks) { - // if (server is null) continue; - // var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); - // await Task.WhenAll(profileTasks); - // profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); - // foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { - // // if (profile is null) continue; - // if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { - // var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); - // Avatars.TryAdd(profile.Key, url); - // } - // else Avatars.TryAdd(profile.Key, null); - // } - // - // StateHasChanged(); - // } - // } - // - // private async Task<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { - // try { - // return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid)); - // } - // catch { - // return null; - // } - // } - - private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; - - private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); - - private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); - - private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() - ?? type.GetCustomAttributes<MatrixEventAttribute>() - .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.EventName))?.EventName; - - private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; - - private async Task RemovePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { }); - PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); - await LoadStatesAsync(); - } - - private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent); - await LoadStatesAsync(); - } - - private async Task UpgradePolicyAsync(StateEventResponse policyEvent) { - policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type; - await LoadStatesAsync(); - } - - private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); - - // event types, unnamed - private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes - .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor deleted file mode 100644
index ce94d01..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor +++ /dev/null
@@ -1,100 +0,0 @@ -@page "/Rooms/{RoomId}/Space" -@using LibMatrix.RoomTypes -@using ArcaneLibs.Extensions -@using LibMatrix -<h3>Room manager - Viewing Space</h3> - -<button onclick="@JoinAllRooms">Join all rooms</button> -@foreach (var room in Rooms) { - <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> - <pre>@stateEvent.RawContent.ToJson()</pre> - } -</details> - -@code { - - [Parameter] - public string RoomId { get; set; } = "invalid!!!!!!"; - - private GenericRoom? Room { get; set; } - - private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>(); - private List<GenericRoom> Rooms { get; } = new(); - private List<string> ServersInSpace { get; } = new(); - - protected override async Task OnInitializedAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - Room = hs.GetRoom(RoomId.Replace('~', '.')); - - var state = Room.GetFullStateAsync(); - await foreach (var stateEvent in state) { - switch (stateEvent.Type) { - case "m.space.child": { - var roomId = stateEvent.StateKey; - var room = hs.GetRoom(roomId); - if (room is not null) { - Rooms.Add(room); - } - break; - } - case "m.room.member": { - var serverName = stateEvent.StateKey.Split(':').Last(); - if (!ServersInSpace.Contains(serverName)) { - ServersInSpace.Add(serverName); - } - break; - } - } - } - await base.OnInitializedAsync(); - - // 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!"); - // } - - // await base.OnInitializedAsync(); - } - - private async Task JoinAllRooms() { - List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList(); - await Task.WhenAll(tasks); - } - -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor deleted file mode 100644
index 0e9d4f6..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor +++ /dev/null
@@ -1,144 +0,0 @@ -@page "/Rooms/{RoomId}/State/Edit" -@using ArcaneLibs.Extensions -@using LibMatrix -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Room state editor - Editing @RoomId</h3> -<hr/> - -<p>@status</p> - -<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events -<br/> -<InputSelect @bind-Value="shownStateKey"> - <option value="">-- State key --</option> - @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey != "").Select(x => x.StateKey).Distinct().OrderBy(x => x)) { - <option value="@stateEvent">@stateEvent</option> - Console.WriteLine(stateEvent); - } -</InputSelect> -<br/> -<InputSelect @bind-Value="shownType"> - <option value="">-- Type --</option> - @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey != shownStateKey).Select(x => x.Type).Distinct().OrderBy(x => x)) { - <option value="@stateEvent">@stateEvent</option> - } -</InputSelect> -<br/> - -<textarea @bind="shownEventJson" style="width: 100%; height: fit-Content;"></textarea> - -<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<StateEventResponse> FilteredEvents { get; set; } = new(); - public List<StateEventResponse> Events { get; set; } = new(); - public string status = ""; - - 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 DateTime _lastUpdate = DateTime.Now; - - private async Task LoadStatesAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - - var StateLoaded = 0; - var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); - await foreach (var _ev in response) { - // var e = new StateEventResponse { - // Type = _ev.Type, - // StateKey = _ev.StateKey, - // OriginServerTs = _ev.OriginServerTs, - // Content = _ev.Content - // }; - Events.Add(_ev); - if (string.IsNullOrEmpty(_ev.StateKey)) { - FilteredEvents.Add(_ev); - } - StateLoaded++; - - if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue; - _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; - - if (_shownType is not null) - shownEventJson = _FilteredEvents.First(x => x.Type == _shownType).RawContent.ToJson(indent: true, ignoreNull: true); - - 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 EventId { get; set; } - // public string UserId { get; set; } - // public string ReplacesState { get; set; } - } - - public bool ShowMembershipEvents { - get => _showMembershipEvents; - set { - _showMembershipEvents = value; - RebuildFilteredData(); - } - } - - private bool _showMembershipEvents; - private string _shownStateKey; - private string _shownType; - - private string shownStateKey { - get => _shownStateKey; - set { - _shownStateKey = value; - RebuildFilteredData(); - } - } - - private string shownType { - get => _shownType; - set { - _shownType = value; - RebuildFilteredData(); - } - } - - private string shownEventJson { get; set; } -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor deleted file mode 100644
index 2d0e0b0..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor +++ /dev/null
@@ -1,127 +0,0 @@ -@page "/Rooms/{RoomId}/State/View" -@using ArcaneLibs.Extensions -@using LibMatrix -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Room state viewer - Viewing @RoomId</h3> -<hr/> - -<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.StateKey == "").OrderBy(x => x.OriginServerTs)) { - <tr> - <td>@stateEvent.Type</td> - <td style="max-width: fit-Content;"> - <pre>@stateEvent.RawContent.ToJson()</pre> - </td> - </tr> - } - </tbody> -</table> - -@foreach (var group in FilteredEvents.GroupBy(x => x.StateKey).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.OriginServerTs)) { - <tr> - <td>@stateEvent.Type</td> - <td style="max-width: fit-Content;"> - <pre>@stateEvent.RawContent.ToJson()</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<StateEventResponse> FilteredEvents { get; set; } = new(); - public List<StateEventResponse> Events { get; set; } = new(); - public string status = ""; - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - await LoadStatesAsync(); - Console.WriteLine("Policy list editor initialized!"); - } - - private DateTime _lastUpdate = DateTime.Now; - - private async Task LoadStatesAsync() { - var StateLoaded = 0; - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); - await foreach (var _ev in response) { - Events.Add(_ev); - if (string.IsNullOrEmpty(_ev.StateKey)) { - FilteredEvents.Add(_ev); - } - StateLoaded++; - - if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue; - _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 bool ShowMembershipEvents { - get => _showMembershipEvents; - set { - _showMembershipEvents = value; - RebuildFilteredData(); - } - } - - private bool _showMembershipEvents; -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor deleted file mode 100644
index 01bf555..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor +++ /dev/null
@@ -1,60 +0,0 @@ -@page "/Rooms/{RoomId}/Timeline" -@using MatrixRoomUtils.Web.Shared.TimelineComponents -@using LibMatrix -@using LibMatrix.EventTypes.Spec -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Homeservers -<h3>RoomManagerTimeline</h3> -<hr/> -<p>Loaded @Events.Count events...</p> - -@foreach (var evt in Events) { - <div type="@evt.Type" key="@evt.StateKey" itemid="@evt.EventId"> - <DynamicComponent Type="@ComponentType(evt)" - Parameters="@(new Dictionary<string, object> { { "Event", evt }, { "Events", Events }, { "Homeserver", Homeserver!} })"> - </DynamicComponent> - </div> -} - -@code { - - [Parameter] - public string RoomId { get; set; } - - private List<MessagesResponse> Messages { get; } = new(); - private List<StateEventResponse> Events { get; } = new(); - - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - protected override async Task OnInitializedAsync() { - Console.WriteLine("RoomId: " + RoomId); - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - var room = Homeserver.GetRoom(RoomId); - MessagesResponse? msgs = null; - do { - msgs = await room.GetMessagesAsync(limit: 1000, from: msgs?.End, dir: "b"); - Messages.Add(msgs); - Console.WriteLine($"Got {msgs.Chunk.Count} messages"); - msgs.Chunk.Reverse(); - Events.InsertRange(0, msgs.Chunk); - } while (msgs.End is not null); - - - await base.OnInitializedAsync(); - } - - private StateEventResponse GetProfileEventBefore(StateEventResponse Event) => Events.TakeWhile(x => x != Event).Last(e => e.Type == "m.room.member" && e.StateKey == Event.Sender); - - private Type ComponentType(StateEvent Event) => Event.TypedContent switch { - RoomCanonicalAliasEventContent => typeof(TimelineCanonicalAliasItem), - RoomHistoryVisibilityEventContent => typeof(TimelineHistoryVisibilityItem), - RoomTopicEventContent => typeof(TimelineRoomTopicItem), - RoomMemberEventContent => typeof(TimelineMemberItem), - RoomMessageEventContent => typeof(TimelineMessageItem), - RoomCreateEventContent => typeof(TimelineRoomCreateItem), - RoomNameEventContent => typeof(TimelineRoomNameItem), - _ => typeof(TimelineUnknownItem) - }; - -} diff --git a/MatrixRoomUtils.Web/Pages/ServerInfo.razor b/MatrixRoomUtils.Web/Pages/ServerInfo.razor deleted file mode 100644
index 71a1980..0000000 --- a/MatrixRoomUtils.Web/Pages/ServerInfo.razor +++ /dev/null
@@ -1,235 +0,0 @@ -@page "/ServerInfo/{Homeserver}" -@using LibMatrix.Homeservers -@using LibMatrix.Responses -@using ArcaneLibs.Extensions -<h3>ServerInfo</h3> -<hr/> -@if (ServerVersionResponse is not null) { - <p>Server version: @ServerVersionResponse.Server.Name @ServerVersionResponse.Server.Version</p> - <pre>@ServerVersionResponse?.ToJson(ignoreNull: true)</pre> - <br/> -} -@if (ClientVersionsResponse is not null) { - <p>Client versions:</p> - <details> - <summary>JSON data</summary> - <pre>@ClientVersionsResponse?.ToJson(ignoreNull: true)</pre> - </details> - <u>Spec versions</u> - <table> - <thead> - <td></td> - <td>Version</td> - <td>Release date</td> - </thead> - @foreach (var (version, info) in ClientVersions) { - <tr> - <td>@(ClientVersionsResponse.Versions.Contains(version) ? "\u2714" : "\u274c")</td> - <td><a href="@info.SpecUrl">@info.Name</a></td> - <td>@info.Released</td> - </tr> - } - - @foreach (var version in ClientVersionsResponse.Versions) { - if (!ClientVersions.ContainsKey(version)) { - <tr> - <td>@("\u2714")</td> - <td><a href="https://spec.matrix.org/@version">Unknown version: @version</a></td> - <td></td> - </tr> - } - } - </table> - <u>Unstable features</u> - <table> - <thead> - <td style="padding-right: 8px;">Supported</td> - <td style="padding-right: 8px;">Enabled</td> - <td style="padding-right: 8px;">Name</td> - </thead> - @* @foreach (var (version, info) in ClientVersions) { *@ - @* <tr> *@ - @* *@ - @* <td>@("\u2714")</td> *@ - @* <td>@(ClientVersionsResponse.Versions.Contains(version) ? "\u2714" : "\u274c")</td> *@ - @* <td>@info.Released</td> *@ - @* </tr> *@ - @* } *@ - - @foreach (var version in ClientVersionsResponse.UnstableFeatures) { - if (!ClientVersions.ContainsKey(version.Key)) { - <tr> - <td>@("\u2714")</td> - <td>@(version.Value ? "\u2714" : "\u274c")</td> - <td>@version.Key</td> - </tr> - } - } - </table> -} - - -@code { - - [Parameter] - public string? Homeserver { get; set; } - - public ServerVersionResponse? ServerVersionResponse { get; set; } - public ClientVersionsResponse? ClientVersionsResponse { get; set; } - - protected override async Task OnParametersSetAsync() { - if (Homeserver is not null) { - var rhs = await hsProvider.GetRemoteHomeserver(Homeserver); - ServerVersionResponse = await (rhs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)); - ClientVersionsResponse = await rhs.GetClientVersionsAsync(); - } - base.OnParametersSetAsync(); - } - - private class ClientVersionInfo { - public string Name { get; set; } - public string SpecUrl { get; set; } - public DateTime Released { get; set; } - } - - private Dictionary<string, ClientVersionInfo> ClientVersions = new() { - { - "legacy", - new() { - Name = "Legacy: Last draft before formal release of r0.0.0", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/legacy/" - } - }, - { - "r0.0.0", - new() { - Name = "r0.0.0: Initial release: media repo, sync v2", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.0.0/" - } - }, - { - "r0.0.1", - new() { - Name = "r0.0.1: User-interactive authentication, groups, read receipts, presence", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.0.1/" - } - }, - { - "r0.1.0", - new() { - Name = "r0.1.0: Device management, account data, push rules, VoIP", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.1.0/" - } - }, - { - "r0.2.0", - new() { - Name = "r0.2.0: Clarifications", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/client_server/r0.2.0.html" - } - }, - { - "r0.3.0", - new() { - Name = "r0.3.0: Device management", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/client_server/r0.3.0.html" - } - }, - { - "r0.4.0", - new() { - Name = "r0.4.0: Room directory", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.4.0/" - } - }, - { - "r0.5.0", - new() { - Name = "r0.5.0: Push rules, VoIP, groups, read receipts, presence", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.5.0/" - } - }, - { - "r0.6.0", - new() { - Name = "r0.6.0: Unbinding 3PIDs, clean up bindings from register", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.6.0/" - } - }, - { - "r0.6.1", - new(){ - Name = "r0.6.1: Moderation policies, better alias handling", - Released = DateTime.Parse("2014-07-01 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/legacy/r0.6.1/" - } - }, - { - "v1.1", - new() { - Name = "v1.1: Key backup, knocking", - Released = DateTime.Parse("2021-11-09 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/v1.1/" - } - }, { - "v1.2", - new() { - Name = "v1.2: ", - Released = DateTime.Parse("2022-02-02 00:00:00 +0000"), - SpecUrl = "https://spec.matrix.org/v1.2/" - } - }, { - "v1.3", - new() { - Name = "v1.3: ", - Released = DateTime.Parse("2022-06-15 00:00:00 +0100"), - SpecUrl = "https://spec.matrix.org/v1.3/" - } - }, { - "v1.4", - new() { - Name = "v1.4: ", - Released = DateTime.Parse("2022-09-29 00:00:00 +0100"), - SpecUrl = "https://spec.matrix.org/v1.4/" - } - }, { - "v1.5", - new() { - Name = "v1.5: ", - Released = DateTime.Parse("2022-11-17 08:22:11 -0700"), - SpecUrl = "https://spec.matrix.org/v1.5/" - } - }, { - "v1.6", - new () { - Name = "v1.6: ", - Released = DateTime.Parse("2023-02-14 08:25:40 -0700"), - SpecUrl = "https://spec.matrix.org/v1.6" - } - }, { - "v1.7", - new () { - Name = "v1.7: ", - Released = DateTime.Parse("2023-05-25 09:47:21 -0600"), - SpecUrl = "https://spec.matrix.org/v1.7" - } - }, { - "v1.8", - new () { - Name = "v1.8: Room version 11", - Released = DateTime.Parse("2023-08-23 09:23:53 -0600"), - SpecUrl = "https://spec.matrix.org/v1.8" - } - } - }; - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor b/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor deleted file mode 100644
index bcbc974..0000000 --- a/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor +++ /dev/null
@@ -1,84 +0,0 @@ -@page "/CopyPowerlevel" -@using System.Diagnostics -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -<h3>Copy powerlevel</h3> -<hr/> - -<p>Users: </p> -@foreach (var hs in hss) { - <p>@hs.WhoAmI.UserId</p> -} - -<br/> -<LinkButton OnClick="Execute">Execute</LinkButton> -<br/> -@foreach (var line in Enumerable.Reverse(log)) { - <p>@line</p> -} - -@code { - private List<string> log { get; set; } = new(); - List<AuthenticatedHomeserverGeneric> hss { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var sessions = await MRUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await MRUStorage.GetSession(userAuth); - if (session is not null) { - hss.Add(session); - StateHasChanged(); - } - } - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - foreach (var hs in hss) { - var rooms = await hs.GetJoinedRooms(); - var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - } - } - - private async Task<string> Execute(AuthenticatedHomeserverGeneric hs, GenericRoom room) { - try { - var pls = await room.GetPowerLevelsAsync(); - // if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.UsersDefault) return "I am default PL in " + room.RoomId; - if (!pls.UserHasStatePermission(hs.WhoAmI.UserId, RoomPowerLevelEventContent.EventId)) return "I do not have permission to send PL in " + room.RoomId; - foreach (var ahs in hss) { - if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.GetUserPowerLevel(ahs.WhoAmI.UserId)) { - log.Add("I am same PL in " + room.RoomId); - continue; - } - - pls.SetUserPowerLevel(ahs.WhoAmI.UserId, pls.GetUserPowerLevel(hs.WhoAmI.UserId)); - await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls); - log.Add($"Updated powerlevel of {room.RoomId} to {pls.GetUserPowerLevel(ahs.WhoAmI.UserId)}"); - } - - } - catch (MatrixException e) { - return $"Failed to update PLs in {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to update PLs in {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor b/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor deleted file mode 100644
index dbf2f5f..0000000 --- a/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor +++ /dev/null
@@ -1,54 +0,0 @@ -@page "/KnownHomeserverList" -@using System.Diagnostics -@using ArcaneLibs.Extensions -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -<h3>Known Homeserver List</h3> -<hr/> - -@if (!IsFinished) { - <p> - <b>Loading...</b> - </p> -} - -@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) { - <p>@homeserver - @members</p> -} -<hr/> - -@code { - Dictionary<string, List<string>> homeservers { get; set; } = new(); - Dictionary<string, int> counts { get; set; } = new(); - // List<HomeserverInfo> Homeservers = new(); - bool IsFinished { get; set; } - // HomeserverInfoQueryProgress QueryProgress { get; set; } = new(); - AuthenticatedHomeserverGeneric? hs { get; set; } - - protected override async Task OnInitializedAsync() { - hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).ToAsyncEnumerable(); - await foreach (var result in fetchTasks) { - foreach (var (resHomeserver, resMembers) in result) { - if (!homeservers.TryAdd(resHomeserver, resMembers)) { - homeservers[resHomeserver].AddRange(resMembers); - } - counts[resHomeserver] = homeservers[resHomeserver].Count; - } - // StateHasChanged(); - // await Task.Delay(250); - } - - foreach (var resHomeserver in homeservers.Keys) { - homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList(); - counts[resHomeserver] = homeservers[resHomeserver].Count; - } - - IsFinished = true; - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor b/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor deleted file mode 100644
index 816b7db..0000000 --- a/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor +++ /dev/null
@@ -1,110 +0,0 @@ -@page "/MassRoomJoin" -@using System.Diagnostics -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -<h3>Mass join room</h3> -<hr/> -<p>Room: </p> -<FancyTextBox @bind-Value="@roomId"></FancyTextBox> - -<p>Users: </p> -@foreach (var hs in hss) { - <p>@hs.WhoAmI.UserId</p> -} - -<br/> -<LinkButton OnClick="Execute">Execute</LinkButton> -<br/> -@foreach (var line in Enumerable.Reverse(log)) { - <p>@line</p> -} - -@code { - private List<string> log { get; set; } = new(); - List<AuthenticatedHomeserverGeneric> hss { get; set; } = new(); - string roomId { get; set; } - - protected override async Task OnInitializedAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var sessions = await MRUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await MRUStorage.GetSession(userAuth); - if (session is not null) { - hss.Add(session); - StateHasChanged(); - } - } - - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - } - - private async Task Execute() { - // foreach (var hs in hss) { - // var rooms = await hs.GetJoinedRooms(); - var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - tasks = hss.Select(ExecuteJoin).ToAsyncEnumerable(); - await foreach (var a in tasks) { - if (!string.IsNullOrWhiteSpace(a)) { - log.Add(a); - StateHasChanged(); - } - } - // } - } - - private async Task<string> ExecuteInvite(AuthenticatedHomeserverGeneric hs) { - var room = hs.GetRoom(roomId); - try { - try { - var joinRule = await room.GetJoinRuleAsync(); - if (joinRule.JoinRule == RoomJoinRulesEventContent.JoinRules.Public) return "Room is public, no invite needed"; - } - catch { } - var pls = await room.GetPowerLevelsAsync(); - if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + room.RoomId; - await room.InviteUsersAsync(hss.Select(x => x.WhoAmI.UserId).ToList()); - log.Add($"Invited to {room.RoomId} to {pls.GetUserPowerLevel(hs.WhoAmI.UserId)}"); - } - catch (MatrixException e) { - return $"Failed to invite in {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to invite in {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - - private async Task<string> ExecuteJoin(AuthenticatedHomeserverGeneric hs) { - var room = hs.GetRoom(roomId); - try { - try { - var mse = await room.GetStateOrNullAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, hs.WhoAmI.UserId); - if (mse?.Membership == "join") return $"User {hs.WhoAmI.UserId} already in room"; - } - catch { } - await room.JoinAsync(); - } - catch (MatrixException e) { - return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; - } - catch (Exception e) { - return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; - } - StateHasChanged(); - return ""; - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor b/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor deleted file mode 100644
index 20aa639..0000000 --- a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor +++ /dev/null
@@ -1,111 +0,0 @@ -@page "/MediaLocator" -@using LibMatrix.Homeservers -@inject HttpClient Http -<h3>Media locator</h3> -<hr/> - -<b>This is going to expose your IP address to all these homeservers!</b> -<details> - <summary>Checked homeserver list (@homeservers.Count entries)</summary> - <ul> - @foreach (var hs in homeservers) { - <li>@hs</li> - } - </ul> -</details> -<button @onclick="addMoreHomeservers">Add more homeservers</button> -<br/> -<span>MXC URL: </span> -<input type="text" @bind="mxcUrl"/> -<button @onclick="executeSearch">Search</button> - -@if (successResults.Count > 0) { - <h4>Successes</h4> - <ul> - @foreach (var result in successResults) { - <li>@result</li> - } - </ul> -} - -@if (errorResults.Count > 0) { - <h4>Errors</h4> - <ul> - @foreach (var result in errorResults) { - <li>@result</li> - } - </ul> -} - - -@code { - string mxcUrl { get; set; } - readonly List<string> successResults = new(); - readonly List<string> errorResults = new(); - readonly List<string> homeservers = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - homeservers.AddRange(new[] { - "matrix.org", - "feline.support", - "rory.gay", - "the-apothecary.club", - "envs.net", - "projectsegfau.lt" - }); - } - - Task executeSearch() { - var sem = new SemaphoreSlim(128, 128); - homeservers.ForEach(async hs => { - await sem.WaitAsync(); - var httpClient = new HttpClient { BaseAddress = new Uri(hs) }; - httpClient.Timeout = TimeSpan.FromSeconds(5); - var rmu = mxcUrl.Replace("mxc://", $"{hs}/_matrix/media/v3/download/"); - try { - var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, rmu)); - if (res.IsSuccessStatusCode) { - successResults.Add($"{hs}: found - {res.Content.Headers.ContentLength} bytes"); - StateHasChanged(); - return; - } - errorResults.Add($"Error: {hs} - {res.StatusCode}\n" + await res.Content.ReadAsStringAsync()); - } - catch (Exception e) { - errorResults.Add($"Error: {e}"); - } - finally { - sem.Release(); - } - StateHasChanged(); - }); - return Task.CompletedTask; - } - - async Task addMoreHomeservers() { - var res = await Http.GetAsync("/homeservers.txt"); - var content = await res.Content.ReadAsStringAsync(); - homeservers.Clear(); - var lines = content.Split("\n"); - - var rhs = new RemoteHomeserver("rory.gay"); - var sem = new SemaphoreSlim(128, 128); - lines.ToList().ForEach(async line => { - await sem.WaitAsync(); - try { - homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client); - StateHasChanged(); - } - catch (Exception e) { - Console.WriteLine(e); - } - finally { - sem.Release(); - } - }); - - StateHasChanged(); - } - -} diff --git a/MatrixRoomUtils.Web/Pages/Tools/SpaceDebug.razor b/MatrixRoomUtils.Web/Pages/Tools/SpaceDebug.razor deleted file mode 100644
index 5b4815d..0000000 --- a/MatrixRoomUtils.Web/Pages/Tools/SpaceDebug.razor +++ /dev/null
@@ -1,113 +0,0 @@ -@page "/SpaceDebug" -@using LibMatrix.Filters -@using LibMatrix.Helpers -<h3>SpaceDebug</h3> -<hr/> - -<p>@Status</p> - -<b>Has parent:</b> -<br/> - -@foreach (var (roomId, parents) in SpaceParents) { - <p>@roomId's parents</p> - <ul> - @foreach (var parent in parents) { - <li>@parent</li> - } - </ul> -} - -<b>Space children:</b> - -@foreach (var (roomId, children) in SpaceChildren) { - <p>@roomId's children</p> - <ul> - @foreach (var child in children) { - <li>@child</li> - } - </ul> -} - -@code { - private string _status = "Loading..."; - - public string Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - public Dictionary<string, List<string>> SpaceChildren { get; set; } = new(); - public Dictionary<string, List<string>> SpaceParents { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - Status = "Getting homeserver..."; - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - var syncHelper = new SyncHelper(hs) { - Filter = new SyncFilter() { - Presence = new(0), - Room = new() { - AccountData = new(limit: 0), - Ephemeral = new(limit: 0), - State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }), - Timeline = new(limit: 0) - }, - AccountData = new(limit: 0) - } - }; - - Status = "Syncing..."; - - var syncs = syncHelper.EnumerateSyncAsync(); - await foreach (var sync in syncs) { - if (sync is null) { - Status = "Sync failed"; - continue; - } - - if (sync.Rooms is null) { - Status = "No rooms in sync..."; - break; - } - - if (sync.Rooms.Join is null) { - Status = "No joined rooms in sync..."; - break; - } - - if (sync.Rooms.Join.Count == 0) { - Status = "Joined rooms list was empty..."; - break; - } - - // nextBatch = sync.NextBatch; - foreach (var (roomId, data) in sync.Rooms!.Join!) { - data.State?.Events?.ForEach(e => { - if (e.Type == "m.space.child") { - if (!SpaceChildren.ContainsKey(roomId)) SpaceChildren[roomId] = new(); - if (e.RawContent is null) e.StateKey += " (null)"; - else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; - SpaceChildren[roomId].Add(e.StateKey); - } - if (e.Type == "m.space.parent") { - if (!SpaceParents.ContainsKey(roomId)) SpaceParents[roomId] = new(); - if (e.RawContent is null) e.StateKey += " (null)"; - else if (e.RawContent.Count == 0) e.StateKey += " (empty)"; - SpaceParents[roomId].Add(e.StateKey); - } - }); - } - Status = $"Synced {sync.Rooms.Join.Count} rooms, found {SpaceChildren.Count} spaces, {SpaceParents.Count} parents"; - } - Status = $"Synced: found {SpaceChildren.Count}->{SpaceChildren.Sum(x => x.Value.Count)} spaces, {SpaceParents.Count}->{SpaceParents.Sum(x => x.Value.Count)} parents!"; - - await base.OnInitializedAsync(); - } - - -} diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor deleted file mode 100644
index a327793..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMManager.razor +++ /dev/null
@@ -1,62 +0,0 @@ -@page "/User/DirectMessages" -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Responses -@using MatrixRoomUtils.Abstractions -<h3>Direct Messages</h3> -<hr/> - -@foreach (var (targetUser, rooms) in DMRooms) { - <div> - <InlineUserItem User="targetUser"></InlineUserItem> - @foreach (var room in rooms) { - <RoomListItem RoomInfo="room" LoadData="true"></RoomListItem> - } - </div> -} - -@code { - private string? _status; - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - private Dictionary<UserProfileResponse, List<RoomInfo>> DMRooms { get; set; } = new(); - - public string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - Status = "Loading global profile..."; - if (Homeserver.WhoAmI?.UserId is null) return; - - Status = "Loading DM list from account data..."; - var dms = await Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); - DMRooms.Clear(); - foreach (var (userId, rooms) in dms) { - var roomList = new List<RoomInfo>(); - DMRooms.Add(await Homeserver.GetProfileAsync(userId), roomList); - foreach (var room in rooms) { - var roomInfo = new RoomInfo() { Room = Homeserver.GetRoom(room) }; - roomList.Add(roomInfo); - roomInfo.StateEvents.Add(new() { - Type = RoomNameEventContent.EventId, - TypedContent = new RoomNameEventContent() { - Name = await Homeserver.GetRoom(room).GetNameOrFallbackAsync(4) - }, - RoomId = room, Sender = null, EventId = null - }); - } - StateHasChanged(); - } - - StateHasChanged(); - Status = null; - - await base.OnInitializedAsync(); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpace.razor b/MatrixRoomUtils.Web/Pages/User/DMSpace.razor deleted file mode 100644
index 21cc264..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMSpace.razor +++ /dev/null
@@ -1,86 +0,0 @@ -@page "/User/DMSpace/Setup" -@using LibMatrix.Homeservers -@using LibMatrix -@using MatrixRoomUtils.LibDMSpace -@using MatrixRoomUtils.LibDMSpace.StateEvents -@using MatrixRoomUtils.Web.Pages.User.DMSpaceStages -<h3>DM Space Management</h3> -<hr/> -<CascadingValue Value="@DmSpace"> - @switch (Stage) { - case -1: - <p>Initialising...</p> - break; - case 0: - <DMSpaceStage0/> - break; - case 1: - <DMSpaceStage1/> - break; - case 2: - <DMSpaceStage2/> - break; - case 3: - <DMSpaceStage3/> - break; - default: - <p>Stage is unknown value: @Stage!</p> - break; - } -</CascadingValue> - -@code { - private int _stage = -1; - - [Parameter, SupplyParameterFromQuery(Name = "stage")] - public int Stage { - get => _stage; - set { - _stage = value; - Console.WriteLine($"Stage is now {value}"); - StateHasChanged(); - } - } - - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } - - [Parameter] - public DMSpace? DmSpace { get; set; } - - protected override async Task OnInitializedAsync() { - if (NavigationManager.Uri.Contains("?stage=")) { - NavigationManager.NavigateTo("/User/DMSpace", true); - } - DmSpace = this; - Homeserver ??= await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - try { - DmSpaceConfiguration = await Homeserver.GetAccountDataAsync<DMSpaceConfiguration>("gay.rory.dm_space"); - var room = Homeserver.GetRoom(DmSpaceConfiguration.DMSpaceId); - await room.GetStateAsync<object>(DMSpaceInfo.EventId); - Stage = 1; - } - catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") { - Stage = 0; - DmSpaceConfiguration = new(); - } - else throw; - } - catch (Exception e) { - throw; - } - finally { - StateHasChanged(); - } - await base.OnInitializedAsync(); - } - - protected override async Task OnParametersSetAsync() { - StateHasChanged(); - await base.OnParametersSetAsync(); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor deleted file mode 100644
index 49fd5b4..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor +++ /dev/null
@@ -1,11 +0,0 @@ -<b> - <u>Welcome to the DM Space tool!</u> -</b> -<p>This wizard will help you set up a DM space.</p> -<p>This is useful for eg. sharing DM rooms across multiple accounts.</p> -<br/> -<LinkButton href="/User/DMSpace?stage=1">Get started</LinkButton> - -@code { - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor deleted file mode 100644
index 5958fc5..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor +++ /dev/null
@@ -1,128 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.Responses -@using MatrixRoomUtils.LibDMSpace -@using MatrixRoomUtils.LibDMSpace.StateEvents -@using Microsoft.Extensions.Primitives -@using ArcaneLibs.Extensions -<b> - <u>DM Space setup tool - stage 1: Configure space</u> -</b> -<p>You will need a space to use for DM rooms.</p> -@if (DmSpace is not null) { - <p> - Selected space: - <InputSelect @bind-Value="DmSpace.DmSpaceConfiguration.DMSpaceId"> - @foreach (var (id, name) in spaces) { - <option value="@id">@name</option> - } - </InputSelect> - </p> - <p> - <InputCheckbox @bind-Value="DmSpaceInfo.LayerByUser"></InputCheckbox> - Create sub-spaces per user - </p> -} -else { - <b>Error: DmSpaceConfiguration is null!</b> -} - -<br/> -<LinkButton OnClick="@Execute">Next</LinkButton> - -@if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> -} - -@code { - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private Dictionary<string, string> spaces = new() { { "", "New space" } }; - private string? _status; - - [CascadingParameter] - public DMSpace? DmSpace { get; set; } - - public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } - - SemaphoreSlim _semaphoreSlim = new(1, 1); - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) - return; - await _semaphoreSlim.WaitAsync(); - DmSpace.DmSpaceConfiguration ??= new(); - if (spaces.Count == 1) { - Status = "Looking for spaces..."; - var userRoomsEnum = DmSpace.Homeserver.GetJoinedRoomsByType("m.space"); - List<GenericRoom> userRooms = new(); - await foreach (var room in userRoomsEnum) { - userRooms.Add(room); - } - var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); - await foreach(var room in roomChecks) - if(room.HasValue) - spaces.TryAdd(room.Value.id, room.Value.name); - - Status = "Done!"; - } - _semaphoreSlim.Release(); - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { - var crr = CreateRoomRequest.CreatePrivate(DmSpace.Homeserver, "Direct Messages"); - crr.CreationContentBaseType.Type = "m.space"; - DmSpace.DmSpaceConfiguration.DMSpaceId = (await DmSpace.Homeserver.CreateRoom(crr)).RoomId; - } - await DmSpace.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, DmSpace.DmSpaceConfiguration); - var space = DmSpace.Homeserver.GetRoom(DmSpace.DmSpaceConfiguration.DMSpaceId); - await space.SendStateEventAsync(DMSpaceInfo.EventId, DmSpaceInfo); - - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); - } - - public async Task<(string id, string name)?> GetFeasibleSpaces(GenericRoom room) { - try { - var pls = await room.GetPowerLevelsAsync(); - if (!pls.UserHasStatePermission(DmSpace.Homeserver.WhoAmI.UserId, "m.space.child")) { - Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); - return null; - } - var roomName = await room.GetNameAsync(); - Status = $"Found viable space: {roomName}"; - if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { - try { - var dsi = await DmSpace.Homeserver.GetRoom(room.RoomId).GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId) ?? new DMSpaceInfo(); - if (await room.GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId) is not null && dsi is not null) { - DmSpace.DmSpaceConfiguration.DMSpaceId = room.RoomId; - DmSpaceInfo = dsi; - } - } - catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"{room.RoomId} is not a DM space."); - else throw; - } - } - return (room.RoomId, roomName); - } - catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); - else throw; - } - return null; - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor deleted file mode 100644
index 60c68ac..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor +++ /dev/null
@@ -1,242 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Responses -@using MatrixRoomUtils.LibDMSpace -@using MatrixRoomUtils.LibDMSpace.StateEvents -@using ArcaneLibs.Extensions -@using System.Text.Json.Serialization -@using MatrixRoomUtils.Abstractions -<b> - <u>DM Space setup tool - stage 2: Fix DM room attribution</u> -</b> -<p>This is just to make sure that your DMs are attributed to the right person!</p> - -@if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> -} - -@if (DmSpace is not null) { - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.Id)) { - <InlineUserItem User="@userId"></InlineUserItem> - @foreach (var roomInfo in room) { - <RoomListItem RoomInfo="@roomInfo"> - <LinkButton Round="true" OnClick="@(async () => DmToReassign = roomInfo)">Reassign</LinkButton> - </RoomListItem> - } - } -} -else { - <b>Error: DmSpaceConfiguration is null!</b> -} - -<br/> -<LinkButton OnClick="@Execute">Next</LinkButton> - -@{ - var _offset = 0; -} -@foreach (var (room, usersList) in duplicateDmRooms) { - <ModalWindow Title="Duplicate room found" X="_offset += 30" Y="_offset"> - <p>Found room assigned to multiple users: <RoomListItem RoomInfo="@room"></RoomListItem></p> - <p>Users:</p> - @foreach (var userProfileResponse in usersList) { - <LinkButton OnClick="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))"> - <span>Assign to </span> - <InlineUserItem User="userProfileResponse"></InlineUserItem> - </LinkButton> - <br/> - } - </ModalWindow> -} - -@if (DmToReassign is not null) { - <ModalWindow Title="Re-assign DM" OnCloseClicked="@(() => DmToReassign = null)"> - <RoomListItem RoomInfo="@DmToReassign"></RoomListItem> - @foreach (var userProfileResponse in roomMembers[DmToReassign]) { - <LinkButton OnClick="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))"> - <span>Assign to </span> - <InlineUserItem User="userProfileResponse"></InlineUserItem> - </LinkButton> - <br/> - } - </ModalWindow> -} - -@code { - - private string newMxid { get; set; } = ""; - - private RoomInfo? DmToReassign { - get => _dmToReassign; - set { - _dmToReassign = value; - StateHasChanged(); - } - } - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string? _status; - private RoomInfo? _dmToReassign; - - [CascadingParameter] - public DMSpace? DmSpace { get; set; } - - private Dictionary<UserProfileWithId, List<RoomInfo>> dmRooms { get; set; } = new(); - private Dictionary<RoomInfo, List<UserProfileWithId>> duplicateDmRooms { get; set; } = new(); - private Dictionary<RoomInfo, List<UserProfileWithId>> roomMembers { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } - - SemaphoreSlim _semaphore = new(1, 1); - - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) - return; - await _semaphore.WaitAsync(); - DmToReassign = null; - var hs = DmSpace.Homeserver; - Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); - Status = "Optimising DM list from account data..."; - var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); - foreach (var (user, rooms) in dms) { - for (var i = rooms.Count - 1; i >= 0; i--) { - var roomId = rooms[i]; - if (!joinedRooms.Contains(roomId)) - rooms.RemoveAt(i); - } - dms[user] = rooms.Distinct().ToList(); - } - dms.RemoveAll((x, y) => y is {Count: 0}); - await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); - dmRooms.Clear(); - - Status = "DM list optimised, fetching info..."; - var results = dms.Select(async x => { - var (userId, rooms) = x; - UserProfileWithId userProfile; - try { - var profile = await DmSpace.Homeserver.GetProfileAsync(userId); - userProfile = new() { - AvatarUrl = profile.AvatarUrl, - Id = userId, - DisplayName = profile.DisplayName - }; - } - catch { - userProfile = new() { - AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - DisplayName = userId, - Id = userId - }; - } - var roomList = new List<RoomInfo>(); - var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); - await foreach (var result in tasks) - roomList.Add(result); - return (userProfile, roomList); - // StateHasChanged(); - }).ToAsyncEnumerable(); - await foreach (var res in results) { - dmRooms.Add(res.userProfile, res.roomList); - // Status = $"Listed {dmRooms.Count} users"; - } - _semaphore.Release(); - var duplicateDmRoomIds = new Dictionary<string, List<UserProfileWithId>>(); - foreach (var (user, rooms) in dmRooms) { - foreach (var roomInfo in rooms) { - if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) - duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); - duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); - } - } - duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); - foreach (var (roomId, users) in duplicateDmRoomIds) { - duplicateDmRooms.Add(dmRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); - } - - // StateHasChanged(); - Status = null; - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); - } - - private async Task<RoomInfo> GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo() { - Room = room - }; - roomMembers[roomInfo] = new(); - roomInfo.CreationEventContent = await room.GetCreateEventAsync(); - try { - roomInfo.RoomName = await room.GetNameAsync(); - } - catch { } - - var membersEnum = room.GetMembersEnumerableAsync(true); - await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent) - roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); - - if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { - List<string> displayNames = new List<string>(); - foreach (var member in roomMembers[roomInfo]) - if (!string.IsNullOrWhiteSpace(member.DisplayName)) - displayNames.Add(member.DisplayName); - roomInfo.RoomName = string.Join(", ", displayNames); - } - try { - string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; - if (room is not null) - roomInfo.RoomIcon = roomIcon; - } - catch { } - return roomInfo; - } - - private async Task<List<RoomInfo>> GetRoomInfoForRooms(List<GenericRoom> rooms) { - var tasks = rooms.Select(GetRoomInfo).ToList(); - await Task.WhenAll(tasks); - return tasks.Select(x => x.Result).ToList(); - } - - private async Task SetRoomAssignment(string roomId, string userId) { - var hs = DmSpace.Homeserver; - Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); - Status = "Updating DM list from account data..."; - - foreach (var (user, rooms) in dms) { - rooms.RemoveAll(x => x == roomId); - dms[user] = rooms.Distinct().ToList(); - } - if(!dms.ContainsKey(userId)) - dms.Add(userId, new()); - dms[userId].Add(roomId); - dms.RemoveAll((x, y) => y is {Count: 0}); - await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); - - duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); - StateHasChanged(); - if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); - } - - private class UserProfileWithId : UserProfileResponse { - [JsonIgnore] - public string Id { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor deleted file mode 100644
index 42573e6..0000000 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor +++ /dev/null
@@ -1,191 +0,0 @@ -@using LibMatrix.Homeservers -@using LibMatrix.RoomTypes -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Responses -@using MatrixRoomUtils.LibDMSpace -@using MatrixRoomUtils.LibDMSpace.StateEvents -@using ArcaneLibs.Extensions -@using System.Text.Json.Serialization -@using MatrixRoomUtils.Abstractions - -<b> - <u>DM Space setup tool - stage 3: Preview space layout</u> -</b> -<p>This gives you a preview of how your settings would impact layout!</p> - -@if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> -} - -@if (DmSpace is not null) { - @if (dmSpaceInfo is not null && dmSpaceRoomInfo is not null) { - <p> - <InputCheckbox @bind-Value="dmSpaceInfo.LayerByUser"></InputCheckbox> - Create sub-spaces per user - </p> - @if (!dmSpaceInfo.LayerByUser) { - <RoomListItem RoomInfo="@dmSpaceRoomInfo"></RoomListItem> - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { - @foreach (var roomInfo in room) { - <div style="margin-left: 32px;"> - <RoomListItem RoomInfo="@roomInfo"></RoomListItem> - </div> - } - } - } - else { - <RoomListItem RoomInfo="@dmSpaceRoomInfo"></RoomListItem> - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { - <div style="margin-left: 32px;"> - <RoomListItem RoomInfo="@userId"></RoomListItem> - </div> - @foreach (var roomInfo in room) { - <div style="margin-left: 64px;"> - <RoomListItem RoomInfo="@roomInfo"></RoomListItem> - </div> - } - } - } - } - else { - <b>Error: dmSpaceInfo is null!</b> - } -} -else { - <b>Error: DmSpaceConfiguration is null!</b> -} - -<br/> -<LinkButton OnClick="@Execute">Next</LinkButton> - -@code { - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string? _status; - - [CascadingParameter] - public DMSpace? DmSpace { get; set; } - - private Dictionary<RoomInfo, List<RoomInfo>> dmRooms { get; set; } = new(); - private DMSpaceInfo? dmSpaceInfo { get; set; } - private RoomInfo? dmSpaceRoomInfo { get; set; } - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } - - SemaphoreSlim _semaphore = new(1, 1); - - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) - return; - await _semaphore.WaitAsync(); - var hs = DmSpace.Homeserver; - var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); - dmSpaceRoomInfo = new() { - RoomName = await dmSpaceRoom.GetNameAsync(), - CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), - RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - Room = dmSpaceRoom - }; - dmSpaceInfo = await dmSpaceRoom.GetDmSpaceInfo(); - Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); - dmRooms.Clear(); - - Status = "DM list optimised, fetching info..."; - var results = dms.Select(async x => { - var (userId, rooms) = x; - UserProfileWithId userProfile; - try { - var profile = await DmSpace.Homeserver.GetProfileAsync(userId); - userProfile = new() { - AvatarUrl = profile.AvatarUrl, - Id = userId, - DisplayName = profile.DisplayName - }; - } - catch { - userProfile = new() { - AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - DisplayName = userId, - Id = userId - }; - } - var roomList = new List<RoomInfo>(); - var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); - await foreach (var result in tasks) - roomList.Add(result); - return (userProfile, roomList); - }).ToAsyncEnumerable(); - await foreach (var res in results) { - dmRooms.Add(new RoomInfo() { - Room = dmSpaceRoom, - RoomIcon = res.userProfile.AvatarUrl, - RoomName = res.userProfile.DisplayName, - CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() - }, res.roomList); - } - _semaphore.Release(); - Status = null; - await base.OnParametersSetAsync(); - } - - private async Task Execute() { - var hs = DmSpace.Homeserver; - var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); - NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); - } - - private async Task<RoomInfo> GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo() { - Room = room - }; - var roomMembers = new List<UserProfileWithId>(); - roomInfo.CreationEventContent = await room.GetCreateEventAsync(); - try { - roomInfo.RoomName = await room.GetNameAsync(); - } - catch { } - - var membersEnum = room.GetMembersEnumerableAsync(true); - await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent) - roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); - - if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { - List<string> displayNames = new List<string>(); - foreach (var member in roomMembers) - if (!string.IsNullOrWhiteSpace(member.DisplayName)) - displayNames.Add(member.DisplayName); - roomInfo.RoomName = string.Join(", ", displayNames); - } - try { - string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; - if (room is not null) - roomInfo.RoomIcon = roomIcon; - } - catch { } - return roomInfo; - } - - private async Task<List<RoomInfo>> GetRoomInfoForRooms(List<GenericRoom> rooms) { - var tasks = rooms.Select(GetRoomInfo).ToList(); - await Task.WhenAll(tasks); - return tasks.Select(x => x.Result).ToList(); - } - - private class UserProfileWithId : UserProfileResponse { - [JsonIgnore] - public string Id { get; set; } - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/Profile.razor b/MatrixRoomUtils.Web/Pages/User/Profile.razor deleted file mode 100644
index 73d7c6e..0000000 --- a/MatrixRoomUtils.Web/Pages/User/Profile.razor +++ /dev/null
@@ -1,134 +0,0 @@ -@page "/User/Profile" -@using LibMatrix.Homeservers -@using LibMatrix.EventTypes.Spec.State -@using ArcaneLibs.Extensions -@using LibMatrix.Responses -<h3>Manage Profile - @Homeserver?.WhoAmI?.UserId</h3> -<hr/> - -@if (NewProfile is not null) { - <h4>Profile</h4> - <hr/> - <div> - <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> - <div style="display: inline-block; vertical-align: middle;"> - <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/> - <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox> - <InputFile OnChange="@AvatarChanged"></InputFile><br/> - <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton> - <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton> - </div> - </div> - @if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> - } - - <br/> - - @* <details> *@ - <h4>Room profiles<hr></h4> - - @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { - <details class="details-compact"> - <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> - <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> - <div style="display: inline-block; vertical-align: middle;"> - <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/> - <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox> - <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/> - <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton> - </div> - <br/> - @if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> - } - </details> - <br/> - } - // </details> -} - -@code { - private string? _status = null; - - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - private UserProfileResponse? NewProfile { get; set; } - private UserProfileResponse? OldProfile { get; set; } - - private string? Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private Dictionary<string, RoomMemberEventContent> RoomProfiles { get; set; } = new(); - private Dictionary<string, string> RoomNames { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - Status = "Loading global profile..."; - if (Homeserver.WhoAmI?.UserId is null) return; - NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); - OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); - Status = "Loading room profiles..."; - var roomProfiles = Homeserver.GetRoomProfilesAsync(); - await foreach (var (roomId, roomProfile) in roomProfiles) { - // Status = $"Got profile for {roomId}..."; - RoomProfiles[roomId] = roomProfile; //.DeepClone(); - } - - StateHasChanged(); - Status = "Room profiles loaded, loading room names..."; - - var roomNameTasks = RoomProfiles.Keys.Select(x => Homeserver.GetRoom(x)).Select(async x => { - var name = await x.GetNameOrFallbackAsync(); - return new KeyValuePair<string, string?>(x.RoomId, name); - }).ToAsyncEnumerable(); - - await foreach (var (roomId, roomName) in roomNameTasks) { - // Status = $"Got room name for {roomId}: {roomName}"; - RoomNames[roomId] = roomName; - } - - StateHasChanged(); - Status = null; - - await base.OnInitializedAsync(); - } - - private async Task AvatarChanged(InputFileChangeEventArgs arg) { - var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType); - Console.WriteLine(res); - NewProfile.AvatarUrl = res; - StateHasChanged(); - } - - private async Task UpdateProfile(bool restoreRoomProfiles = false) { - Status = "Busy processing global profile update, please do not leave this page..."; - StateHasChanged(); - await Homeserver.UpdateProfileAsync(NewProfile, restoreRoomProfiles); - Status = null; - StateHasChanged(); - await OnInitializedAsync(); - } - - private async Task RoomAvatarChanged(InputFileChangeEventArgs arg, string roomId) { - var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType); - Console.WriteLine(res); - RoomProfiles[roomId].AvatarUrl = res; - StateHasChanged(); - } - - private async Task UpdateRoomProfile(string roomId) { - Status = "Busy processing room profile update, please do not leave this page..."; - StateHasChanged(); - var room = Homeserver.GetRoom(roomId); - await room.SendStateEventAsync("m.room.member", Homeserver.WhoAmI.UserId, RoomProfiles[roomId]); - Status = null; - StateHasChanged(); - } - -} \ No newline at end of file