about summary refs log tree commit diff
path: root/MatrixUtils.LibDMSpace
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-02-23 13:57:06 +0100
committerRory& <root@rory.gay>2024-02-23 13:57:06 +0100
commitd0d11db2209a8be65c27e15ca9d8a3b594f1a352 (patch)
treeb42b7de4b09888a1439d0939707ba1331becf626 /MatrixUtils.LibDMSpace
parentHS emulator (diff)
downloadMatrixUtils-d0d11db2209a8be65c27e15ca9d8a3b594f1a352.tar.xz
Add eons of work because I forgot to push
Diffstat (limited to 'MatrixUtils.LibDMSpace')
-rw-r--r--MatrixUtils.LibDMSpace/DMSpaceRoom.cs192
-rw-r--r--MatrixUtils.LibDMSpace/HomeserverExtensions.cs10
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs9
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs20
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs2
5 files changed, 187 insertions, 46 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
diff --git a/MatrixUtils.LibDMSpace/HomeserverExtensions.cs b/MatrixUtils.LibDMSpace/HomeserverExtensions.cs
new file mode 100644
index 0000000..2bf5eaa
--- /dev/null
+++ b/MatrixUtils.LibDMSpace/HomeserverExtensions.cs
@@ -0,0 +1,10 @@
+using LibMatrix.Homeservers;
+
+namespace MatrixUtils.LibDMSpace;
+
+public static class HomeserverExtensions {
+    public static async Task<DMSpaceRoom> GetDMSpaceRoomAsync(this AuthenticatedHomeserverGeneric homeserver) {
+        return null; //TODO: implement
+    }
+    
+}
\ No newline at end of file
diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
index 5aa62d7..bc595b5 100644
--- a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
+++ b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
@@ -5,10 +5,13 @@ using LibMatrix.Interfaces;
 namespace MatrixUtils.LibDMSpace.StateEvents;
 
 [MatrixEvent(EventName = EventId)]
-public class DMRoomInfo : TimelineEventContent {
+public class DMRoomInfo : EventContent {
     public const string EventId = "gay.rory.dm_room_info";
-    [JsonPropertyName("remote_users")]
-    public List<string> RemoteUsers { get; set; }
+    // [JsonPropertyName("remote_users")]
+    // public List<string> RemoteUsers { get; set; }
 
+    [JsonPropertyName("attributed_user")]
+    public string AttributedUser { get; set; }
+    
 
 }
diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs
new file mode 100644
index 0000000..16c7b70
--- /dev/null
+++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+using LibMatrix.EventTypes;
+using LibMatrix.Interfaces;
+
+namespace MatrixUtils.LibDMSpace.StateEvents;
+
+[MatrixEvent(EventName = EventId)]
+public class DMSpaceChildLayer : EventContent {
+    public const string EventId = "gay.rory.dm_space_child_layer";
+
+    [JsonPropertyName("space_id")]
+    public string SpaceId { get; set; }
+    
+    [JsonPropertyName("override_name")]
+    public string? OverrideName { get; set; }
+    
+    [JsonPropertyName("override_avatar")]
+    public string? OverrideAvatar { get; set; }
+    
+}
diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
index 0df1913..f5daa74 100644
--- a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
+++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
@@ -5,7 +5,7 @@ using LibMatrix.Interfaces;
 namespace MatrixUtils.LibDMSpace.StateEvents;
 
 [MatrixEvent(EventName = EventId)]
-public class DMSpaceInfo : TimelineEventContent {
+public class DMSpaceInfo : EventContent {
     public const string EventId = "gay.rory.dm_space_info";
 
     [JsonPropertyName("is_layered")]