From 89315fa530e1f21e2e50d94f955693b9413c98fe Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Wed, 25 Oct 2023 14:00:02 +0200 Subject: New things --- MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor | 55 ++++- MatrixRoomUtils.Web/Pages/Rooms/Index.razor | 2 +- .../Pages/Tools/CopyPowerlevel.razor | 84 +++++++ MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor | 110 ++++++++++ MatrixRoomUtils.Web/Pages/User/DMManager.razor | 56 +++++ MatrixRoomUtils.Web/Pages/User/DMSpace.razor | 86 ++++++++ .../Pages/User/DMSpaceStages/DMSpaceStage0.razor | 11 + .../Pages/User/DMSpaceStages/DMSpaceStage1.razor | 128 +++++++++++ .../Pages/User/DMSpaceStages/DMSpaceStage2.razor | 241 +++++++++++++++++++++ .../Pages/User/DMSpaceStages/DMSpaceStage3.razor | 189 ++++++++++++++++ MatrixRoomUtils.Web/Pages/User/Manage.razor | 11 +- 11 files changed, 960 insertions(+), 13 deletions(-) create mode 100644 MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor create mode 100644 MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMManager.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMSpace.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor create mode 100644 MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor (limited to 'MatrixRoomUtils.Web/Pages') diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor b/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor index 9b0f61c..a1e928f 100644 --- a/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor +++ b/MatrixRoomUtils.Web/Pages/Dev/DevOptions.razor @@ -1,5 +1,8 @@ @page "/Dev/Options" @using ArcaneLibs.Extensions +@using System.Text.Unicode +@using System.Text +@using System.Text.Json @inject NavigationManager NavigationManager @inject ILocalStorageService LocalStorage @@ -8,23 +11,61 @@

Rory&::MatrixUtils - Developer options


-
-
-
+

+ Import local storage: + +

+

+ Export local storage: + +

