diff options
15 files changed, 323 insertions, 121 deletions
diff --git a/.gitignore b/.gitignore index 1821c7e..984ec54 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ MatrixRoomUtils.Bot/bot_data/ appsettings.Local*.json nixpkgs/ *.DotSettings.user +*.patch test.tsv test-proxy.tsv diff --git a/LibMatrix b/LibMatrix -Subproject 37b97d65c0a5262539a5de560e911048166b8bb +Subproject 896ee7f099f817e8cc9aba96a9db00fcce67163 diff --git a/MatrixUtils.Abstractions/RoomInfo.cs b/MatrixUtils.Abstractions/RoomInfo.cs index 5c258a4..aff0e25 100644 --- a/MatrixUtils.Abstractions/RoomInfo.cs +++ b/MatrixUtils.Abstractions/RoomInfo.cs @@ -27,6 +27,7 @@ public class RoomInfo : NotifyPropertyChanged { public readonly GenericRoom Room; public ObservableCollection<StateEventResponse?> StateEvents { get; private set; } = new(); + public ObservableCollection<StateEventResponse?> Timeline { get; private set; } = new(); private static ConcurrentBag<AuthenticatedHomeserverGeneric> homeserversWithoutEventFormatSupport = new(); private static SvgIdenticonGenerator identiconGenerator = new(); diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index 53c056a..c2732de 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -46,5 +46,9 @@ <ItemGroup> <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> </ItemGroup> + + <ItemGroup> + <Folder Include="Pages\Tools\Moderation\Draupnir\" /> + </ItemGroup> </Project> diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor new file mode 100644 index 0000000..845f30d --- /dev/null +++ b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor @@ -0,0 +1,15 @@ +@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext +@* user header and room list *@ +@foreach (var room in Data.SyncWrapper.Rooms) { + <LinkButton OnClick="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")"> + @room.RoomName + </LinkButton> + <br/> +} + +@code { + + [Parameter] + public ClientContext Data { get; set; } = null!; + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor new file mode 100644 index 0000000..1100c98 --- /dev/null +++ b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor @@ -0,0 +1,35 @@ +@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext; +@using System.Collections.ObjectModel + +@foreach (var ctx in Data) { + <pre> + @ctx.Homeserver.UserId - @ctx.SyncWrapper.Status + </pre> +} + +@code { + + [Parameter] + public ObservableCollection<ClientContext> Data { get; set; } = null!; + + protected override void OnInitialized() { + Data.CollectionChanged += (_, e) => { + foreach (var item in e.NewItems?.Cast<ClientContext>() ?? []) { + item.SyncWrapper.PropertyChanged += (_, pe) => { + if (pe.PropertyName == nameof(item.SyncWrapper.Status)) + StateHasChanged(); + }; + } + + StateHasChanged(); + }; + + Data.ToList().ForEach(ctx => { + ctx.SyncWrapper.PropertyChanged += (_, pe) => { + if (pe.PropertyName == nameof(ctx.SyncWrapper.Status)) + StateHasChanged(); + }; + }); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs new file mode 100644 index 0000000..16051b8 --- /dev/null +++ b/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs @@ -0,0 +1,41 @@ +using System.Collections.ObjectModel; +using ArcaneLibs; +using LibMatrix; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using MatrixUtils.Abstractions; + +namespace MatrixUtils.Web.Pages.Client.ClientComponents; + +public class ClientSyncWrapper(AuthenticatedHomeserverGeneric homeserver) : NotifyPropertyChanged { + private SyncHelper _syncHelper = new SyncHelper(homeserver) { + MinimumDelay = TimeSpan.FromMilliseconds(2000), + IsInitialSync = false + }; + private string _status = "Loading..."; + + public ObservableCollection<StateEvent> AccountData { get; set; } = new(); + public ObservableCollection<RoomInfo> Rooms { get; set; } = new(); + + public string Status { + get => _status; + set => SetField(ref _status, value); + } + + public async Task Start() { + Task.Yield(); + var resp = _syncHelper.EnumerateSyncAsync(); + Status = $"[{DateTime.Now:s}] Syncing..."; + await foreach (var response in resp) { + Task.Yield(); + Status = $"[{DateTime.Now:s}] {response.Rooms?.Join?.Count ?? 0 + response.Rooms?.Invite?.Count ?? 0 + response.Rooms?.Leave?.Count ?? 0} rooms, {response.AccountData?.Events?.Count ?? 0} account data, {response.ToDevice?.Events?.Count ?? 0} to-device, {response.DeviceLists?.Changed?.Count ?? 0} device lists, {response.Presence?.Events?.Count ?? 0} presence updates"; + await HandleSyncResponse(response); + await Task.Yield(); + } + } + + private async Task HandleSyncResponse(SyncResponse resp) { + + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor b/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor new file mode 100644 index 0000000..b4a81f7 --- /dev/null +++ b/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor @@ -0,0 +1,31 @@ +@using Index = MatrixUtils.Web.Pages.Client.Index +@using MatrixUtils.Web.Pages.Client.ClientComponents + +<div class="container-fluid"> + <div class="row"> + <div class="col-3"> + <ClientRoomList Data="@Data"/> + </div> + <div class="col-6"> + @if (Data.SelectedRoom != null) { + <Index.RoomHeader Data="@Data"/> + <Index.RoomTimeline Data="@Data"/> + } + else { + <p>No room selected</p> + } + </div> + @if (Data.SelectedRoom != null) { + <div class="col-3"> + <Index.UserList Data="@Data"/> + </div> + } + </div> +</div> + +@code { + + [Parameter] + public Index.ClientContext Data { get; set; } = null!; + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Client/Index.razor b/MatrixUtils.Web/Pages/Client/Index.razor new file mode 100644 index 0000000..2a9a327 --- /dev/null +++ b/MatrixUtils.Web/Pages/Client/Index.razor @@ -0,0 +1,72 @@ +@page "/Client" +@using LibMatrix +@using MatrixUtils.Abstractions +@using MatrixUtils.Web.Pages.Client.ClientComponents +@using System.Collections.ObjectModel + +<h3>Client</h3> + + +@foreach (var client in Clients) { + <LinkButton Color="@(SelectedClient == client ? "#ff00ff" : "")" OnClick="@(async () => SelectedClient = client)"> + @client.Homeserver.WhoAmI.UserId + </LinkButton> +} +<ClientStatusList Data="@Clients"></ClientStatusList> + + +@* @foreach (var client in Clients) { *@ +@* <div class="card"> *@ +@* <span>@client.Homeserver.UserId - @client.SyncWrapper.Status</span> *@ +@* </div> *@ +@* } *@ + +@if (SelectedClient != null) { + <div class="card"> + <MatrixClient Data="@SelectedClient"/> + </div> +} + +@code { + + private static readonly ObservableCollection<ClientContext> Clients = []; + private static ClientContext _selectedClient; + + private ClientContext SelectedClient { + get => _selectedClient; + set { + _selectedClient = value; + StateHasChanged(); + } + } + + protected override async Task OnInitializedAsync() { + var tokens = await RMUStorage.GetAllTokens(); + var tasks = tokens.Select(async token => { + try { + var cc = new ClientContext() { + Homeserver = await RMUStorage.GetSession(token) + }; + cc.SyncWrapper = new ClientSyncWrapper(cc.Homeserver); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + cc.SyncWrapper.Start(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + Clients.Add(cc); + StateHasChanged(); + } + catch { } + }).ToList(); + await Task.WhenAll(tasks); + } + + public class ClientContext { + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + public ClientSyncWrapper SyncWrapper { get; set; } + + public RoomInfo? SelectedRoom { get; set; } + } + +} + diff --git a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor index afd58af..11df261 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor @@ -82,18 +82,19 @@ @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> + @* <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> *@ <p> @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) { - <span>@res.CanonicalAlias (@res.RoomId)</span> + <span>@res.CanonicalAlias - @res.RoomId (@res.Name)</span> <br/> } else { - <span>@res.RoomId</span> + <span>@res.RoomId (@res.Name)</span> <br/> } @if (!string.IsNullOrWhiteSpace(res.Creator)) { - <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span> + @* <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span> *@ + <span>Created by @res.Creator</span> <br/> } </p> @@ -178,6 +179,7 @@ } } + StateHasChanged(); } private readonly Dictionary<string, string> validOrderBy = new() { diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor index 7a3b27b..6483f01 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor @@ -1,15 +1,16 @@ @using MatrixUtils.Abstractions <div class="spaceListItem" style="@(SelectedSpace == Space ? "background-color: #FFFFFF33;" : "")" onclick="@SelectSpace"> - @if (IsSpaceOpened()) { - <span onclick="@ToggleSpace">▼ </span> - } - else { - <span onclick="@ToggleSpace">▶ </span> - } - - <MxcImage Circular="true" Height="32" Width="32" Homeserver="Space.Room.Homeserver" MxcUri="@Space.RoomIcon"></MxcImage> - <span class="spaceNameEllipsis">@Space.RoomName</span> + <div class="spaceListItemContainer"> + @if (IsSpaceOpened()) { + <span onclick="@ToggleSpace">▼ </span> + } + else { + <span onclick="@ToggleSpace">▶ </span> + } + <MxcImage Circular="true" Height="32" Width="32" Homeserver="Space.Room.Homeserver" MxcUri="@Space.RoomIcon"></MxcImage> + <span class="spaceNameEllipsis">@Space.RoomName</span> + </div> @if (IsSpaceOpened()) { <span>meow</span> } @@ -19,10 +20,10 @@ [Parameter] public RoomInfo Space { get; set; } - + [Parameter] public RoomInfo SelectedSpace { get; set; } - + [Parameter] public EventCallback<RoomInfo> SelectedSpaceChanged { get; set; } @@ -52,5 +53,4 @@ return OpenedSpaces.Contains(Space); } -} - +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css index a88975b..d6e413f 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css @@ -11,7 +11,17 @@ .spaceListItem { display: block; width: 100%; - height: 50px; + height: 3em; +} + +.spaceListItemContainer { + display: flex; + align-items: center; + vertical-align: center; + justify-content: space-between; + padding: 0 16px; + width: 100%; + height: 100%; } .spaceListItem > img { diff --git a/MatrixUtils.Web/Pages/Tools/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/UserTrace.razor index 4ad9874..95fe02b 100644 --- a/MatrixUtils.Web/Pages/Tools/UserTrace.razor +++ b/MatrixUtils.Web/Pages/Tools/UserTrace.razor @@ -5,6 +5,7 @@ @using LibMatrix @using System.Collections.Frozen @using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Filters @using MatrixUtils.Abstractions <h3>User Trace</h3> <hr/> @@ -17,7 +18,7 @@ <details> <summary>Rooms to be searched (@rooms.Count)</summary> @foreach (var room in rooms) { - <span>@room.Room.RoomId</span> + <span>@room.RoomId</span> <br/> } </details> @@ -48,8 +49,11 @@ } @code { + private ObservableCollection<string> log { get; set; } = new(); - List<RoomInfo> rooms { get; set; } = new(); + + // List<RoomInfo> rooms { get; set; } = new(); + List<GenericRoom> rooms { get; set; } = []; Dictionary<string, List<Match>> matches = new(); private string UserIdString { @@ -63,46 +67,58 @@ log.CollectionChanged += (sender, args) => StateHasChanged(); var hs = await RMUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - var baseRooms = new List<GenericRoom>(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); - if (session is not null) { - baseRooms.AddRange(await session.GetJoinedRooms()); - var sessionRooms = (await session.GetJoinedRooms()).Where(x => !rooms.Any(y => y.Room.RoomId == x.RoomId)).ToList(); - StateHasChanged(); - log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + // var sessions = await RMUStorage.GetAllTokens(); + // var baseRooms = new List<GenericRoom>(); + // foreach (var userAuth in sessions) { + // var session = await RMUStorage.GetSession(userAuth); + // if (session is not null) { + // baseRooms.AddRange(await session.GetJoinedRooms()); + // var sessionRooms = (await session.GetJoinedRooms()).Where(x => !rooms.Any(y => y.Room.RoomId == x.RoomId)).ToList(); + // StateHasChanged(); + // log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + // } + // } + // + // log.Add("Done fetching rooms!"); + // + // baseRooms = baseRooms.DistinctBy(x => x.RoomId).ToList(); + // + // // rooms.CollectionChanged += (sender, args) => StateHasChanged(); + // var tasks = baseRooms.Select(async newRoom => { + // bool success = false; + // while (!success) + // try { + // var state = await newRoom.GetFullStateAsListAsync(); + // var newRoomInfo = new RoomInfo(newRoom, state); + // rooms.Add(newRoomInfo); + // log.Add($"Got {newRoomInfo.StateEvents.Count} events for {newRoomInfo.RoomName}"); + // success = true; + // } + // catch (MatrixException e) { + // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); + // throw; + // } + // catch (HttpRequestException e) { + // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); + // } + // }); + // await Task.WhenAll(tasks); + // + // log.Add($"Done fetching members!"); + // + // UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + + foreach (var session in await RMUStorage.GetAllTokens()) { + var _hs = await RMUStorage.GetSession(session); + if (_hs is not null) { + rooms.AddRange(await _hs.GetJoinedRooms()); + log.Add($"Got {rooms.Count} rooms after adding {_hs.UserId}"); } } - log.Add("Done fetching rooms!"); - - baseRooms = baseRooms.DistinctBy(x => x.RoomId).ToList(); - - // rooms.CollectionChanged += (sender, args) => StateHasChanged(); - var tasks = baseRooms.Select(async newRoom => { - bool success = false; - while (!success) - try { - var state = await newRoom.GetFullStateAsListAsync(); - var newRoomInfo = new RoomInfo(newRoom, state); - rooms.Add(newRoomInfo); - log.Add($"Got {newRoomInfo.StateEvents.Count} events for {newRoomInfo.RoomName}"); - success = true; - } - catch (MatrixException e) { - log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); - throw; - } - catch (HttpRequestException e) { - log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); - } - }); - await Task.WhenAll(tasks); - - log.Add($"Done fetching members!"); - - UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + //get distinct rooms evenly distributed per session, accounting for count per session + rooms = rooms.OrderBy(x => rooms.Count(y => y.Homeserver == x.Homeserver)).DistinctBy(x => x.RoomId).ToList(); + log.Add($"Got {rooms.Count} rooms"); StateHasChanged(); Console.WriteLine("Rerendered!"); @@ -113,18 +129,25 @@ foreach (var userId in UserIDs) { matches.Add(userId, new List<Match>()); - foreach (var room in rooms) { - var state = room.StateEvents.Where(x => x!.Type == RoomMemberEventContent.EventId).ToList(); - if (state!.Any(x => x.StateKey == userId)) { - matches[userId].Add(new() { - Event = state.First(x => x.StateKey == userId), - Room = room.Room, - RoomName = room.RoomName ?? "No name" - }); - } + // foreach (var room in rooms) { + // var state = room.StateEvents.Where(x => x!.Type == RoomMemberEventContent.EventId).ToList(); + // if (state!.Any(x => x.StateKey == userId)) { + // matches[userId].Add(new() { + // Event = state.First(x => x.StateKey == userId), + // Room = room.Room, + // RoomName = room.RoomName ?? "No name" + // }); + // } + // } + + log.Add($"Searching for {userId}..."); + await foreach (var match in GetMatches(userId)) { + matches[userId].Add(match); } } + log.Add("Done!"); + StateHasChanged(); return ""; @@ -135,8 +158,8 @@ private async Task DoImportFromRoomId() { try { if (ImportFromRoomId is null) return; - var room = rooms.FirstOrDefault(x => x.Room.RoomId == ImportFromRoomId); - UserIdString = string.Join("\n", (await room.Room.GetMembersListAsync()).Select(x => x.StateKey)); + var room = rooms.FirstOrDefault(x => x.RoomId == ImportFromRoomId); + UserIdString = string.Join("\n", (await room.GetMembersListAsync()).Select(x => x.StateKey)); } catch (Exception e) { Console.WriteLine(e); @@ -152,4 +175,24 @@ public string RoomName { get; set; } } + private async IAsyncEnumerable<Match> GetMatches(string userId) { + var results = rooms.Select(async room => { + var state = await room.GetStateEventOrNullAsync(room.RoomId, userId); + if (state is not null) { + return new Match { + Room = room, + Event = state, + RoomName = await room.GetNameOrFallbackAsync() + }; + } + + return null; + }).ToAsyncEnumerable(); + await foreach (var result in results) { + if (result is not null) { + yield return result; + } + } + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor deleted file mode 100644 index 5197a6f..0000000 --- a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor +++ /dev/null @@ -1,38 +0,0 @@ -@* Source: https://whuysentruit.medium.com/blazor-wasm-pwa-adding-a-new-update-available-notification-d9f65c4ad13 *@ -@inject IJSRuntime _jsRuntime - -@if (_newVersionAvailable) -{ - <button type="button" class="btn btn-warning shadow floating-update-button" onclick="window.location.reload()"> - A new version of the application is available. Click here to reload. - </button> -} - -@code { - - private bool _newVersionAvailable = false; - - protected override async Task OnInitializedAsync() - { - await RegisterForUpdateAvailableNotification(); - } - - private async Task RegisterForUpdateAvailableNotification() - { - await _jsRuntime.InvokeAsync<object>( - identifier: "registerForUpdateAvailableNotification", - DotNetObjectReference.Create(this), - nameof(OnUpdateAvailable)); - } - - [JSInvokable(nameof(OnUpdateAvailable))] - public Task OnUpdateAvailable() - { - _newVersionAvailable = true; - - StateHasChanged(); - - return Task.CompletedTask; - } - -} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css deleted file mode 100644 index 32bff09..0000000 --- a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css +++ /dev/null @@ -1,15 +0,0 @@ -.floating-update-button { - position: fixed; - - right: 2rem; - bottom: 2rem; - - padding: 1rem 1.5rem; - - animation: fadein 2s ease-out; -} - -@keyframes fadein { - from { right: -100%; } - to { right: 2rem; } -} \ No newline at end of file |