diff options
17 files changed, 214 insertions, 169 deletions
diff --git a/MatrixRoomUtils.Bot/Bot/Commands/CmdCommand.cs b/MatrixRoomUtils.Bot/Bot/Commands/CmdCommand.cs index c267298..66f3c4d 100644 --- a/MatrixRoomUtils.Bot/Bot/Commands/CmdCommand.cs +++ b/MatrixRoomUtils.Bot/Bot/Commands/CmdCommand.cs @@ -39,7 +39,7 @@ public class CmdCommand : ICommand { if ((output.Count > 0 && (msg + output[0]).Length > 64000) || output.Count == 0) { await ctx.Room.SendMessageEventAsync("m.room.message", new() { FormattedBody = $"```ansi\n{msg}\n```", - Body = Markdig.Markdown.ToHtml(msg), + // Body = Markdig.Markdown.ToHtml(msg), Format = "org.matrix.custom.html" }); msg = ""; diff --git a/MatrixRoomUtils.Bot/MatrixRoomUtils.Bot.csproj b/MatrixRoomUtils.Bot/MatrixRoomUtils.Bot.csproj index 7012647..5eba4fd 100644 --- a/MatrixRoomUtils.Bot/MatrixRoomUtils.Bot.csproj +++ b/MatrixRoomUtils.Bot/MatrixRoomUtils.Bot.csproj @@ -22,7 +22,6 @@ <ItemGroup> <PackageReference Include="ArcaneLibs" Version="1.0.0-preview3020494760.012ed3f" /> - <PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.5.23280.8" /> </ItemGroup> <ItemGroup> diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs index 852e1d8..060867d 100644 --- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs +++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs @@ -1,3 +1,4 @@ +using System.Net.Http.Headers; using System.Reflection; using System.Text.Json; @@ -19,6 +20,7 @@ public static class HttpClientExtensions { public class MatrixHttpClient : HttpClient { public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + Console.WriteLine($"Sending request to {request.RequestUri}"); try { HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"); @@ -35,10 +37,10 @@ public class MatrixHttpClient : HttpClient { } var a = await base.SendAsync(request, cancellationToken); if (!a.IsSuccessStatusCode) { - Console.WriteLine($"Failed to send request: {a.StatusCode}"); var content = await a.Content.ReadAsStringAsync(cancellationToken); if (content.StartsWith('{')) { var ex = JsonSerializer.Deserialize<MatrixException>(content); + Console.WriteLine($"Failed to send request: {ex}"); if (ex?.RetryAfterMs is not null) { await Task.Delay(ex.RetryAfterMs.Value, cancellationToken); typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(request, 0); @@ -50,4 +52,13 @@ public class MatrixHttpClient : HttpClient { } return a; } + // GetFromJsonAsync + public async Task<T> GetFromJsonAsync<T>(string requestUri, CancellationToken cancellationToken = default) { + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await SendAsync(request, cancellationToken); + response.EnsureSuccessStatusCode(); + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + return await JsonSerializer.DeserializeAsync<T>(responseStream, cancellationToken: cancellationToken); + } } \ No newline at end of file diff --git a/MatrixRoomUtils.Core/MatrixException.cs b/MatrixRoomUtils.Core/MatrixException.cs index 50fae20..4795d6d 100644 --- a/MatrixRoomUtils.Core/MatrixException.cs +++ b/MatrixRoomUtils.Core/MatrixException.cs @@ -17,41 +17,41 @@ public class MatrixException : Exception { public int? RetryAfterMs { get; set; } public override string Message => - ErrorCode switch { + $"{ErrorCode}: {ErrorCode switch { // common - "M_FORBIDDEN" => "You do not have permission to perform this action: " + Error, - "M_UNKNOWN_TOKEN" => "The access token specified was not recognised: " + Error + (SoftLogout == true ? " (soft logout)" : ""), - "M_MISSING_TOKEN" => "No access token was specified: " + Error, - "M_BAD_JSON" => "Request contained valid JSON, but it was malformed in some way: " + Error, - "M_NOT_JSON" => "Request did not contain valid JSON: " + Error, - "M_NOT_FOUND" => "The requested resource was not found: " + Error, - "M_LIMIT_EXCEEDED" => "Too many requests have been sent in a short period of time. Wait a while then try again: " + Error, - "M_UNRECOGNISED" => "The server did not recognise the request: " + Error, - "M_UNKOWN" => "The server encountered an unexpected error: " + Error, + "M_FORBIDDEN" => $"You do not have permission to perform this action: {Error}", + "M_UNKNOWN_TOKEN" => $"The access token specified was not recognised: {Error}{(SoftLogout == true ? " (soft logout)" : "")}", + "M_MISSING_TOKEN" => $"No access token was specified: {Error}", + "M_BAD_JSON" => $"Request contained valid JSON, but it was malformed in some way: {Error}", + "M_NOT_JSON" => $"Request did not contain valid JSON: {Error}", + "M_NOT_FOUND" => $"The requested resource was not found: {Error}", + "M_LIMIT_EXCEEDED" => $"Too many requests have been sent in a short period of time. Wait a while then try again: {Error}", + "M_UNRECOGNISED" => $"The server did not recognise the request: {Error}", + "M_UNKOWN" => $"The server encountered an unexpected error: {Error}", // endpoint specific - "M_UNAUTHORIZED" => "The request did not contain valid authentication information for the target of the request: " + Error, - "M_USER_DEACTIVATED" => "The user ID associated with the request has been deactivated: " + Error, - "M_USER_IN_USE" => "The user ID associated with the request is already in use: " + Error, - "M_INVALID_USERNAME" => "The requested user ID is not valid: " + Error, - "M_ROOM_IN_USE" => "The room alias requested is already taken: " + Error, - "M_INVALID_ROOM_STATE" => "The room associated with the request is not in a valid state to perform the request: " + Error, - "M_THREEPID_IN_USE" => "The threepid requested is already associated with a user ID on this server: " + Error, - "M_THREEPID_NOT_FOUND" => "The threepid requested is not associated with any user ID: " + Error, - "M_THREEPID_AUTH_FAILED" => "The provided threepid and/or token was invalid: " + Error, - "M_THREEPID_DENIED" => "The homeserver does not permit the third party identifier in question: " + Error, - "M_SERVER_NOT_TRUSTED" => "The homeserver does not trust the identity server: " + Error, - "M_UNSUPPORTED_ROOM_VERSION" => "The room version is not supported: " + Error, - "M_INCOMPATIBLE_ROOM_VERSION" => "The room version is incompatible: " + Error, - "M_BAD_STATE" => "The request was invalid because the state was invalid: " + Error, - "M_GUEST_ACCESS_FORBIDDEN" => "Guest access is forbidden: " + Error, - "M_CAPTCHA_NEEDED" => "Captcha needed: " + Error, - "M_CAPTCHA_INVALID" => "Captcha invalid: " + Error, - "M_MISSING_PARAM" => "Missing parameter: " + Error, - "M_INVALID_PARAM" => "Invalid parameter: " + Error, - "M_TOO_LARGE" => "The request or entity was too large: " + Error, - "M_EXCLUSIVE" => "The resource being requested is reserved by an application service, or the application service making the request has not created the resource: " + Error, - "M_RESOURCE_LIMIT_EXCEEDED" => "Exceeded resource limit: " + Error, - "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => "Cannot leave server notice room: " + Error, - _ => "Unknown error: " + new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true) - }; + "M_UNAUTHORIZED" => $"The request did not contain valid authentication information for the target of the request: {Error}", + "M_USER_DEACTIVATED" => $"The user ID associated with the request has been deactivated: {Error}", + "M_USER_IN_USE" => $"The user ID associated with the request is already in use: {Error}", + "M_INVALID_USERNAME" => $"The requested user ID is not valid: {Error}", + "M_ROOM_IN_USE" => $"The room alias requested is already taken: {Error}", + "M_INVALID_ROOM_STATE" => $"The room associated with the request is not in a valid state to perform the request: {Error}", + "M_THREEPID_IN_USE" => $"The threepid requested is already associated with a user ID on this server: {Error}", + "M_THREEPID_NOT_FOUND" => $"The threepid requested is not associated with any user ID: {Error}", + "M_THREEPID_AUTH_FAILED" => $"The provided threepid and/or token was invalid: {Error}", + "M_THREEPID_DENIED" => $"The homeserver does not permit the third party identifier in question: {Error}", + "M_SERVER_NOT_TRUSTED" => $"The homeserver does not trust the identity server: {Error}", + "M_UNSUPPORTED_ROOM_VERSION" => $"The room version is not supported: {Error}", + "M_INCOMPATIBLE_ROOM_VERSION" => $"The room version is incompatible: {Error}", + "M_BAD_STATE" => $"The request was invalid because the state was invalid: {Error}", + "M_GUEST_ACCESS_FORBIDDEN" => $"Guest access is forbidden: {Error}", + "M_CAPTCHA_NEEDED" => $"Captcha needed: {Error}", + "M_CAPTCHA_INVALID" => $"Captcha invalid: {Error}", + "M_MISSING_PARAM" => $"Missing parameter: {Error}", + "M_INVALID_PARAM" => $"Invalid parameter: {Error}", + "M_TOO_LARGE" => $"The request or entity was too large: {Error}", + "M_EXCLUSIVE" => $"The resource being requested is reserved by an application service, or the application service making the request has not created the resource: {Error}", + "M_RESOURCE_LIMIT_EXCEEDED" => $"Exceeded resource limit: {Error}", + "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => $"Cannot leave server notice room: {Error}", + _ => $"Unknown error: {new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true)}" + }}"; } \ No newline at end of file diff --git a/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs b/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs index f57c855..879ae6b 100644 --- a/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs +++ b/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs @@ -44,7 +44,17 @@ public class GenericRoom { var url = $"/_matrix/client/v3/rooms/{RoomId}/state"; if (!string.IsNullOrEmpty(type)) url += $"/{type}"; if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}"; - return await _httpClient.GetFromJsonAsync<T>(url); + try { + var resp = await _httpClient.GetFromJsonAsync<T>(url); + return resp; + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) { + throw; + } + Console.WriteLine(e); + return default; + } } public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", @@ -56,8 +66,13 @@ public class GenericRoom { } public async Task<string> GetNameAsync() { - var res = await GetStateAsync<RoomNameEventData>("m.room.name"); - return res.Name ?? RoomId; + try { + var res = await GetStateAsync<RoomNameEventData>("m.room.name"); + return res?.Name ?? RoomId; + } + catch (MatrixException e) { + return $"{RoomId} ({e.ErrorCode})"; + } } public async Task JoinAsync(string[]? homeservers = null, string? reason = null) { diff --git a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs index 3be3130..1b93064 100644 --- a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs +++ b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs @@ -13,15 +13,16 @@ public class SpaceRoom : GenericRoom { _homeServer = homeServer; } - public async Task<List<GenericRoom>> GetRoomsAsync(bool includeRemoved = false) { + private static SemaphoreSlim _semaphore = new(1, 1); + public async IAsyncEnumerable<GenericRoom> GetRoomsAsync(bool includeRemoved = false) { + await _semaphore.WaitAsync(); var rooms = new List<GenericRoom>(); - var state = GetFullStateAsync().ToBlockingEnumerable().ToList(); - var childStates = state.Where(x => x.Type == "m.space.child"); - foreach (var stateEvent in childStates) { - if (stateEvent.TypedContent.ToJson() != "{}" || includeRemoved) - rooms.Add(await _homeServer.GetRoom(stateEvent.StateKey)); + var state = GetFullStateAsync(); + await foreach (var stateEvent in state) { + if (stateEvent.Type != "m.space.child") continue; + if (stateEvent.RawContent.ToJson() != "{}" || includeRemoved) + yield return await _homeServer.GetRoom(stateEvent.StateKey); } - - return rooms; + _semaphore.Release(); } } \ No newline at end of file diff --git a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs index 870e0d4..b2ea987 100644 --- a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs +++ b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs @@ -30,9 +30,9 @@ public class HomeserverProviderService { await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); hs._httpClient.Dispose(); hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; - hs._httpClient.Timeout = TimeSpan.FromSeconds(5); + hs._httpClient.Timeout = TimeSpan.FromSeconds(120); hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - + hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!; return hs; } @@ -43,7 +43,7 @@ public class HomeserverProviderService { await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); hs._httpClient.Dispose(); hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; - hs._httpClient.Timeout = TimeSpan.FromSeconds(5); + hs._httpClient.Timeout = TimeSpan.FromSeconds(120); return hs; } diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor index b2d28f6..8b2ff0c 100644 --- a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor +++ b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor @@ -1,4 +1,4 @@ -@page "/RoomStateViewer/{RoomId}/Edit" +@page "/Rooms/{RoomId}/State/Edit" @using System.Net.Http.Headers @using System.Text.Json @using MatrixRoomUtils.Core.Responses diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor deleted file mode 100644 index 55c44d9..0000000 --- a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor +++ /dev/null @@ -1,32 +0,0 @@ -@page "/RoomStateViewer" -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Room state viewer - Room list</h3> -<hr/> -@if (Rooms.Count == 0) { - <p>You are not in any rooms!</p> - @* <p>Loading progress: @checkedRoomCount/@totalRoomCount</p> *@ -} -else { - @foreach (var room in Rooms) { - <a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@room.Replace('.', '~')"> - <RoomListItem RoomId="@room"></RoomListItem> - </a> - } - <div style="margin-bottom: 4em;"></div> -} - -<LogView></LogView> - -@code { - public List<string> Rooms { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - Rooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); - Console.WriteLine("Fetched joined rooms!"); - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor index a0072ab..09b38f0 100644 --- a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor +++ b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor @@ -1,4 +1,4 @@ -@page "/RoomStateViewer/{RoomId}" +@page "/Rooms/{RoomId}/State/View" @using System.Net.Http.Headers @using System.Text.Json @using MatrixRoomUtils.Core.Responses @@ -73,7 +73,6 @@ await base.OnInitializedAsync(); var hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - RoomId = RoomId.Replace('~', '.'); await LoadStatesAsync(); Console.WriteLine("Policy list editor initialized!"); } diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor index 20ddd0d..932748d 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor @@ -1,18 +1,21 @@ @page "/Rooms" +@using MatrixRoomUtils.Core.StateEventTypes <h3>Room list</h3> @if (Rooms is not null) { - <RoomList Rooms="Rooms"></RoomList> + <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile"></RoomList> } @code { private List<GenericRoom> Rooms { get; set; } + private ProfileResponse GlobalProfile { get; set; } protected override async Task OnInitializedAsync() { var hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; + GlobalProfile = await hs.GetProfile(hs.WhoAmI.UserId); Rooms = await hs.GetJoinedRooms(); await base.OnInitializedAsync(); diff --git a/MatrixRoomUtils.Web/Shared/NavMenu.razor b/MatrixRoomUtils.Web/Shared/NavMenu.razor index 5f9ad8a..48d3196 100644 --- a/MatrixRoomUtils.Web/Shared/NavMenu.razor +++ b/MatrixRoomUtils.Web/Shared/NavMenu.razor @@ -23,19 +23,10 @@ <h5 style="margin-left: 1em;">Main tools</h5> <hr style="margin-bottom: 0em;"/> </div> + <div class="nav-item px-3"> - <NavLink class="nav-link" href="RoomManager"> - <span class="oi oi-plus" aria-hidden="true"></span> Manage Rooms - </NavLink> - </div> - <div class="nav-item px-3"> - <NavLink class="nav-link" href="PolicyListEditor"> - <span class="oi oi-plus" aria-hidden="true"></span> Policy list editor - </NavLink> - </div> - <div class="nav-item px-3"> - <NavLink class="nav-link" href="RoomStateViewer"> - <span class="oi oi-plus" aria-hidden="true"></span> Room state viewer + <NavLink class="nav-link" href="Rooms"> + <span class="oi oi-plus" aria-hidden="true"></span> Room list </NavLink> </div> @* <div class="nav-item px-3"> *@ diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor b/MatrixRoomUtils.Web/Shared/RoomList.razor index ac2cbb3..7e002ed 100644 --- a/MatrixRoomUtils.Web/Shared/RoomList.razor +++ b/MatrixRoomUtils.Web/Shared/RoomList.razor @@ -1,18 +1,29 @@ @using MatrixRoomUtils.Web.Shared.RoomListComponents; @using MatrixRoomUtils.Core.StateEventTypes <p>@Rooms.Count rooms total, @RoomsWithTypes.Sum(x=>x.Value.Count) fetched so far...</p> -@foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) { - <RoomListCategory Category="@category"></RoomListCategory> +@if(Rooms.Count != RoomsWithTypes.Sum(x=>x.Value.Count)) { + <p>Fetching more rooms...</p> + @foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) { + <p>@category.Key (@category.Value.Count)</p> + } +} +else { + @foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) { + <RoomListCategory Category="@category" GlobalProfile="@GlobalProfile"></RoomListCategory> + } } @code { [Parameter] public List<GenericRoom> Rooms { get; set; } + [Parameter] + public ProfileResponse? GlobalProfile { get; set; } Dictionary<string, List<GenericRoom>> RoomsWithTypes = new(); - + protected override async Task OnInitializedAsync() { + GlobalProfile ??= await (await MRUStorage.GetCurrentSession()!).GetProfile((await MRUStorage.GetCurrentSession()!).WhoAmI.UserId); if (RoomsWithTypes.Any()) return; var tasks = Rooms.Select(AddRoom); @@ -29,27 +40,32 @@ }; - private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(8, 8); + private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(4, 4); private async Task AddRoom(GenericRoom room) { await _semaphoreSlim.WaitAsync(); - var roomType = GetRoomTypeName((await room.GetCreateEventAsync()).Type); + string roomType; + try { + var createEvent = await room.GetCreateEventAsync(); + roomType = GetRoomTypeName(createEvent.Type); - if (roomType == "Room") { - var shortcodeState = await room.GetStateAsync<MjolnirShortcodeEventData>("org.matrix.mjolnir.shortcode"); - if (shortcodeState is not null) roomType = "Legacy policy room"; + if (roomType == "Room") { + var shortcodeState = await room.GetStateAsync<MjolnirShortcodeEventData>("org.matrix.mjolnir.shortcode"); + if (shortcodeState is not null) roomType = "Legacy policy room"; + } + } + catch (MatrixException e) { + roomType = $"Error: {e.ErrorCode}"; } if (!RoomsWithTypes.ContainsKey(roomType)) { RoomsWithTypes.Add(roomType, new List<GenericRoom>()); } RoomsWithTypes[roomType].Add(room); - + // if (RoomsWithTypes.Count % 10 == 0) StateHasChanged(); - await Task.Delay(100); + // await Task.Delay(100); _semaphoreSlim.Release(); } - private bool _isSpaceChildrenOpen = false; - } \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor index a7e9399..e860321 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor @@ -1,9 +1,12 @@ +@using MatrixRoomUtils.Core.StateEventTypes <details> <summary>@roomType (@rooms.Count)</summary> @foreach (var room in rooms) { <div class="room-list-item"> <RoomListItem Room="@room" ShowOwnProfile="@(roomType == "Room")"></RoomListItem> <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.RoomId}/Timeline")">View timeline</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton> + <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.RoomId}/State/View")">View state</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton> + <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.RoomId}/State/Edit")">Edit state</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton> @if (roomType == "Space") { <RoomListSpace Space="@room"></RoomListSpace> @@ -17,6 +20,9 @@ [Parameter] public KeyValuePair<string, List<GenericRoom>> Category { get; set; } + + [Parameter] + public ProfileResponse? GlobalProfile { get; set; } private string roomType => Category.Key; private List<GenericRoom> rooms => Category.Value; diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListPolicyRoom.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListPolicyRoom.razor new file mode 100644 index 0000000..f05ac7b --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListPolicyRoom.razor @@ -0,0 +1,12 @@ +<LinkButton href="@($"/Rooms/{Room.RoomId}/Policies")">Manage policies</LinkButton> + +@code { + + [Parameter] + public GenericRoom Room { get; set; } + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor index 5d106c3..73dc334 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor @@ -26,7 +26,12 @@ protected override async Task OnInitializedAsync() { if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs)); - Children = (await Space.AsSpace.GetRoomsAsync()).Where(x => !Breadcrumbs.Contains(x.RoomId)).ToList(); + await Task.Delay(Random.Shared.Next(1000, 10000)); + var rooms = Space.AsSpace.GetRoomsAsync(); + await foreach (var room in rooms) { + if(Breadcrumbs.Contains(room.RoomId)) continue; + Children.Add(room); + } await base.OnInitializedAsync(); } diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor index 53219d6..13cc02d 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor @@ -2,15 +2,20 @@ @using System.Text.Json @using MatrixRoomUtils.Core.Helpers @using MatrixRoomUtils.Core.StateEventTypes -<div class="roomListItem" style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content; @(hasDangerousRoomVersion ? "border: red 4px solid;" : hasOldRoomVersion ? "border: #FF0 1px solid;" : "")"> - @if (ShowOwnProfile) { - <img class="imageUnloaded @(string.IsNullOrWhiteSpace(profileAvatar) ? "" : "imageLoaded")" style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@(profileAvatar ?? "/icon-192.png")"/> - <span style="vertical-align: middle; margin-right: 8px; border-radius: 75px; @(hasCustomProfileName ? "background-color: red;" : "")">@(profileName ?? "Loading...")</span> +<div class="roomListItem" id="@RoomId" style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content; @(hasDangerousRoomVersion ? "border: red 4px solid;" : hasOldRoomVersion ? "border: #FF0 1px solid;" : "")"> + @if (OwnMemberState != null) { + <img class="imageUnloaded @(string.IsNullOrWhiteSpace(OwnMemberState?.AvatarUrl ?? GlobalProfile?.AvatarUrl) ? "" : "imageLoaded")" + style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" + src="@MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl ?? "/icon-192.png")"/> + <span style="vertical-align: middle; margin-right: 8px; border-radius: 75px; @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "background-color: red;" : "")"> + @(OwnMemberState?.Displayname ?? GlobalProfile?.DisplayName ?? "Loading...") + </span> <span style="vertical-align: middle; padding-right: 8px; padding-left: 0px;">-></span> } - <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@roomIcon"/> + <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" + src="@roomIcon"/> <div style="display: inline-block;"> - <span style="vertical-align: middle; padding-right: 8px;">@RoomName</span> + <span style="vertical-align: middle; padding-right: 8px;">@roomName</span> @if (ChildContent is not null) { @ChildContent } @@ -24,91 +29,105 @@ public RenderFragment? ChildContent { get; set; } [Parameter] - public GenericRoom Room { get; set; } + public GenericRoom? Room { get; set; } [Parameter] - public string RoomId { get; set; } + public string? RoomId { get; set; } [Parameter] public bool ShowOwnProfile { get; set; } = false; [Parameter] - public string? RoomName { get; set; } + public RoomMemberEventData? OwnMemberState { get; set; } - private string? roomIcon { get; set; } = "/icon-192.png"; + [Parameter] + public ProfileResponse? GlobalProfile { get; set; } - private string? profileAvatar { get; set; } - private string? profileName { get; set; } - private bool hasCustomProfileAvatar { get; set; } = false; - private bool hasCustomProfileName { get; set; } = false; + private string? roomName { get; set; } + + private string? roomIcon { get; set; } = "/icon-192.png"; private bool hasOldRoomVersion { get; set; } = false; private bool hasDangerousRoomVersion { get; set; } = false; - private static SemaphoreSlim _semaphoreSlim = new(128); + private static SemaphoreSlim _semaphoreSlim = new(8); + private static AuthenticatedHomeServer? hs { get; set; } + private static readonly string[] DangerousRoomVersions = { "1", "8" }; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); await _semaphoreSlim.WaitAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + hs ??= await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - if (Room == null) { - if (RoomId == null) { - throw new ArgumentNullException(nameof(RoomId)); - } - Room = await hs.GetRoom(RoomId); - } - else { - RoomId = Room.RoomId; + if (Room is null && RoomId is null) { + throw new ArgumentNullException(nameof(RoomId)); } + Room ??= await hs.GetRoom(RoomId); + RoomId = Room.RoomId; - RoomName ??= await Room.GetNameAsync() ?? "Unnamed room: " + RoomId; + await CheckRoomVersion(); + await GetRoomInfo(); + await LoadOwnProfile(); + _semaphoreSlim.Release(); + } - var ce = await Room.GetCreateEventAsync(); - if (ce is not null) { - if (int.TryParse(ce.RoomVersion, out var rv) && rv < 10) { - hasOldRoomVersion = true; + private async Task LoadOwnProfile() { + if (!ShowOwnProfile) return; + try { + OwnMemberState ??= await Room.GetStateAsync<RoomMemberEventData>("m.room.member", hs.UserId); + GlobalProfile ??= await hs.GetProfile(hs.UserId, true); + } + catch (MatrixException e) { + if (e is { ErrorCode: "M_FORBIDDEN" }) { + Console.WriteLine($"Failed to get profile for {hs.UserId}: {e.Message}"); + ShowOwnProfile = false; } - if (new[] { "1", "8" }.Contains(ce.RoomVersion)) { - hasDangerousRoomVersion = true; - RoomName = "Dangerous room: " + RoomName; + else { + throw; } } + } - var state = await Room.GetStateAsync<RoomAvatarEventData>("m.room.avatar"); - if (state is not null) { - try { - var url = state.Url; - if (url is not null) { - roomIcon = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, url); - Console.WriteLine($"Got avatar for room {RoomId}: {roomIcon} ({url})"); - } + private async Task CheckRoomVersion() { + try { + var ce = await Room.GetCreateEventAsync(); + if (int.TryParse(ce.RoomVersion, out var rv)) { + if (rv < 10) + hasOldRoomVersion = true; } - catch (InvalidOperationException e) { - Console.WriteLine($"Failed to get avatar for room {RoomId}: {e.Message}\n{state.ToJson()}"); + else // treat unstable room versions as dangerous + hasDangerousRoomVersion = true; + + if (DangerousRoomVersions.Contains(ce.RoomVersion)) { + hasDangerousRoomVersion = true; + roomName = "Dangerous room: " + roomName; } - catch (Exception e) { - Console.WriteLine(e); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_FORBIDDEN" }) { + throw; } } + } + + private async Task GetRoomInfo() { + try { + roomName ??= await Room.GetNameAsync(); - if (ShowOwnProfile) { - var profile = await hs.GetProfile(hs.UserId, true); - - var memberState = await Room.GetStateAsync<RoomMemberEventData>("m.room.member", hs.UserId); - if (memberState is not null) { - - hasCustomProfileAvatar = memberState.AvatarUrl != profile.AvatarUrl; - profileAvatar = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, memberState.AvatarUrl ?? profile.AvatarUrl ?? "/icon-192.png"); - - hasCustomProfileName = memberState.Displayname != profile.DisplayName; - profileName = memberState.Displayname; + var state = await Room.GetStateAsync<RoomAvatarEventData>("m.room.avatar"); + if (state?.Url is { } url) { + roomIcon = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, url); + Console.WriteLine($"Got avatar for room {RoomId}: {roomIcon} ({url})"); + } + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_FORBIDDEN" }) { + throw; } } - _semaphoreSlim.Release(); } } \ No newline at end of file |