+ +@if (userSettings is not null) { + + +
+ + +
+ + +
+}
@code { - MRUStorageWrapper.Settings settings { get; set; } = new(); + private MRUStorageWrapper.Settings? userSettings { get; set; } protected override async Task OnInitializedAsync() { - settings = await TieredStorage.DataStorageProvider.LoadObjectAsync("mru.settings"); + // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync("mru.settings"); + await base.OnInitializedAsync(); } private async Task LogStuff() { await Task.Delay(100); - Console.WriteLine($"Settings: {settings.ToJson()}"); - await TieredStorage.DataStorageProvider.SaveObjectAsync("mru.settings", settings); + Console.WriteLine($"Settings: {userSettings.ToJson()}"); + await TieredStorage.DataStorageProvider.SaveObjectAsync("mru.settings", userSettings); + } + + private async Task ExportLocalStorage() { + var keys = await TieredStorage.DataStorageProvider.GetAllKeysAsync(); + var data = new Dictionary(); + foreach (var key in keys) { + data.Add(key, await TieredStorage.DataStorageProvider.LoadObjectAsync(key)); + } + var dataUri = "data:application/json;base64,"; + dataUri += Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data))); + await JSRuntime.InvokeVoidAsync("window.open", dataUri, "_blank"); + } + + private async Task ImportLocalStorage(InputFileChangeEventArgs obj) { + if (obj.FileCount != 1) return; + var data = await JsonSerializer.DeserializeAsync>(obj.File.OpenReadStream()); + foreach (var (key, value) in data) { + await TieredStorage.DataStorageProvider.SaveObjectAsync(key, value); + } + NavigationManager.NavigateTo(NavigationManager.Uri, true, true); } } diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor index 4d98402..516e9ca 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor @@ -22,7 +22,7 @@ @code { private ObservableCollection Rooms { get; } = new(); - private ProfileResponseEventContent GlobalProfile { get; set; } + private UserProfileResponse GlobalProfile { get; set; } private AuthenticatedHomeserverGeneric? Homeserver { get; set; } diff --git a/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor b/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor new file mode 100644 index 0000000..aaeb5a3 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/Tools/CopyPowerlevel.razor @@ -0,0 +1,84 @@ +@page "/CopyPowerlevel" +@using System.Diagnostics +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +

Copy powerlevel

+
+ +

Users:

+@foreach (var hs in hss) { +

@hs.WhoAmI.UserId

+} + +
+Execute +
+@foreach (var line in Enumerable.Reverse(log)) { +

@line

+} + +@code { + private List log { get; set; } = new(); + List hss { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var sessions = await MRUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await MRUStorage.GetSession(userAuth); + if (session is not null) { + hss.Add(session); + StateHasChanged(); + } + } + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + foreach (var hs in hss) { + var rooms = await hs.GetJoinedRooms(); + var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + } + } + + private async Task Execute(AuthenticatedHomeserverGeneric hs, GenericRoom room) { + try { + var pls = await room.GetPowerLevelsAsync(); + // if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.UsersDefault) return "I am default PL in " + room.RoomId; + if (!pls.UserHasPermission(hs.WhoAmI.UserId, RoomPowerLevelEventContent.EventId)) return "I do not have permission to send PL in " + room.RoomId; + foreach (var ahs in hss) { + if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.GetUserPowerLevel(ahs.WhoAmI.UserId)) { + log.Add("I am same PL in " + room.RoomId); + continue; + } + + pls.SetUserPowerLevel(ahs.WhoAmI.UserId, pls.GetUserPowerLevel(hs.WhoAmI.UserId)); + await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls); + log.Add($"Updated powerlevel of {room.RoomId} to {pls.GetUserPowerLevel(ahs.WhoAmI.UserId)}"); + } + + } + catch (MatrixException e) { + return $"Failed to update PLs in {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to update PLs in {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor b/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor new file mode 100644 index 0000000..bcf8095 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/Tools/MassJoinRoom.razor @@ -0,0 +1,110 @@ +@page "/MassRoomJoin" +@using System.Diagnostics +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +

Mass join room

+
+

Room:

+ + +

Users:

+@foreach (var hs in hss) { +

@hs.WhoAmI.UserId

+} + +
+Execute +
+@foreach (var line in Enumerable.Reverse(log)) { +

@line

+} + +@code { + private List log { get; set; } = new(); + List hss { get; set; } = new(); + string roomId { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var sessions = await MRUStorage.GetAllTokens(); + foreach (var userAuth in sessions) { + var session = await MRUStorage.GetSession(userAuth); + if (session is not null) { + hss.Add(session); + StateHasChanged(); + } + } + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task Execute() { + // foreach (var hs in hss) { + // var rooms = await hs.GetJoinedRooms(); + var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + tasks = hss.Select(ExecuteJoin).ToAsyncEnumerable(); + await foreach (var a in tasks) { + if (!string.IsNullOrWhiteSpace(a)) { + log.Add(a); + StateHasChanged(); + } + } + // } + } + + private async Task ExecuteInvite(AuthenticatedHomeserverGeneric hs) { + var room = hs.GetRoom(roomId); + try { + try { + var joinRule = await room.GetJoinRuleAsync(); + if (joinRule.JoinRule == "public") return "Room is public, no invite needed"; + } + catch { } + var pls = await room.GetPowerLevelsAsync(); + if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + room.RoomId; + await room.InviteUsersAsync(hss.Select(x => x.WhoAmI.UserId).ToList()); + log.Add($"Invited to {room.RoomId} to {pls.GetUserPowerLevel(hs.WhoAmI.UserId)}"); + } + catch (MatrixException e) { + return $"Failed to invite in {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to invite in {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + + private async Task ExecuteJoin(AuthenticatedHomeserverGeneric hs) { + var room = hs.GetRoom(roomId); + try { + try { + var mse = await room.GetStateOrNullAsync(RoomMemberEventContent.EventId, hs.WhoAmI.UserId); + if (mse?.Membership == "join") return $"User {hs.WhoAmI.UserId} already in room"; + } + catch { } + await room.JoinAsync(); + } + catch (MatrixException e) { + return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; + } + catch (Exception e) { + return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}"; + } + StateHasChanged(); + return ""; + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor new file mode 100644 index 0000000..04ff6e5 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMManager.razor @@ -0,0 +1,56 @@ +@page "/User/DirectMessages" +@using LibMatrix.Homeservers +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +

Direct Messages

+
+ +@foreach (var (targetUser, rooms) in DMRooms) { +
+ + @foreach (var room in rooms) { + + } +
+} + +@code { + private string? _status; + private AuthenticatedHomeserverGeneric? HomeServer { get; set; } + private Dictionary> DMRooms { get; set; } = new(); + + public string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + protected override async Task OnInitializedAsync() { + HomeServer = await MRUStorage.GetCurrentSessionOrNavigate(); + if (HomeServer is null) return; + Status = "Loading global profile..."; + if (HomeServer.WhoAmI?.UserId is null) return; + + Status = "Loading DM list from account data..."; + var dms = await HomeServer.GetAccountDataAsync>>("m.direct"); + DMRooms.Clear(); + foreach (var (userId, rooms) in dms) { + var roomList = new List(); + DMRooms.Add(await HomeServer.GetProfileAsync(userId), roomList); + foreach (var room in rooms) { + roomList.Add(new RoomInfo() { Room = HomeServer.GetRoom(room) }); + } + StateHasChanged(); + } + + StateHasChanged(); + Status = null; + + await base.OnInitializedAsync(); + } + + + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpace.razor b/MatrixRoomUtils.Web/Pages/User/DMSpace.razor new file mode 100644 index 0000000..21cc264 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMSpace.razor @@ -0,0 +1,86 @@ +@page "/User/DMSpace/Setup" +@using LibMatrix.Homeservers +@using LibMatrix +@using MatrixRoomUtils.LibDMSpace +@using MatrixRoomUtils.LibDMSpace.StateEvents +@using MatrixRoomUtils.Web.Pages.User.DMSpaceStages +

DM Space Management

+
+ + @switch (Stage) { + case -1: +

Initialising...

+ break; + case 0: + + break; + case 1: + + break; + case 2: + + break; + case 3: + + break; + default: +

Stage is unknown value: @Stage!

+ break; + } +
+ +@code { + private int _stage = -1; + + [Parameter, SupplyParameterFromQuery(Name = "stage")] + public int Stage { + get => _stage; + set { + _stage = value; + Console.WriteLine($"Stage is now {value}"); + StateHasChanged(); + } + } + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } + + [Parameter] + public DMSpace? DmSpace { get; set; } + + protected override async Task OnInitializedAsync() { + if (NavigationManager.Uri.Contains("?stage=")) { + NavigationManager.NavigateTo("/User/DMSpace", true); + } + DmSpace = this; + Homeserver ??= await MRUStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; + try { + DmSpaceConfiguration = await Homeserver.GetAccountDataAsync("gay.rory.dm_space"); + var room = Homeserver.GetRoom(DmSpaceConfiguration.DMSpaceId); + await room.GetStateAsync(DMSpaceInfo.EventId); + Stage = 1; + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") { + Stage = 0; + DmSpaceConfiguration = new(); + } + else throw; + } + catch (Exception e) { + throw; + } + finally { + StateHasChanged(); + } + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() { + StateHasChanged(); + await base.OnParametersSetAsync(); + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor new file mode 100644 index 0000000..49fd5b4 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor @@ -0,0 +1,11 @@ + + Welcome to the DM Space tool! + +

This wizard will help you set up a DM space.

+

This is useful for eg. sharing DM rooms across multiple accounts.

+
+Get started + +@code { + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor new file mode 100644 index 0000000..3642da5 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor @@ -0,0 +1,128 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.Responses +@using MatrixRoomUtils.LibDMSpace +@using MatrixRoomUtils.LibDMSpace.StateEvents +@using Microsoft.Extensions.Primitives +@using ArcaneLibs.Extensions + + DM Space setup tool - stage 1: Configure space + +

You will need a space to use for DM rooms.

+@if (DmSpace is not null) { +

+ Selected space: + + @foreach (var (id, name) in spaces) { + + } + +

+

+ + Create sub-spaces per user +

+} +else { + Error: DmSpaceConfiguration is null! +} + +
+Next + +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private Dictionary spaces = new() { { "", "New space" } }; + private string? _status; + + [CascadingParameter] + public DMSpace? DmSpace { get; set; } + + public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphoreSlim = new(1, 1); + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphoreSlim.WaitAsync(); + DmSpace.DmSpaceConfiguration ??= new(); + if (spaces.Count == 1) { + Status = "Looking for spaces..."; + var userRoomsEnum = DmSpace.Homeserver.GetJoinedRoomsByType("m.space"); + List userRooms = new(); + await foreach (var room in userRoomsEnum) { + userRooms.Add(room); + } + var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); + await foreach(var room in roomChecks) + if(room.HasValue) + spaces.TryAdd(room.Value.id, room.Value.name); + + Status = "Done!"; + } + _semaphoreSlim.Release(); + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { + var crr = CreateRoomRequest.CreatePrivate(DmSpace.Homeserver, "Direct Messages"); + crr._creationContentBaseType.Type = "m.space"; + DmSpace.DmSpaceConfiguration.DMSpaceId = (await DmSpace.Homeserver.CreateRoom(crr)).RoomId; + } + await DmSpace.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, DmSpace.DmSpaceConfiguration); + var space = DmSpace.Homeserver.GetRoom(DmSpace.DmSpaceConfiguration.DMSpaceId); + await space.SendStateEventAsync(DMSpaceInfo.EventId, DmSpaceInfo); + + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); + } + + public async Task<(string id, string name)?> GetFeasibleSpaces(GenericRoom room) { + try { + var pls = await room.GetPowerLevelsAsync(); + if (!pls.UserHasPermission(DmSpace.Homeserver.WhoAmI.UserId, "m.space.child")) { + Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); + return null; + } + var roomName = await room.GetNameAsync(); + Status = $"Found viable space: {roomName}"; + if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { + try { + var dsi = await DmSpace.Homeserver.GetRoom(room.RoomId).GetStateOrNullAsync(DMSpaceInfo.EventId) ?? new DMSpaceInfo(); + if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is not null && dsi is not null) { + DmSpace.DmSpaceConfiguration.DMSpaceId = room.RoomId; + DmSpaceInfo = dsi; + } + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"{room.RoomId} is not a DM space."); + else throw; + } + } + return (room.RoomId, roomName); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); + else throw; + } + return null; + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor new file mode 100644 index 0000000..553f46d --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor @@ -0,0 +1,241 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixRoomUtils.LibDMSpace +@using MatrixRoomUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization + + DM Space setup tool - stage 2: Fix DM room attribution + +

This is just to make sure that your DMs are attributed to the right person!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (DmSpace is not null) { + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.Id)) { + + @foreach (var roomInfo in room) { + + Reassign + + } + } +} +else { + Error: DmSpaceConfiguration is null! +} + +
+Next + +@{ + var _offset = 0; +} +@foreach (var (room, usersList) in duplicateDmRooms) { + +

Found room assigned to multiple users:

+

Users:

+ @foreach (var userProfileResponse in usersList) { + + Assign to + + +
+ } +
+} + +@if (DmToReassign is not null) { + + + @foreach (var userProfileResponse in roomMembers[DmToReassign]) { + + Assign to + + +
+ } +
+} + +@code { + + private string newMxid { get; set; } = ""; + + private RoomInfo? DmToReassign { + get => _dmToReassign; + set { + _dmToReassign = value; + StateHasChanged(); + } + } + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + private RoomInfo? _dmToReassign; + + [CascadingParameter] + public DMSpace? DmSpace { get; set; } + + private Dictionary> dmRooms { get; set; } = new(); + private Dictionary> duplicateDmRooms { get; set; } = new(); + private Dictionary> roomMembers { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphore.WaitAsync(); + DmToReassign = null; + var hs = DmSpace.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Optimising DM list from account data..."; + var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); + foreach (var (user, rooms) in dms) { + for (var i = rooms.Count - 1; i >= 0; i--) { + var roomId = rooms[i]; + if (!joinedRooms.Contains(roomId)) + rooms.RemoveAt(i); + } + dms[user] = rooms.Distinct().ToList(); + } + dms.RemoveAll((x, y) => y is {Count: 0}); + await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); + dmRooms.Clear(); + + Status = "DM list optimised, fetching info..."; + var results = dms.Select(async x => { + var (userId, rooms) = x; + UserProfileWithId userProfile; + try { + var profile = await DmSpace.Homeserver.GetProfileAsync(userId); + userProfile = new() { + AvatarUrl = profile.AvatarUrl, + Id = userId, + DisplayName = profile.DisplayName + }; + } + catch { + userProfile = new() { + AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + DisplayName = userId, + Id = userId + }; + } + var roomList = new List(); + var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + await foreach (var result in tasks) + roomList.Add(result); + return (userProfile, roomList); + // StateHasChanged(); + }).ToAsyncEnumerable(); + await foreach (var res in results) { + dmRooms.Add(res.userProfile, res.roomList); + // Status = $"Listed {dmRooms.Count} users"; + } + _semaphore.Release(); + var duplicateDmRoomIds = new Dictionary>(); + foreach (var (user, rooms) in dmRooms) { + foreach (var roomInfo in rooms) { + if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) + duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); + duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); + } + } + duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); + foreach (var (roomId, users) in duplicateDmRoomIds) { + duplicateDmRooms.Add(dmRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); + } + + // StateHasChanged(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo() { + Room = room + }; + roomMembers[roomInfo] = new(); + roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + try { + roomInfo.RoomName = await room.GetNameAsync(); + } + catch { } + + var membersEnum = room.GetMembersAsync(); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join") + roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { + List displayNames = new List(); + foreach (var member in roomMembers[roomInfo]) + if (!string.IsNullOrWhiteSpace(member.DisplayName)) + displayNames.Add(member.DisplayName); + roomInfo.RoomName = string.Join(", ", displayNames); + } + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private async Task SetRoomAssignment(string roomId, string userId) { + var hs = DmSpace.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Updating DM list from account data..."; + + foreach (var (user, rooms) in dms) { + rooms.RemoveAll(x => x == roomId); + dms[user] = rooms.Distinct().ToList(); + } + if(!dms.ContainsKey(userId)) + dms.Add(userId, new()); + dms[userId].Add(roomId); + dms.RemoveAll((x, y) => y is {Count: 0}); + await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); + + duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); + StateHasChanged(); + if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); + } + + private class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor new file mode 100644 index 0000000..854b09c --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor @@ -0,0 +1,189 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixRoomUtils.LibDMSpace +@using MatrixRoomUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization + + DM Space setup tool - stage 3: Preview space layout + +

This gives you a preview of how your settings would impact layout!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (DmSpace is not null) { + @if (dmSpaceInfo is not null && dmSpaceRoomInfo is not null) { +

+ + Create sub-spaces per user +

+ @if (!dmSpaceInfo.LayerByUser) { + + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { + @foreach (var roomInfo in room) { +
+ +
+ } + } + } + else { + + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { +
+ +
+ @foreach (var roomInfo in room) { +
+ +
+ } + } + } + } + else { + Error: dmSpaceInfo is null! + } +} +else { + Error: DmSpaceConfiguration is null! +} + +
+Next + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace? DmSpace { get; set; } + + private Dictionary> dmRooms { get; set; } = new(); + private DMSpaceInfo? dmSpaceInfo { get; set; } + private RoomInfo? dmSpaceRoomInfo { get; set; } + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphore.WaitAsync(); + var hs = DmSpace.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); + dmSpaceRoomInfo = new() { + RoomName = await dmSpaceRoom.GetNameAsync(), + CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), + RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + Room = dmSpaceRoom + }; + dmSpaceInfo = await dmSpaceRoom.GetDmSpaceInfo(); + Status = "Loading DM list from account data..."; + var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); + dmRooms.Clear(); + + Status = "DM list optimised, fetching info..."; + var results = dms.Select(async x => { + var (userId, rooms) = x; + UserProfileWithId userProfile; + try { + var profile = await DmSpace.Homeserver.GetProfileAsync(userId); + userProfile = new() { + AvatarUrl = profile.AvatarUrl, + Id = userId, + DisplayName = profile.DisplayName + }; + } + catch { + userProfile = new() { + AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + DisplayName = userId, + Id = userId + }; + } + var roomList = new List(); + var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + await foreach (var result in tasks) + roomList.Add(result); + return (userProfile, roomList); + }).ToAsyncEnumerable(); + await foreach (var res in results) { + dmRooms.Add(new RoomInfo() { + Room = dmSpaceRoom, + RoomIcon = res.userProfile.AvatarUrl, + RoomName = res.userProfile.DisplayName, + CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() + }, res.roomList); + } + _semaphore.Release(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + var hs = DmSpace.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo() { + Room = room + }; + var roomMembers = new List(); + roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + try { + roomInfo.RoomName = await room.GetNameAsync(); + } + catch { } + + var membersEnum = room.GetMembersAsync(); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join") + roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { + List displayNames = new List(); + foreach (var member in roomMembers) + if (!string.IsNullOrWhiteSpace(member.DisplayName)) + displayNames.Add(member.DisplayName); + roomInfo.RoomName = string.Join(", ", displayNames); + } + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/Manage.razor b/MatrixRoomUtils.Web/Pages/User/Manage.razor index 25debf7..eac47e8 100644 --- a/MatrixRoomUtils.Web/Pages/User/Manage.razor +++ b/MatrixRoomUtils.Web/Pages/User/Manage.razor @@ -1,8 +1,9 @@ -@page "/User/Manage" +@page "/User/Profile" @using LibMatrix.Homeservers @using LibMatrix.EventTypes.Spec.State @using ArcaneLibs.Extensions -

Manage user - @HomeServer?.WhoAmI?.UserId

+@using LibMatrix.Responses +

Manage Profile - @HomeServer?.WhoAmI?.UserId


@if (Profile is not null) { @@ -48,8 +49,8 @@ private string? _status = null; private AuthenticatedHomeserverGeneric? HomeServer { get; set; } - private ProfileResponseEventContent? Profile { get; set; } - private ProfileResponseEventContent? OldProfile { get; set; } + private UserProfileResponse? Profile { get; set; } + private UserProfileResponse? OldProfile { get; set; } private string? Status { get => _status; @@ -77,7 +78,7 @@ var roomNameTasks = RoomProfiles.Keys.Select(x => HomeServer.GetRoom(x)).Select(async x => { var name = await x.GetNameAsync(); - return new KeyValuePair(x.RoomId, name); + return new KeyValuePair(x.RoomId, name); }).ToAsyncEnumerable(); await foreach (var (roomId, roomName) in roomNameTasks) { // Status = $"Got room name for {roomId}: {roomName}"; -- cgit 1.5.1