diff options
Diffstat (limited to 'MatrixRoomUtils.Web')
27 files changed, 745 insertions, 184 deletions
diff --git a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs index b6836c8..2c3b9ce 100644 --- a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs +++ b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs @@ -43,11 +43,11 @@ public class MRUStorageWrapper(TieredStorageService storageService, HomeserverPr return null; } - return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); + return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); } public async Task<AuthenticatedHomeserverGeneric?> GetSession(UserAuth userAuth) { - return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken); + return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy); } public async Task<AuthenticatedHomeserverGeneric?> GetCurrentSessionOrNavigate() { diff --git a/MatrixRoomUtils.Web/Classes/RoomInfo.cs b/MatrixRoomUtils.Web/Classes/RoomInfo.cs index a2fa6f5..37973a0 100644 --- a/MatrixRoomUtils.Web/Classes/RoomInfo.cs +++ b/MatrixRoomUtils.Web/Classes/RoomInfo.cs @@ -25,7 +25,18 @@ public class RoomInfo : NotifyPropertyChanged { @event.RawContent = await Room.GetStateAsync<JsonObject>(type, stateKey); } catch (MatrixException e) { - if (e is { ErrorCode: "M_NOT_FOUND" }) @event.RawContent = default!; + if (e is { ErrorCode: "M_NOT_FOUND" }) { + if (type == "m.room.name") + @event = new() { + Type = type, + StateKey = stateKey, + TypedContent = new RoomNameEventContent() { + Name = await Room.GetNameOrFallbackAsync() + } + }; + else + @event.RawContent = default!; + } else throw; } @@ -60,7 +71,7 @@ public class RoomInfo : NotifyPropertyChanged { private string? _roomName; private RoomCreateEventContent? _creationEventContent; private string? _roomCreator; - + public string? DefaultRoomName { get; set; } public RoomInfo() { diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj index 543f8db..5d5568f 100644 --- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj +++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj @@ -4,9 +4,11 @@ <TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> - <UseBlazorWebAssembly>true</UseBlazorWebAssembly> <LinkIncremental>true</LinkIncremental> <LangVersion>preview</LangVersion> + <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> + + <UseBlazorWebAssembly>true</UseBlazorWebAssembly> </PropertyGroup> <ItemGroup> diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor index 4b2dc4f..94c51b2 100644 --- a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor @@ -16,7 +16,7 @@ else { <summary>Room List</summary> @foreach (var room in Rooms) { <a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@room.Replace('.', '~')"> - <RoomListItem RoomId="@room"></RoomListItem> + <RoomListItem RoomInfo="@(new RoomInfo() { Room = hs.GetRoom(room) })" LoadData="true"></RoomListItem> </a> } </details> @@ -37,10 +37,11 @@ else { @code { public List<string> Rooms { get; set; } = new(); + public AuthenticatedHomeserverGeneric? hs { get; set; } protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs == null) return; Rooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); Console.WriteLine("Fetched joined rooms!"); @@ -76,4 +77,4 @@ else { StateHasChanged(); } -} +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor index 59ce70f..c605e7a 100644 --- a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor @@ -1,20 +1,34 @@ @page "/HSAdmin" @using LibMatrix.Homeservers +@using ArcaneLibs.Extensions <h3>Homeserver Admininistration</h3> <hr/> -<h4>Synapse tools</h4> -<hr/> -<a href="/HSAdmin/RoomQuery">Query rooms</a> +@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.GetServerVersionAsync(); await base.OnInitializedAsync(); } - } \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor index 74dd651..804fde3 100644 --- a/MatrixRoomUtils.Web/Pages/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Index.razor @@ -3,6 +3,7 @@ @using LibMatrix @using LibMatrix.Homeservers @using ArcaneLibs.Extensions +@using MatrixRoomUtils.Web.Pages.Dev <PageTitle>Index</PageTitle> @@ -28,13 +29,18 @@ Small collection of tools to do not-so-everyday things. </p> <span style="display: inline-block; width: 128px;">@__auth.UserInfo.RoomCount rooms</span> - <span style="color: #888888">@__auth.ServerVersion.Server.Name @__auth.ServerVersion.Server.Version</span> + <a style="color: #888888" href="@("/ServerInfo/"+__auth.Homeserver.ServerName+"/")">@__auth.ServerVersion.Server.Name @__auth.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=@__auth.Homeserver.GetType().FullName</p> + <p>D=@__auth.Homeserver.WhoAmI.DeviceId</p> + <p>U=@__auth.Homeserver.WhoAmI.UserId</p> + } </td> <td> <p> @@ -51,10 +57,17 @@ Small collection of tools to do not-so-everyday things. @code { +#if DEBUG + bool DEBUG = true; +#else + 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(); @@ -69,7 +82,7 @@ Small collection of tools to do not-so-everyday things. UserInfo userInfo = new(); AuthenticatedHomeserverGeneric hs; try { - hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); + hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); } catch (MatrixException e) { if (e.ErrorCode == "M_UNKNOWN_TOKEN") { @@ -88,7 +101,8 @@ Small collection of tools to do not-so-everyday things. _auth.Add(new() { UserInfo = userInfo, UserAuth = token, - ServerVersion = await hs.GetServerVersionAsync() + ServerVersion = await hs.GetServerVersionAsync(), + Homeserver = hs }); // StateHasChanged(); }); @@ -105,7 +119,7 @@ Small collection of tools to do not-so-everyday things. private async Task RemoveUser(UserAuth auth, bool logout = false) { try { if (logout) { - await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken)).Logout(); + await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); } } catch (Exception e) { diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor index 1b466c9..c926a93 100644 --- a/MatrixRoomUtils.Web/Pages/LoginPage.razor +++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor @@ -7,15 +7,15 @@ <span> <span>@@</span><!-- - --><FancyTextBox @bind-Value="@newRecordInput.username"></FancyTextBox><!-- + --><FancyTextBox @bind-Value="@newRecordInput.Username"></FancyTextBox><!-- --><span>:</span><!-- - --><FancyTextBox @bind-Value="@newRecordInput.homeserver"></FancyTextBox> + --><FancyTextBox @bind-Value="@newRecordInput.Homeserver"></FancyTextBox> via - <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox> + <FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox> </span> <span style="display: block;"> <label>Password:</label> - <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox> + <FancyTextBox @bind-Value="@newRecordInput.Password" IsPassword="true"></FancyTextBox> </span> <button @onclick="AddRecord">Add account to queue</button> <br/> @@ -34,18 +34,18 @@ </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")"> + <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.homeserver"></FancyTextBox> + <FancyTextBox @bind-Value="@r.Username"></FancyTextBox> </td> <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.username"></FancyTextBox> + <FancyTextBox @bind-Value="@r.Homeserver"></FancyTextBox> </td> <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.password" IsPassword="true"></FancyTextBox> + <FancyTextBox @bind-Value="@r.Password" IsPassword="true"></FancyTextBox> </td> <td style="border-width: 1px;"> - <FancyTextBox @bind-Value="@r.proxy"></FancyTextBox> + <FancyTextBox @bind-Value="@r.Proxy"></FancyTextBox> </td> <td> <a role="button" @onclick="() => records.Remove(r)">Remove</a> @@ -59,21 +59,20 @@ <LogView></LogView> @code { - readonly List<(string homeserver, string username, string password, string? proxy)> records = new(); - (string homeserver, string username, string password, string? proxy) newRecordInput = ("", "", "", null); + readonly List<LoginStruct> records = new(); + private LoginStruct newRecordInput = new(); List<UserAuth>? LoggedInSessions { get; set; } = new(); async Task Login() { var loginTasks = records.Select(async record => { - var (homeserver, username, password, proxy) = record; - if (LoggedInSessions.Any(x => x.UserId == $"@{username}:{homeserver}" && x.Proxy == proxy)) return; + if (LoggedInSessions.Any(x => x.UserId == $"@{record.Username}:{record.Homeserver}" && x.Proxy == record.Proxy)) return; try { - var result = new UserAuth(await hsProvider.Login(homeserver, username, password, proxy)) { - Proxy = proxy + 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 {homeserver} as {username}!"); + Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); return; } Console.WriteLine($"Obtained access token for {result.UserId}!"); @@ -82,7 +81,7 @@ LoggedInSessions = await MRUStorage.GetAllTokens(); } catch (Exception e) { - Console.WriteLine($"Failed to login to {homeserver} as {username}!"); + Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); Console.WriteLine(e); } StateHasChanged(); @@ -104,14 +103,21 @@ if (parts.Length < 3) continue; string? via = parts.Length > 3 ? parts[3] : null; - records.Add((parts[0], parts[1], parts[2], via)); + 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 = ("", "", "", null); + 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; } + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor new file mode 100644 index 0000000..02dfe44 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor @@ -0,0 +1,113 @@ +@page "/UserRoomHistory/{UserId}" +@using LibMatrix.Homeservers +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.RoomTypes +@using ArcaneLibs.Extensions +<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) + } + }); + 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 index 5823757..08b21dd 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor @@ -88,7 +88,7 @@ <tr> <td>Room icon:</td> <td> - <img src="@hsResolver.ResolveMediaUri(Homeserver.ServerName, roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> + <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> diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor index fd32cb3..60f4f62 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor @@ -156,6 +156,7 @@ Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; } RenderContents |= queue.Count == 0; + if (queue.Count > 10) RenderContents = false; await Task.Delay(RenderContents ? 25 : 25); } else { diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor index 1f4a923..01bf555 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor @@ -47,10 +47,14 @@ 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 { - RoomMessageEventContent => typeof(TimelineMessageItem), + 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 new file mode 100644 index 0000000..5b3f1c1 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/ServerInfo.razor @@ -0,0 +1,235 @@ +@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.GetServerVersionAsync(); + 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/KnownHomeserverList.razor b/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor index 0ab0bd2..dbf2f5f 100644 --- a/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor +++ b/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor @@ -7,51 +7,43 @@ <hr/> @if (!IsFinished) { - <p>Loading... Please wait...</p> - <progress value="@QueryProgress.ProcessedRooms" max="@QueryProgress.TotalRooms"></progress> - <p>@QueryProgress.ProcessedRooms / @QueryProgress.TotalRooms</p> - @foreach (var (room, state) in QueryProgress.ProcessedUsers.Where(x => !x.Value.IsFinished).OrderByDescending(x => x.Value.Total).ToList()) { - @if (state.Blocked) { - <p>🔒 @room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p> - } - else if (state.Slowmode) { - <p>🐢 @room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p> - } - else { - <p>@room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p> - } - <progress value="@state.Processed" max="@state.Total"></progress> - } + <p> + <b>Loading...</b> + </p> } -else { - @foreach (var server in Homeservers.OrderByDescending(x => x.KnownUserCount).ThenBy(x => x.Server).ToList()) { - <p>@server.Server - @server.KnownUserCount</p> - } + +@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) { + <p>@homeserver - @members</p> } <hr/> @code { - List<HomeserverInfo> Homeservers = new(); + 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; } + // HomeserverInfoQueryProgress QueryProgress { get; set; } = new(); + AuthenticatedHomeserverGeneric? hs { get; set; } + protected override async Task OnInitializedAsync() { hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var sw = Stopwatch.StartNew(); - Homeservers = await GetHomeservers(progressCallback: async progress => { - if (sw.ElapsedMilliseconds > 1000) { - Console.WriteLine("Progress updated..."); - QueryProgress = progress; - StateHasChanged(); - Console.WriteLine("Progress rendered!"); - sw.Restart(); - await Task.Delay(100); - return true; + 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; } - Console.WriteLine($"Progress updated, but not rendering because only {sw.ElapsedMilliseconds}ms elapsed since last call..."); - return false; - }); + // 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(); @@ -59,64 +51,4 @@ else { await base.OnInitializedAsync(); } - private async Task<List<HomeserverInfo>> GetHomeservers(int memberLimit = 1000, Func<HomeserverInfoQueryProgress, Task<bool>>? progressCallback = null) { - HomeserverInfoQueryProgress progress = new(); - List<HomeserverInfo> homeServers = new(); - - var rooms = await hs.GetJoinedRooms(); - progress.TotalRooms = rooms.Count; - - var semaphore = new SemaphoreSlim(4); - var tasks = rooms.Select(async room => { - await semaphore.WaitAsync(); - progress.ProcessedUsers.Add(room, new HomeserverInfoQueryProgress.State()); - Console.WriteLine($"Fetching states for room ({rooms.IndexOf(room)}/{rooms.Count}) ({room.RoomId})"); - var states = room.GetMembersAsync(); - await foreach (var state in states) { - if (state.Type is not "m.room.member") continue; - progress.ProcessedUsers[room].Total++; - - if (homeServers.Any(x => x.Server == state.StateKey.Split(':')[1])) continue; - homeServers.Add(new HomeserverInfo { Server = state.StateKey.Split(':')[1] }); - Console.WriteLine($"Added new homeserver {state.StateKey.Split(':')[1]}"); - } - semaphore.Release(); - progress.ProcessedUsers[room].IsFinished = true; - progress.ProcessedRooms++; - if (progressCallback is not null) - await progressCallback.Invoke(progress); - }); - // var results = tasks.ToAsyncEnumerable(); - await Task.WhenAll(tasks); - - Console.WriteLine("Calculating member counts..."); - homeServers.ForEach(x => x.KnownUserCount = x.KnownUsers.Count); - Console.WriteLine(homeServers.First(x => x.Server == "rory.gay").ToJson()); - Console.WriteLine("Recalculated!"); - return homeServers; - } - - class HomeserverInfo { - public string Server { get; set; } - public int? KnownUserCount { get; set; } - public List<string> KnownUsers { get; } = new(); - } - - class HomeserverInfoQueryProgress { - public int ProcessedRooms { get; set; } - public int TotalRooms { get; set; } - public Dictionary<GenericRoom, State> ProcessedUsers { get; } = new(); - public List<HomeserverInfo> CurrentState { get; set; } = new(); - - public class State { - public int Processed { get; set; } - public int Total { get; set; } - public bool Blocked { get; set; } - public bool Slowmode { get; set; } - public float Progress => (float)Processed / Total; - public bool IsFinished { get; set; } - public Stopwatch Timing { get; } = Stopwatch.StartNew(); - } - } - -} +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor b/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor index 59ec79e..20aa639 100644 --- a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor +++ b/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor @@ -94,7 +94,7 @@ lines.ToList().ForEach(async line => { await sem.WaitAsync(); try { - homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).client); + homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client); StateHasChanged(); } catch (Exception e) { diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor index 92e1bc2..f753f18 100644 --- a/MatrixRoomUtils.Web/Pages/User/DMManager.razor +++ b/MatrixRoomUtils.Web/Pages/User/DMManager.razor @@ -40,7 +40,14 @@ var roomList = new List<RoomInfo>(); DMRooms.Add(await Homeserver.GetProfileAsync(userId), roomList); foreach (var room in rooms) { - roomList.Add(new RoomInfo() { Room = Homeserver.GetRoom(room) }); + 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) + } + }); } StateHasChanged(); } @@ -51,6 +58,4 @@ await base.OnInitializedAsync(); } - - } \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/MxcImage.razor b/MatrixRoomUtils.Web/Shared/MxcImage.razor new file mode 100644 index 0000000..f54c1f8 --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/MxcImage.razor @@ -0,0 +1,43 @@ +<img class="@Class" src="@ResolvedUri" style="@Style"/> +@code { + private string _mxcUri; + private string _style; + private string _resolvedUri; + + [Parameter] + public string MxcUri { + get => _mxcUri ?? ""; + set { + _mxcUri = value; + UriHasChanged(value); + } + } + + [Parameter] + public string Style { + get => _style; + set { + _style = value; + StateHasChanged(); + } + } + [Parameter] + public RemoteHomeserver? Homeserver { get; set; } + + private string ResolvedUri { + get => _resolvedUri; + set { + _resolvedUri = value; + StateHasChanged(); + } + } + + private async Task UriHasChanged(string value) { + var uri = value[5..].Split('/'); + ResolvedUri = (Homeserver ?? await hsProvider.GetRemoteHomeserver(uri[0])).ResolveMediaUri(value); + } + + [Parameter] + public string Class { get; set; } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor index a24ccad..970526d 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor @@ -9,14 +9,14 @@ @if (RoomInfo is not null) { <div class="roomListItem @(HasDangerousRoomVersion ? "dangerousRoomVersion" : HasOldRoomVersion ? "oldRoomVersion" : "")" id="@RoomInfo.Room.RoomId"> @if (OwnMemberState != null) { - <img class="avatar32 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "") @(ChildContent is not null ? "vcenter" : "")" - src="@(hs.ResolveMediaUri(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl) ?? "/icon-192.png")"/> + <MxcImage Class="@("avatar32" + (OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? " highlightChange" : "") + (ChildContent is not null ? " vcenter" : ""))" + MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> <span class="centerVertical border75 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "")"> @(OwnMemberState?.DisplayName ?? GlobalProfile?.DisplayName ?? "Loading...") </span> <span class="centerVertical noLeftPadding">-></span> } - <img class="avatar32" src="@hs?.ResolveMediaUri(RoomInfo.RoomIcon)" style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> + <MxcImage Class="avatar32" MxcUri="RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> <div class="inlineBlock"> <span class="centerVertical">@RoomInfo.RoomName</span> @if (ChildContent is not null) { @@ -36,7 +36,13 @@ else { public RenderFragment? ChildContent { get; set; } [Parameter] - public RoomInfo? RoomInfo { get; set; } + public RoomInfo? RoomInfo { + get => _roomInfo; + set { + _roomInfo = value; + OnParametersSetAsync(); + } + } [Parameter] public bool ShowOwnProfile { get; set; } = false; @@ -48,42 +54,52 @@ else { public UserProfileResponse? GlobalProfile { get; set; } [Parameter] - public bool LoadData { get; set; } = false; + public bool LoadData { + get => _loadData; + set { + _loadData = value; + OnParametersSetAsync(); + } + } private bool HasOldRoomVersion { get; set; } = false; private bool HasDangerousRoomVersion { get; set; } = false; private static SemaphoreSlim _semaphoreSlim = new(8); + private RoomInfo? _roomInfo; + private bool _loadData = false; private static AuthenticatedHomeserverGeneric? hs { get; set; } protected override async Task OnParametersSetAsync() { - RoomInfo.PropertyChanged += (_, a) => { - Console.WriteLine(a.PropertyName); - StateHasChanged(); - }; - - if (LoadData) { - try { - await RoomInfo.GetStateEvent("m.room.create"); - if (ShowOwnProfile) - OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.WhoAmI.UserId)).TypedContent as RoomMemberEventContent; - - await RoomInfo.GetStateEvent("m.room.name"); - await RoomInfo.GetStateEvent("m.room.avatar"); - } - catch (MatrixException e) { - if (e.ErrorCode == "M_FORBIDDEN") { - LoadData = false; - RoomInfo.StateEvents.Add(new() { - Type = "m.room.create", - TypedContent = new RoomCreateEventContent() { RoomVersion = "0" } - }); - RoomInfo.StateEvents.Add(new() { - Type = "m.room.name", - TypedContent = new RoomNameEventContent() { - Name = "M_FORBIDDEN: Are you a member of this room? " + RoomInfo.Room.RoomId - } - }); + if (RoomInfo != null) { + RoomInfo.PropertyChanged += (_, a) => { + Console.WriteLine(a.PropertyName); + StateHasChanged(); + }; + + if (LoadData) { + try { + await RoomInfo.GetStateEvent("m.room.create"); + if (ShowOwnProfile) + OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.WhoAmI.UserId)).TypedContent as RoomMemberEventContent; + + await RoomInfo.GetStateEvent("m.room.name"); + await RoomInfo.GetStateEvent("m.room.avatar"); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_FORBIDDEN") { + LoadData = false; + RoomInfo.StateEvents.Add(new() { + Type = "m.room.create", + TypedContent = new RoomCreateEventContent() { RoomVersion = "0" } + }); + RoomInfo.StateEvents.Add(new() { + Type = "m.room.name", + TypedContent = new RoomNameEventContent() { + Name = "M_FORBIDDEN: Are you a member of this room? " + RoomInfo.Room.RoomId + } + }); + } } } } @@ -170,5 +186,4 @@ else { // } // } -} - +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor index 9efeaab..8d608e3 100644 --- a/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor @@ -1,5 +1,7 @@ @using LibMatrix +@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Homeservers +@using LibMatrix.Responses <h3>BaseTimelineItem</h3> @code { @@ -13,4 +15,19 @@ [Parameter] public AuthenticatedHomeserverGeneric Homeserver { get; set; } -} + public List<StateEventResponse> EventsBefore => Events.TakeWhile(e => e.EventId != Event.EventId).ToList(); + + public List<StateEventResponse> MatchingEventsBefore => EventsBefore.Where(x => x.Type == Event.Type && x.StateKey == Event.StateKey).ToList(); + + public StateEventResponse? PreviousState => MatchingEventsBefore.LastOrDefault(); + + public RoomMemberEventContent? CurrentSenderMemberEventContent => EventsBefore.LastOrDefault(x => x.Type == "m.room.member" && x.StateKey == Event.Sender)? + .TypedContent as RoomMemberEventContent; + + public UserProfileResponse CurrentSenderProfile => new() { DisplayName = CurrentSenderMemberEventContent?.DisplayName, AvatarUrl = CurrentSenderMemberEventContent?.AvatarUrl }; + + public bool HasPreviousMessage => EventsBefore.Last() is { Type: "m.room.message" } response && response.Sender == Event.Sender; + + + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor new file mode 100644 index 0000000..1213432 --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor @@ -0,0 +1,27 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@inherits BaseTimelineItem + +@if (currentEventContent is not null) { + @if (previousEventContent is null) { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room alias to "@currentEventContent.Alias"</i> + } + else { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room name from "@previousEventContent.Alias" to "@currentEventContent.Alias"</i> + } +} +else { + <details> + <summary>Unknown event @Event.Type (@Event.StateKey)</summary> + <pre> + @Event.ToJson() + </pre> + </details> +} + +@code { + private RoomCanonicalAliasEventContent? previousEventContent => PreviousState?.TypedContent as RoomCanonicalAliasEventContent; + + private RoomCanonicalAliasEventContent? currentEventContent => Event.TypedContent as RoomCanonicalAliasEventContent; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor new file mode 100644 index 0000000..172a38c --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor @@ -0,0 +1,27 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@inherits BaseTimelineItem + +@if (currentEventContent is not null) { + @if (previousEventContent is null) { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the history visibility to "@currentEventContent.HistoryVisibility"</i> + } + else { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the history visibility from "@previousEventContent.HistoryVisibility" to "@currentEventContent.HistoryVisibility"</i> + } +} +else { + <details> + <summary>Unknown event @Event.Type (@Event.StateKey)</summary> + <pre> + @Event.ToJson() + </pre> + </details> +} + +@code { + private RoomHistoryVisibilityEventContent? previousEventContent => PreviousState?.TypedContent as RoomHistoryVisibilityEventContent; + + private RoomHistoryVisibilityEventContent? currentEventContent => Event.TypedContent as RoomHistoryVisibilityEventContent; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor index ed4dceb..3b18b95 100644 --- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor @@ -15,7 +15,12 @@ <i>@Event.StateKey changed their display name to @(roomMemberData.DisplayName ?? Event.Sender)</i> break; case "join": - <i><InlineUserItem User="@(new UserProfileResponse())" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> joined</i> + @if (prevRoomMemberData is null) { + <i><InlineUserItem User="@(new UserProfileResponse() { DisplayName = roomMemberData.DisplayName, AvatarUrl = roomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> joined</i> + } + else { + <i><InlineUserItem User="@(new UserProfileResponse() { DisplayName = prevRoomMemberData.DisplayName, AvatarUrl = prevRoomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed their profile to <InlineUserItem User="@(new UserProfileResponse() { DisplayName = roomMemberData.DisplayName, AvatarUrl = roomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem></i> + } break; case "leave": <i>@Event.StateKey left</i> @@ -43,5 +48,6 @@ else { @code { private RoomMemberEventContent? roomMemberData => Event.TypedContent as RoomMemberEventContent; + private RoomMemberEventContent? prevRoomMemberData => PreviousState?.TypedContent as RoomMemberEventContent; } diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor index 8073406..81956b0 100644 --- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor @@ -1,10 +1,34 @@ @using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec @inherits BaseTimelineItem -<pre> - @Event.RawContent?.ToJson(indent: false) -</pre> +<span> + @if (!HasPreviousMessage) { + <span><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem>:</span><br/> + } + @switch (currentEventContent.MessageType) { + case "m.text": { + @foreach (var line in currentEventContent.Body.Split('\n')) { + <span>@line</span><br/> + } + break; + } + case "m.image": { + <i>@currentEventContent.Body</i><br/> + <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)"> + break; + } + default: { + <pre> + @Event.RawContent?.ToJson(indent: false) + </pre> + break; + } + } +</span> @code { + private RoomMessageEventContent? previousEventContent => PreviousState?.TypedContent as RoomMessageEventContent; -} + private RoomMessageEventContent? currentEventContent => Event.TypedContent as RoomMessageEventContent; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor index 2d05151..f3e6c7e 100644 --- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor @@ -2,11 +2,11 @@ @using LibMatrix.EventTypes.Spec.State @inherits BaseTimelineItem -<p> +<i> @Event.Sender created the room with room version @CreationEventContent.RoomVersion - @(CreationEventContent.Federate ?? false ? "and" : "without") federating with other servers.<br/> + @(CreationEventContent.Federate ?? true ? "and" : "without") federating with other servers.<br/> This room is of type @(CreationEventContent.Type ?? "Untyped room (usually a chat room)") -</p> +</i> <pre> @Event.RawContent?.ToJson(indent: false) </pre> diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor new file mode 100644 index 0000000..eeec3de --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor @@ -0,0 +1,27 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@inherits BaseTimelineItem + +@if (currentEventContent is not null) { + @if (previousEventContent is null) { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room name to "@currentEventContent.Name"</i> + } + else { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room name from "@previousEventContent.Name" to "@currentEventContent.Name"</i> + } +} +else { + <details> + <summary>Unknown event @Event.Type (@Event.StateKey)</summary> + <pre> + @Event.ToJson() + </pre> + </details> +} + +@code { + private RoomNameEventContent? previousEventContent => PreviousState?.TypedContent as RoomNameEventContent; + + private RoomNameEventContent? currentEventContent => Event.TypedContent as RoomNameEventContent; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor new file mode 100644 index 0000000..7ef17a8 --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor @@ -0,0 +1,37 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@inherits BaseTimelineItem + +@if (currentEventContent is not null) { + @if (previousEventContent is null) { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room topic to</i><br/> + <pre> + @currentEventContent.Topic + </pre> + } + else { + <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room topic from</i><br/> + <pre> + @previousEventContent.Topic + </pre><br/> + <i>to</i><br/> + <pre> + @currentEventContent.Topic + </pre> + } +} +else { + <details> + <summary>Unknown event @Event.Type (@Event.StateKey)</summary> + <pre> + @Event.ToJson() + </pre> + </details> +} + +@code { + private RoomTopicEventContent? previousEventContent => PreviousState?.TypedContent as RoomTopicEventContent; + + private RoomTopicEventContent? currentEventContent => Event.TypedContent as RoomTopicEventContent; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor index 1ab530d..4f05b30 100644 --- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor +++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor @@ -6,7 +6,7 @@ <summary> <i style="color: red;">Unknown event type: <pre style="display: inline;">@Event.Type</pre></i> </summary> - <pre>@Event.ToJson()</pre> + <pre>@Event.ToJson(ignoreNull: true)</pre> </details> </div> diff --git a/MatrixRoomUtils.Web/_Imports.razor b/MatrixRoomUtils.Web/_Imports.razor index 91b69fb..0d07b3a 100644 --- a/MatrixRoomUtils.Web/_Imports.razor +++ b/MatrixRoomUtils.Web/_Imports.razor @@ -11,8 +11,8 @@ @using MatrixRoomUtils.Web @using MatrixRoomUtils.Web.Classes @using MatrixRoomUtils.Web.Shared - @using ArcaneLibs.Blazor.Components +@using LibMatrix.Homeservers @inject NavigationManager NavigationManager @inject MRUStorageWrapper MRUStorage |