about summary refs log tree commit diff
path: root/MatrixUtils.LibDMSpace/DMSpaceRoom.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.LibDMSpace/DMSpaceRoom.cs')
-rw-r--r--MatrixUtils.LibDMSpace/DMSpaceRoom.cs192
1 files changed, 150 insertions, 42 deletions
diff --git a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs
index 3fb0ab6..e2c8192 100644
--- a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs
+++ b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs
@@ -1,6 +1,10 @@
+using System.Net;
 using ArcaneLibs.Extensions;
 using LibMatrix;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
 using LibMatrix.Homeservers;
+using LibMatrix.Responses;
 using LibMatrix.RoomTypes;
 using MatrixUtils.LibDMSpace.StateEvents;
 
@@ -9,75 +13,179 @@ namespace MatrixUtils.LibDMSpace;
 public class DMSpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : SpaceRoom(homeserver, roomId) {
     private readonly GenericRoom _room;
 
-    public async Task<DMSpaceInfo?> GetDmSpaceInfo() {
+    // ReSharper disable once InconsistentNaming
+    public async Task<DMSpaceInfo?> GetDMSpaceInfo() {
         return await GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId);
     }
 
-    public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) {
-        var rooms = new List<GenericRoom>();
+    public async Task<Dictionary<string, List<string>>?> ExportNativeDMs() {
         var state = GetFullStateAsync();
-        await foreach (var stateEvent in state) {
-            if (stateEvent!.Type != "m.space.child") continue;
-            if (stateEvent.RawContent!.ToJson() != "{}" || includeRemoved)
-                yield return homeserver.GetRoom(stateEvent.StateKey);
-        }
-    }
-
-    public async Task<EventIdResponse> AddChildAsync(GenericRoom room) {
-        var members = room.GetMembersEnumerableAsync(true);
-        Dictionary<string, int> memberCountByHs = new();
-        await foreach (var member in members) {
-            var server = member.StateKey.Split(':')[1];
-            if (memberCountByHs.ContainsKey(server)) memberCountByHs[server]++;
-            else memberCountByHs[server] = 1;
-        }
+        var mdirect = new Dictionary<string, List<string>>();
+        await foreach (var stateEvent in state) { }
 
-        var resp = await SendStateEventAsync("m.space.child", room.RoomId, new {
-            via = memberCountByHs
-                .OrderByDescending(x => x.Value)
-                .Select(x => x.Key)
-                .Take(10)
-        });
-        return resp;
+        return mdirect;
     }
 
     public async Task ImportNativeDMs() {
-        var dmSpaceInfo = await GetDmSpaceInfo();
+        var dmSpaceInfo = await GetDMSpaceInfo();
         if (dmSpaceInfo is null) throw new NullReferenceException("DM Space is not configured!");
         if (dmSpaceInfo.LayerByUser)
             await ImportNativeDMsIntoLayers();
         else await ImportNativeDMsWithoutLayers();
     }
 
-    #region Import Native DMs
+    public async Task<List<StateEventResponse>> GetAllActiveLayersAsync() {
+        var state = await GetFullStateAsListAsync();
+        return state.Where(x => x.Type == DMSpaceChildLayer.EventId && x.RawContent.ContainsKey("space_id")).ToList();
+    }
+
+#region Import Native DMs
 
     private async Task ImportNativeDMsWithoutLayers() {
         var mdirect = await homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct");
         foreach (var (userId, dmRooms) in mdirect) {
             foreach (var roomid in dmRooms) {
                 var dri = new DMRoomInfo() {
-                    RemoteUsers = new() {
-                        userId
-                    }
+                    AttributedUser = userId
                 };
-                // Add all DM room members
-                var members = homeserver.GetRoom(roomid).GetMembersEnumerableAsync();
-                await foreach (var member in members)
-                    if (member.StateKey != userId)
-                        dri.RemoteUsers.Add(member.StateKey);
-                // Remove members of DM space
-                members = GetMembersEnumerableAsync();
-                await foreach (var member in members)
-                    if (dri.RemoteUsers.Contains(member.StateKey))
-                        dri.RemoteUsers.Remove(member.StateKey);
                 await SendStateEventAsync(DMRoomInfo.EventId, roomid, dri);
+                await AddChildAsync(Homeserver.GetRoom(roomid));
             }
         }
     }
 
     private async Task ImportNativeDMsIntoLayers() {
         var mdirect = await homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct");
+        var layerTasks = mdirect.Select(async entry => {
+            var (userId, dmRooms) = entry;
+            DMSpaceChildLayer? layer = await GetStateOrNullAsync<DMSpaceChildLayer>(DMSpaceChildLayer.EventId, userId.UrlEncode()) ?? await CreateLayer(userId);
+            return (entry, layer);
+        }).ToAsyncEnumerable();
+
+        await foreach (var ((userId, dmRooms), layer) in layerTasks) {
+            var space = Homeserver.GetRoom(layer.SpaceId).AsSpace;
+            foreach (var roomid in dmRooms) {
+                var dri = new DMRoomInfo() {
+                    AttributedUser = userId
+                };
+                await space.SendStateEventAsync(DMRoomInfo.EventId, roomid, dri);
+                await space.AddChildAsync(Homeserver.GetRoom(roomid));
+            }
+
+            await UpdateLayer(layer, userId);
+        }
+
+        // ensure all spaces are linked
+        Console.WriteLine("Ensuring all child layers are inside space...");
+        var layerAssuranceTasks = (await GetFullStateAsListAsync())
+            .Where(x => x.Type == DMSpaceChildLayer.EventId && (x.RawContent?.ContainsKey("space_id") ?? false))
+            .Select(async layer => {
+                var content = layer.TypedContent as DMSpaceChildLayer;
+                var space = homeserver.GetRoom(content!.SpaceId);
+                try {
+                    var state = await space.GetFullStateAsListAsync();
+                    if (!state.Any(e => e.Type == DMRoomInfo.EventId)) throw new Exception();
+                }
+                catch {
+                    await homeserver.JoinRoomAsync(content!.SpaceId);
+                }
+
+                return AddChildAsync(space);
+            });
+        await Task.WhenAll(layerAssuranceTasks);
+        Console.WriteLine("All child layers should be inside of space, if not, something went horribly wrong!");
+    }
+
+    private async Task<DMSpaceChildLayer> CreateLayer(string userId) {
+        var childCreateRequest = CreateRoomRequest.CreatePrivate(homeserver, userId);
+        childCreateRequest.CreationContent["type"] = "m.space";
+
+        var layer = new DMSpaceChildLayer() {
+            SpaceId = (await homeserver.CreateRoom(childCreateRequest)).RoomId
+        };
+
+        await SendStateEventAsync(DMSpaceChildLayer.EventId, userId[1..], layer);
+        await AddChildAsync(Homeserver.GetRoom(layer.SpaceId));
+        return layer;
+    }
+
+    private async Task UpdateLayerProfilesAsync() {
+        var layers = await GetAllActiveLayersAsync();
+        var getProfileTasks = layers.Select(async x => {
+            UserProfileResponse? profile = null;
+            try {
+                return (x, profile);
+            }
+            catch {
+                return (x, null);
+            }
+
+        }).ToAsyncEnumerable();
+        await foreach (var (layer, profile) in getProfileTasks) {
+            if (profile is null) continue;
+            var layerContent = layer.TypedContent as DMSpaceChildLayer;
+            var space = Homeserver.GetRoom(layerContent!.SpaceId).AsSpace;
+
+            try {
+                await space.SendStateEventAsync(RoomAvatarEventContent.EventId, "", new RoomAvatarEventContent() {
+                    Url = layerContent.OverrideAvatar ?? profile?.AvatarUrl
+                });
+                await space.SendStateEventAsync(RoomNameEventContent.EventId, "", new RoomNameEventContent() {
+                    Name = layerContent.OverrideName ?? profile?.DisplayName ?? "@" + layer.StateKey.UrlDecode()
+                });
+            }
+            catch (MatrixException e) {
+                Console.WriteLine("Failed to update space: {0}", e);
+            }
+        }
+    }
+
+    private async Task UpdateLayer(DMSpaceChildLayer layer, string mxid) {
+        UserProfileResponse? profile = null;
+        var space = Homeserver.GetRoom(layer.SpaceId).AsSpace;
+
+        if (string.IsNullOrWhiteSpace(layer.OverrideAvatar) || string.IsNullOrWhiteSpace(layer.OverrideName)) {
+            try {
+                profile = await homeserver.GetProfileAsync(mxid);
+            }
+            catch (MatrixException e) {
+                // if (e.ErrorCode != "M_NOT_FOUND") throw;
+                Console.Error.WriteLine(e);
+            }
+        }
+
+        try {
+            await space.SendStateEventAsync(RoomAvatarEventContent.EventId, "", new RoomAvatarEventContent() {
+                Url = layer.OverrideAvatar ?? profile?.AvatarUrl
+            });
+            await space.SendStateEventAsync(RoomNameEventContent.EventId, "", new RoomNameEventContent() {
+                Name = layer.OverrideName ?? profile?.DisplayName ?? mxid
+            });
+        }
+        catch (MatrixException e) {
+            Console.WriteLine("Failed to update space: {0}", e);
+        }
+    }
+
+    public async Task DisbandDMSpace() {
+        var state = await GetFullStateAsListAsync();
+        var leaveTasks = state.Select(async x => {
+            if (x.Type != DMSpaceChildLayer.EventId) return;
+            var content = x.TypedContent as DMSpaceChildLayer;
+            if (content?.SpaceId is null) return;
+            var space = homeserver.GetRoom(content.SpaceId);
+            try {
+                await space.LeaveAsync();
+            }
+            catch {
+                // might not be in room, doesnt matter
+            }
+        });
+
+        await LeaveAsync();
+
+        await Task.WhenAll(leaveTasks);
     }
 
-    #endregion
-}
+#endregion
+}
\ No newline at end of file