diff options
Diffstat (limited to '')
-rw-r--r-- | MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor | 97 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor (renamed from MatrixUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor) | 2 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Rooms/Index.razor | 80 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 1 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Rooms/Space.razor | 7 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Tools/RoomIntersections.razor | 199 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Tools/SessionCount.razor | 155 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Tools/SpaceDebug.razor | 22 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Tools/UserTrace.razor | 139 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/Tools/ViewAccountData.razor | 30 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/Profile.razor | 56 |
11 files changed, 721 insertions, 67 deletions
diff --git a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor new file mode 100644 index 0000000..fb4f9bf --- /dev/null +++ b/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor @@ -0,0 +1,97 @@ +@page "/Moderation/DraupnirProtectedRoomsEditor" +@using System.Text.Json.Serialization +@using MatrixUtils.Abstractions +@using System.Collections.Frozen +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.RoomTypes +<h3>Edit Draupnir protected rooms</h3> +<hr/> + +@if (data is not null) { + <div class="row"> + <div class="col-12"> + <h4>Current rooms</h4> + <ul> + @foreach (var room in data.Rooms) { + <li>@room</li> + } + </ul> + <hr/> + <h4>Tickyboxes</h4> + <table class="table"> + <thead> + <tr> + <th></th> @* Checkbox column *@ + <th>Kick?</th> @* PL > kick *@ + <th>Ban?</th> @* PL > ban *@ + <th>ACL?</th> @* PL > m.room.server_acls event *@ + <th>Room ID</th> + <th>Room name</th> + </tr> + </thead> + <tbody> + @foreach (var room in Rooms.OrderBy(x => x.RoomName)) { + <tr> + <td> + <input type="checkbox" @bind="room.IsProtected"/> + </td> + <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")</td> + <td>@room.Room.RoomId</td> + <td>@room.RoomName</td> + </tr> + } + </tbody> + </table> + </div> + </div> +} +<br/> +<LinkButton OnClick="@Apply">Apply</LinkButton> + + +@code { + private DraupnirProtectedRoomsData data { get; set; } = new(); + private List<EditorRoomInfo> Rooms { get; set; } = new(); + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms"); + StateHasChanged(); + foreach (var room in await hs.GetJoinedRooms()) { + var plTask = room.GetPowerLevelsAsync(); + var roomNameTask = room.GetNameOrFallbackAsync(); + var EditorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = data.Rooms.Contains(room.RoomId), + RoomName = await roomNameTask, + PowerLevels = await plTask + }; + + Rooms.Add(EditorRoomInfo); + StateHasChanged(); + } + } + + private class DraupnirProtectedRoomsData { + [JsonPropertyName("rooms")] + public List<string> Rooms { get; set; } = new(); + } + + private class EditorRoomInfo { + public GenericRoom Room { get; set; } + public bool IsProtected { get; set; } + public string RoomName { get; set; } + public RoomPowerLevelEventContent PowerLevels { get; set; } + } + + private async Task Apply() { + Console.WriteLine(string.Join('\n', Rooms.Where(x=>x.IsProtected).Select(x=>x.Room.RoomId))); + data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList(); + await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor index 5ba83e4..e4eea83 100644 --- a/MatrixUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor +++ b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor @@ -1,4 +1,4 @@ -@page "/UserRoomHistory/{UserId}" +@page "/Moderation/UserRoomHistory/{UserId}" @using LibMatrix.Homeservers @using LibMatrix @using LibMatrix.EventTypes.Spec.State diff --git a/MatrixUtils.Web/Pages/Rooms/Index.razor b/MatrixUtils.Web/Pages/Rooms/Index.razor index 170f489..c3deb40 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixUtils.Web/Pages/Rooms/Index.razor @@ -6,6 +6,7 @@ @using System.Collections.ObjectModel @using System.Diagnostics @using ArcaneLibs.Extensions +@using LibMatrix.Utilities @using MatrixUtils.Abstractions @inject ILogger<Index> logger <h3>Room list</h3> @@ -18,8 +19,9 @@ <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList> @code { - + private ObservableCollection<RoomInfo> _rooms = new(); + private ObservableCollection<RoomInfo> Rooms { get => _rooms; set => _rooms = value; @@ -29,39 +31,7 @@ private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - private static SyncFilter filter = new() { - AccountData = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Presence = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Room = new SyncFilter.RoomFilter { - AccountData = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Ephemeral = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - State = new SyncFilter.RoomFilter.StateFilter { - Types = new List<string> { - "m.room.create", - "m.room.name", - "m.room.avatar", - "org.matrix.mjolnir.shortcode", - "m.room.power_levels", - } - }, - Timeline = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - } - } - }; + // private static SyncFilter filter = // private static SyncFilter profileUpdateFilter = new() { // AccountData = new SyncFilter.EventFilter { @@ -105,22 +75,23 @@ // SemaphoreSlim _semaphore = new(160, 160); GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); - Rooms = new ObservableCollection<RoomInfo>(rooms.Select(x => new RoomInfo() { Room = x })); - foreach (var stateType in filter.Room?.State?.Types ?? []) { - var tasks = Rooms.Select(async room => { - try { - - await room.GetStateEvent(stateType); - } - catch (Exception e) { - Console.WriteLine($"Failed to get state event {stateType} for room {room.Room.RoomId}: {e}"); - } - }); - await Task.WhenAll(tasks); - Status = $"Fetched all {stateType} events..."; - // StateHasChanged(); - } + var filter = await Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetBasicRoomInfo); + var filterData = await Homeserver.GetFilterAsync(filter); + Rooms = new ObservableCollection<RoomInfo>(rooms.Select(x => new RoomInfo() { Room = x })); + // foreach (var stateType in filterData.Room?.State?.Types ?? []) { + // var tasks = Rooms.Select(async room => { + // try { + // await room.GetStateEvent(stateType); + // } + // catch (Exception e) { + // Console.WriteLine($"Failed to get state event {stateType} for room {room.Room.RoomId}: {e}"); + // } + // }); + // await Task.WhenAll(tasks); + // Status = $"Fetched all {stateType} events..."; + // // StateHasChanged(); + // } RenderContents = true; Status = "Initial fetch done! Starting initial sync..."; @@ -128,7 +99,7 @@ await Task.Delay(1000); syncHelper = new SyncHelper(Homeserver, logger) { Timeout = 30000, - Filter = filter, + FilterId = filter, MinimumDelay = TimeSpan.FromMilliseconds(5000) }; // profileSyncHelper = new SyncHelper(Homeserver, logger) { @@ -189,8 +160,9 @@ await Task.Delay(100); } + Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; + Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; RenderContents |= queue.Count == 0; await Task.Delay(Rooms.Count); @@ -224,7 +196,6 @@ } private Queue<KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>> queue = new(); - private async Task RunSyncLoop(SyncHelper syncHelper) { // Status = "Initial syncing..."; @@ -235,6 +206,8 @@ Console.WriteLine("trying sync"); if (sync is null) continue; + var filter = await Homeserver.GetFilterAsync(syncHelper.FilterId); + Status = $"Got sync with {sync.Rooms?.Join?.Count ?? 0} room updates, next batch: {sync.NextBatch}!"; if (sync?.Rooms?.Join != null) foreach (var joinedRoom in sync.Rooms.Join) @@ -248,7 +221,8 @@ queue.Enqueue(joinedRoom); } - if (sync.Rooms.Leave is {Count: > 0}) + + if (sync.Rooms.Leave is { Count: > 0 }) foreach (var leftRoom in sync.Rooms.Leave) if (Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key)); diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index bfc0375..b7ebae2 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -250,6 +250,7 @@ else { private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent); + CurrentlyEditingEvent = null; await LoadStatesAsync(); } diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor index 2dd84a1..01ab1c4 100644 --- a/MatrixUtils.Web/Pages/Rooms/Space.razor +++ b/MatrixUtils.Web/Pages/Rooms/Space.razor @@ -93,8 +93,11 @@ } private async Task JoinAllRooms() { - List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList(); - await Task.WhenAll(tasks); + // List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList(); + // await Task.WhenAll(tasks); + foreach (var room in Rooms) { + await room.JoinAsync(ServersInSpace.ToArray()); + } } } diff --git a/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor new file mode 100644 index 0000000..395f84c --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor @@ -0,0 +1,199 @@ +@page "/Tools/RoomIntersections" +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes.Spec.State +<h3>Room intersections</h3> +<hr/> + +<p>Set A: </p> +<InputText @bind-Value="@ImportSetASpaceId"></InputText> +<LinkButton OnClick="@(() => AppendSet(ImportSetASpaceId, RoomsA))">Append Set A</LinkButton> + +<p>Set B: </p> +<InputText @bind-Value="@ImportSetBSpaceId"></InputText> +<LinkButton OnClick="@(() => AppendSet(ImportSetBSpaceId, RoomsB))">Append Set B</LinkButton> +<br/> +<LinkButton OnClick="@Execute">Execute</LinkButton> +<br/> + +<details> + <summary>Results</summary> + <pre> + @{ + var userColWidth = matches.Count == 0 ? 0 : matches.Keys.Max(x => x.Length); + } + <table border="1"> + @foreach (var (userId, sets) in matches) { + <tr> + <td>@userId.PadRight(userColWidth + 5)</td> + <td>@sets.Item1[0].Room.RoomId</td> + <td>@((sets.Item1[0].Member.TypedContent as RoomMemberEventContent).Membership)</td> + <td>@(roomNames.ContainsKey(sets.Item1[0].Room) ? roomNames[sets.Item1[0].Room] : "")</td> + <td>@(roomAliasses.ContainsKey(sets.Item1[0].Room) ? roomAliasses[sets.Item1[0].Room] : "")</td> + <td>@sets.Item2[0].Room.RoomId</td> + <td>@((sets.Item2[0].Member.TypedContent as RoomMemberEventContent).Membership)</td> + <td>@(roomNames.ContainsKey(sets.Item2[0].Room) ? roomNames[sets.Item2[0].Room] : "")</td> + <td>@(roomAliasses.ContainsKey(sets.Item2[0].Room) ? roomAliasses[sets.Item2[0].Room] : "")</td> + </tr> + @for (int i = 1; i < Math.Max(sets.Item1.Count, sets.Item2.Count); i++) { + <tr> + <td/> + @if (sets.Item1.Count > i) { + <td>@sets.Item1[i].Room.RoomId</td> + <td>@((sets.Item1[i].Member.TypedContent as RoomMemberEventContent).Membership)</td> + <td>@(roomNames.ContainsKey(sets.Item1[i].Room) ? roomNames[sets.Item1[i].Room] : "")</td> + <td>@(roomAliasses.ContainsKey(sets.Item1[i].Room) ? roomAliasses[sets.Item1[i].Room] : "")</td> + } + else { + <td/> + <td/> + <td/> + <td/> + } + @if (sets.Item2.Count > i) { + <td>@sets.Item2[0].Room.RoomId</td> + <td>@((sets.Item2[i].Member.TypedContent as RoomMemberEventContent).Membership)</td> + <td>@(roomNames.ContainsKey(sets.Item2[i].Room) ? roomNames[sets.Item2[i].Room] : "")</td> + <td>@(roomAliasses.ContainsKey(sets.Item2[i].Room) ? roomAliasses[sets.Item2[i].Room] : "")</td> + } + else { + <td/> + <td/> + <td/> + <td/> + } + </tr> + } + } + + </table> + <br/> + </pre> +</details> + +<br/> +@foreach (var line in Log.Reverse()) { + <pre>@line</pre> +} + +@code { + private ObservableCollection<string> Log { get; set; } = new(); + List<GenericRoom> RoomsA { get; set; } = new(); + List<GenericRoom> RoomsB { get; set; } = new(); + + [Parameter, SupplyParameterFromQuery(Name = "a")] + public string ImportSetASpaceId { get; set; } = ""; + + [Parameter, SupplyParameterFromQuery(Name = "b")] + public string ImportSetBSpaceId { get; set; } = ""; + + Dictionary<string, Dictionary<GenericRoom, StateEventResponse>> roomMembers { get; set; } = new(); + + Dictionary<string, (List<Match>, List<Match>)> matches { get; set; } = new(); + + AuthenticatedHomeserverGeneric hs { get; set; } + + // private string RoomListAString { + // get => string.Join("\n", RoomIdsA); + // set => RoomIdsA = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + // } + // + // private string RoomListBString { + // get => string.Join("\n", RoomIdsB); + // set => RoomIdsB = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + // } + + // private List<string> RoomIdsA { get; set; } = new(); + // private List<string> RoomIdsB { get; set; } = new(); + + // room info + Dictionary<GenericRoom, string> roomNames { get; set; } = new(); + Dictionary<GenericRoom, string?> roomAliasses { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + Log.CollectionChanged += (sender, args) => StateHasChanged(); + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + // get all users which are in any room of both sets of rooms, and which rooms + var setAusers = new Dictionary<string, List<Match>>(); + var setBusers = new Dictionary<string, List<Match>>(); + + await Task.WhenAll(GetMembers(RoomsA, setAusers), GetMembers(RoomsB, setBusers)); + + Log.Add($"Got {setAusers.Count} users in set A"); + Log.Add($"Got {setBusers.Count} users in set B"); + Log.Add("Calculating intersections..."); + + // get all users which are in both sets of rooms + // var users = setAusers.Keys.Intersect(setBusers.Keys).ToList(); + // var groups = setAusers.IntersectBy(setBusers, (x,y) => x.Key).ToList(); + matches = setAusers.Keys.Intersect(setBusers.Keys).Select(x => (x, setAusers[x], setBusers[x])).ToDictionary(x => x.x, x => (x.Item2, x.Item3)); + + Log.Add($"Found {matches.Count} users in both sets of rooms"); + StateHasChanged(); + } + + public async Task GetMembers(List<GenericRoom> rooms, Dictionary<string, List<Match>> users) { + foreach (var room in rooms) { + Log.Add($"Getting members for {room.RoomId}"); + var members = await room.GetMembersListAsync(false); + foreach (var member in members) { + if (member.RawContent?["membership"]?.ToString() == "ban") continue; + if (member.RawContent?["membership"]?.ToString() == "invite") continue; + if (!users.ContainsKey(member.StateKey)) users[member.StateKey] = new(); + users[member.StateKey].Add(new() { + Room = room, + Member = member + }); + } + } + } + + public async Task AppendSet(string spaceId, List<GenericRoom> rooms) { + var space = hs.GetRoom(spaceId).AsSpace; + Log.Add($"Found space {spaceId}"); + var roomIdsEnum = space.GetChildrenAsync(true); + List<Task> tasks = new(); + await foreach (var room in roomIdsEnum) { + tasks.Add(loadRoomData(room, rooms)); + } + + await Task.WhenAll(tasks); + + async Task loadRoomData(GenericRoom room, List<GenericRoom> rooms) { + Log.Add($"Found room {room.RoomId}"); + try { + await room.GetPowerLevelsAsync(); + rooms.Add(room); + try { + roomAliasses[room] = (await room.GetCanonicalAliasAsync()).Alias; + } + catch { } + + try { + roomNames[room] = await room.GetNameOrFallbackAsync(); + } + catch { } + } + catch (MatrixException e) { + Log.Add($"Failed to get power levels for {room.RoomId}: {e.Message}"); + } + } + } + + public class Match { + public GenericRoom Room { get; set; } + public StateEventResponse Member { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/SessionCount.razor new file mode 100644 index 0000000..3b68bfa --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/SessionCount.razor @@ -0,0 +1,155 @@ +@page "/Tools/SessionCount" +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes.Spec.State +<h3>User Trace</h3> +<hr/> + +<p>Users: </p> +<InputTextArea @bind-Value="@UserIdString"></InputTextArea> +<br/> +<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton> + +<details> + <summary>Rooms to be searched (@rooms.Count)</summary> + @foreach (var room in rooms) { + <span>@room.RoomId</span> + <br/> + } +</details> +<br/> +<LinkButton OnClick="Execute">Execute</LinkButton> +<br/> + +<details> + <summary>Results</summary> + @foreach (var (userId, events) in matches) { + <h4>@userId</h4> + <ul> + @foreach (var eventResponse in events) { + <li>@eventResponse.Room.RoomId</li> + } + </ul> + } +</details> +<details> + <summary>Results text</summary> + @{ + var col1Width = matches.Keys.Max(x => x.Length); + } + <pre> + @foreach (var (userId, events) in matches) { + <p> + <span>@userId.PadRight(col1Width)</span> + @foreach (var @event in events) { + +} + </p> + } + </pre> +</details> + +<br/> +@foreach (var line in log.Reverse()) { + <pre>@line</pre> +} + +@code { + private ObservableCollection<string> log { get; set; } = new(); + List<AuthenticatedHomeserverGeneric> hss { get; set; } = new(); + ObservableCollection<GenericRoom> rooms { get; set; } = new(); + Dictionary<GenericRoom, FrozenSet<StateEventResponse>> roomMembers { get; set; } = new(); + Dictionary<string, List<Matches>> matches = new(); + + private string UserIdString { + get => string.Join("\n", UserIDs); + set => UserIDs = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + } + + private List<string> UserIDs { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + var sessions = await RMUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await RMUStorage.GetSession(userAuth); + if (session is not null) { + var sessionRooms = await session.GetJoinedRooms(); + foreach (var room in sessionRooms) { + rooms.Add(room); + } + + StateHasChanged(); + log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + } + } + + log.Add("Done fetching rooms!"); + + var distinctRooms = rooms.DistinctBy(x => x.RoomId).ToArray(); + Random.Shared.Shuffle(distinctRooms); + rooms = new ObservableCollection<GenericRoom>(distinctRooms); + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + + var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync(false))).ToAsyncEnumerable(); + + await foreach (var (room, state) in stateTasks) { + roomMembers.Add(room, state); + log.Add($"Got {state.Count} members for {room.RoomId}..."); + } + + log.Add($"Done fetching members!"); + + UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task<string> Execute() { + foreach (var userId in UserIDs) { + matches.Add(userId, new List<Matches>()); + foreach (var (room, events) in roomMembers) { + if (events.Any(x => x.Type == RoomMemberEventContent.EventId && x.StateKey == userId)) { + matches[userId].Add(new() { + Event = events.First(x => x.StateKey == userId && x.Type == RoomMemberEventContent.EventId), + Room = room, + }); + } + } + } + + return ""; + } + + public string? ImportFromRoomId { get; set; } + + private async Task DoImportFromRoomId() { + try { + if (ImportFromRoomId is null) return; + 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); + log.Add("Could not fetch members list!\n" + e.ToString()); + } + + StateHasChanged(); + } + + private class Matches { + public GenericRoom Room; + + public StateEventResponse Event; + // public + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor index 5d9b8eb..09e5b12 100644 --- a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor +++ b/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor @@ -1,6 +1,7 @@ @page "/Tools/SpaceDebug" @using LibMatrix.Filters @using LibMatrix.Helpers +@using LibMatrix.Utilities <h3>SpaceDebug</h3> <hr/> @@ -49,16 +50,17 @@ if (hs is null) return; var syncHelper = new SyncHelper(hs) { - Filter = new SyncFilter() { - Presence = new(0), - Room = new() { - AccountData = new(limit: 0), - Ephemeral = new(limit: 0), - State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }), - Timeline = new(limit: 0) - }, - AccountData = new(limit: 0) - } + // Filter = new SyncFilter() { + // Presence = new(0), + // Room = new() { + // AccountData = new(limit: 0), + // Ephemeral = new(limit: 0), + // State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }), + // Timeline = new(limit: 0) + // }, + // AccountData = new(limit: 0) + // } + NamedFilterName = CommonSyncFilters.GetSpaceRelations }; Status = "Syncing..."; diff --git a/MatrixUtils.Web/Pages/Tools/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/UserTrace.razor new file mode 100644 index 0000000..b3a7487 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/UserTrace.razor @@ -0,0 +1,139 @@ +@page "/Tools/UserTrace" +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes.Spec.State +<h3>User Trace</h3> +<hr/> + +<p>Users: </p> +<InputTextArea @bind-Value="@UserIdString"></InputTextArea> +<br/> +<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton> + +<details> + <summary>Rooms to be searched (@rooms.Count)</summary> + @foreach (var room in rooms) { + <span>@room.RoomId</span> + <br/> + } +</details> +<br/> +<LinkButton OnClick="Execute">Execute</LinkButton> +<br/> + +<details> + <summary>Results</summary> + @foreach (var (userId, events) in matches) { + <h4>@userId</h4> + <ul> + @foreach (var eventResponse in events) { + <li>@eventResponse.Room.RoomId</li> + } + </ul> + } +</details> + +<br/> +@foreach (var line in log.Reverse()) { + <pre>@line</pre> +} + +@code { + private ObservableCollection<string> log { get; set; } = new(); + List<AuthenticatedHomeserverGeneric> hss { get; set; } = new(); + ObservableCollection<GenericRoom> rooms { get; set; } = new(); + Dictionary<GenericRoom, FrozenSet<StateEventResponse>> roomMembers { get; set; } = new(); + Dictionary<string, List<Matches>> matches = new(); + + private string UserIdString { + get => string.Join("\n", UserIDs); + set => UserIDs = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + } + + private List<string> UserIDs { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + var sessions = await RMUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await RMUStorage.GetSession(userAuth); + if (session is not null) { + var sessionRooms = await session.GetJoinedRooms(); + foreach (var room in sessionRooms) { + rooms.Add(room); + } + + StateHasChanged(); + log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + } + } + + log.Add("Done fetching rooms!"); + + var distinctRooms = rooms.DistinctBy(x => x.RoomId).ToArray(); + Random.Shared.Shuffle(distinctRooms); + rooms = new ObservableCollection<GenericRoom>(distinctRooms); + rooms.CollectionChanged += (sender, args) => StateHasChanged(); + + var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync(false))).ToAsyncEnumerable(); + + await foreach (var (room, state) in stateTasks) { + roomMembers.Add(room, state); + log.Add($"Got {state.Count} members for {room.RoomId}..."); + } + + log.Add($"Done fetching members!"); + + UserIDs.RemoveAll(x=>sessions.Any(y=>y.UserId == x)); + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task<string> Execute() { + foreach (var userId in UserIDs) { + matches.Add(userId, new List<Matches>()); + foreach (var (room, events) in roomMembers) { + if (events.Any(x => x.Type == RoomMemberEventContent.EventId && x.StateKey == userId)) { + matches[userId].Add(new() { + Event = events.First(x => x.StateKey == userId && x.Type == RoomMemberEventContent.EventId), + Room = room, + + }); + } + } + } + + return ""; + } + + public string? ImportFromRoomId { get; set; } + + private async Task DoImportFromRoomId() { + try { + if (ImportFromRoomId is null) return; + 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); + log.Add("Could not fetch members list!\n" + e.ToString()); + } + + StateHasChanged(); + } + + private class Matches { + public GenericRoom Room; + public StateEventResponse Event; + // public + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor new file mode 100644 index 0000000..398c7ce --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor @@ -0,0 +1,30 @@ +@page "/Tools/ViewAccountData" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.Filters +@using LibMatrix.Helpers +@using LibMatrix.Utilities +<h3>View account data</h3> +<hr/> +<pre>@globalAccountData?.Events.ToJson(ignoreNull: true)</pre> +<br/> + +@foreach (var (key, value) in perRoomAccountData) { + <u>@key</u><br/><hr/> + <pre>@value?.Events.ToJson(ignoreNull: true)</pre> +} + +@code { + EventList? globalAccountData; + Dictionary<string, EventList?> perRoomAccountData = new(); + + protected override async Task OnInitializedAsync() { + var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + perRoomAccountData = await hs.EnumerateAccountDataPerRoom(); + globalAccountData = await hs.EnumerateAccountData(); + + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor index 8cffaab..deebdaf 100644 --- a/MatrixUtils.Web/Pages/User/Profile.razor +++ b/MatrixUtils.Web/Pages/User/Profile.razor @@ -2,7 +2,9 @@ @using LibMatrix.Homeservers @using LibMatrix.EventTypes.Spec.State @using ArcaneLibs.Extensions +@using LibMatrix @using LibMatrix.Responses +@using MatrixUtils.Abstractions <h3>Manage Profile - @Homeserver?.WhoAmI?.UserId</h3> <hr/> @@ -28,6 +30,35 @@ @* <details> *@ <h4>Room profiles<hr></h4> + @foreach (var room in Rooms) { + <details class="details-compact"> + <summary style="@(room.OwnMembership?.DisplayName == OldProfile.DisplayName && room.OwnMembership?.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")"> + <div style="display: inline-block; width: calc(100% - 50px); vertical-align: middle; margin-top: -8px; margin-bottom: -8px;"> + <CascadingValue Value="OldProfile"> + <RoomListItem ShowOwnProfile="true" RoomInfo="@room" OwnMemberState="@room.OwnMembership"></RoomListItem> + </CascadingValue> + </div> + </summary> + @if (room.OwnMembership is not null) { + <img src="@Homeserver.ResolveMediaUri(room.OwnMembership.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <div style="display: inline-block; vertical-align: middle;"> + <span>Display name: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox><br/> + <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox> + <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, room.Room.RoomId))"></InputFile><br/> + <LinkButton OnClick="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton> + </div> + <br/> + @if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> + } + } + else { + <p>Something went wrong, own membership is missing...</p> + } + </details> + <br/> + } + @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { <details class="details-compact"> <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> @@ -63,6 +94,7 @@ } } + private List<RoomInfo> Rooms { get; set; } = new(); private Dictionary<string, RoomMemberEventContent> RoomProfiles { get; set; } = new(); private Dictionary<string, string> RoomNames { get; set; } = new(); @@ -76,6 +108,28 @@ Status = "Loading room profiles..."; var roomProfiles = Homeserver.GetRoomProfilesAsync(); await foreach (var (roomId, roomProfile) in roomProfiles) { + var room = Homeserver.GetRoom(roomId); + var roomNameTask = room.GetNameOrFallbackAsync(); + var roomIconTask = room.GetAvatarUrlAsync(); + var roomInfo = new RoomInfo() { + Room = room, + OwnMembership = roomProfile + }; + try { + roomInfo.RoomIcon = (await roomIconTask).Url; + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; + } + + try { + roomInfo.RoomName = await roomNameTask; + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; + } + + Rooms.Add(roomInfo); // Status = $"Got profile for {roomId}..."; RoomProfiles[roomId] = roomProfile; //.DeepClone(); } @@ -87,7 +141,7 @@ var name = await x.GetNameOrFallbackAsync(); return new KeyValuePair<string, string?>(x.RoomId, name); }).ToAsyncEnumerable(); - + await foreach (var (roomId, roomName) in roomNameTasks) { // Status = $"Got room name for {roomId}: {roomName}"; RoomNames[roomId] = roomName; |