about summary refs log tree commit diff
path: root/Tests/LibMatrix.HomeserverEmulator/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/LibMatrix.HomeserverEmulator/Controllers')
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs34
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs86
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs7
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs116
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs59
-rw-r--r--Tests/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs2
6 files changed, 205 insertions, 99 deletions
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs
new file mode 100644
index 0000000..52d5932
--- /dev/null
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs
@@ -0,0 +1,34 @@
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.HomeserverEmulator.Services;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+using LibMatrix.Services;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.HomeserverEmulator.Controllers;
+
+[ApiController]
+[Route("/_matrix/")]
+public class DirectoryController(ILogger<DirectoryController> logger, RoomStore roomStore) : ControllerBase {
+    [HttpGet("client/v3/directory/room/{alias}")]
+    public async Task<AliasResult> GetRoomAliasV3(string alias) {
+        var match = roomStore._rooms.FirstOrDefault(x =>
+            x.State.Any(y => y.Type == RoomCanonicalAliasEventContent.EventId && y.StateKey == "" && y.RawContent?["alias"]?.ToString() == alias));
+
+        if (match == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Room not found"
+            };
+
+        var servers = match.State.Where(x => x.Type == RoomMemberEventContent.EventId && x.RawContent?["membership"]?.ToString() == "join")
+            .Select(x => x.StateKey!.Split(':', 2)[1]).ToList();
+
+        return new() {
+            RoomId = match.RoomId,
+            Servers = servers
+        };
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs
index 4820a65..7899ada 100644
--- a/Tests/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs
@@ -1,9 +1,8 @@
 using System.Text.Json.Nodes;
 using System.Text.RegularExpressions;
-using ArcaneLibs.Extensions;
+using ArcaneLibs.Collections;
 using LibMatrix.HomeserverEmulator.Services;
 using LibMatrix.Services;
-using Microsoft.AspNetCore.Html;
 using Microsoft.AspNetCore.Mvc;
 
 namespace LibMatrix.HomeserverEmulator.Controllers.Media;
@@ -41,60 +40,10 @@ public class MediaController(
         return media;
     }
 
-    private Dictionary<string, SemaphoreSlim> downloadLocks = new();
-
     [HttpGet("download/{serverName}/{mediaId}")]
     public async Task DownloadMedia(string serverName, string mediaId) {
-        while (true)
-            try {
-                if (cfg.StoreData) {
-                    SemaphoreSlim ss;
-                    if (!downloadLocks.ContainsKey(serverName + mediaId))
-                        downloadLocks[serverName + mediaId] = new SemaphoreSlim(1);
-                    ss = downloadLocks[serverName + mediaId];
-                    await ss.WaitAsync();
-                    var serverMediaPath = Path.Combine(cfg.DataStoragePath, "media", serverName);
-                    Directory.CreateDirectory(serverMediaPath);
-                    var mediaPath = Path.Combine(serverMediaPath, mediaId);
-                    if (System.IO.File.Exists(mediaPath)) {
-                        ss.Release();
-                        await using var stream = new FileStream(mediaPath, FileMode.Open);
-                        await stream.CopyToAsync(Response.Body);
-                        return;
-                    }
-                    else {
-                        var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
-                        if (mediaUrl is null)
-                            throw new MatrixException() {
-                                ErrorCode = "M_NOT_FOUND",
-                                Error = "Media not found"
-                            };
-                        await using var stream = System.IO.File.OpenWrite(mediaPath);
-                        using var response = await new HttpClient().GetAsync(mediaUrl);
-                        await response.Content.CopyToAsync(stream);
-                        await stream.FlushAsync();
-                        ss.Release();
-                        await DownloadMedia(serverName, mediaId);
-                        return;
-                    }
-                }
-                else {
-                    var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
-                    if (mediaUrl is null)
-                        throw new MatrixException() {
-                            ErrorCode = "M_NOT_FOUND",
-                            Error = "Media not found"
-                        };
-                    using var response = await new HttpClient().GetAsync(mediaUrl);
-                    await response.Content.CopyToAsync(Response.Body);
-                    return;
-                }
-
-                return;
-            }
-            catch (IOException) {
-                //ignored
-            }
+        var stream = await DownloadRemoteMedia(serverName, mediaId);
+        await stream.CopyToAsync(Response.Body);
     }
 
     [HttpGet("thumbnail/{serverName}/{mediaId}")]
@@ -118,4 +67,33 @@ public class MediaController(
 
         return data;
     }
+
+    private async Task<Stream> DownloadRemoteMedia(string serverName, string mediaId) {
+        if (cfg.StoreData) {
+            var path = Path.Combine(cfg.DataStoragePath, "media", serverName, mediaId);
+            if (!System.IO.File.Exists(path)) {
+                var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
+                if (mediaUrl is null)
+                    throw new MatrixException() {
+                        ErrorCode = "M_NOT_FOUND",
+                        Error = "Media not found"
+                    };
+                using var client = new HttpClient();
+                var stream = await client.GetStreamAsync(mediaUrl);
+                await using var fs = System.IO.File.Create(path);
+                await stream.CopyToAsync(fs);
+            }
+            return new FileStream(path, FileMode.Open);
+        }
+        else {
+            var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
+            if (mediaUrl is null)
+                throw new MatrixException() {
+                    ErrorCode = "M_NOT_FOUND",
+                    Error = "Media not found"
+                };
+            using var client = new HttpClient();
+            return await client.GetStreamAsync(mediaUrl);
+        }
+    }
 }
\ No newline at end of file
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
index 3896ac0..a1738c9 100644
--- a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
@@ -1,4 +1,5 @@
 using System.Collections.Frozen;
+using System.Text.Json.Nodes;
 using LibMatrix.HomeserverEmulator.Extensions;
 using LibMatrix.HomeserverEmulator.Services;
 using Microsoft.AspNetCore.Mvc;
@@ -73,12 +74,12 @@ public class RoomStateController(ILogger<RoomStateController> logger, TokenServi
     }
 
     [HttpPut("{eventType}")]
-    public async Task<EventIdResponse> SetState(string roomId, string eventType, [FromBody] StateEvent request) {
+    public async Task<EventIdResponse> SetState(string roomId, string eventType, [FromBody] JsonObject? request) {
         return await SetState(roomId, eventType, "", request);
     }
 
     [HttpPut("{eventType}/{stateKey}")]
-    public async Task<EventIdResponse> SetState(string roomId, string eventType, string stateKey, [FromBody] StateEvent request) {
+    public async Task<EventIdResponse> SetState(string roomId, string eventType, string stateKey, [FromBody] JsonObject? request) {
         var token = tokenService.GetAccessTokenOrNull(HttpContext);
         if (token == null)
             throw new MatrixException() {
@@ -99,7 +100,7 @@ public class RoomStateController(ILogger<RoomStateController> logger, TokenServi
                 ErrorCode = "M_NOT_FOUND",
                 Error = "Room not found"
             };
-        var evt = room.SetStateInternal(request.ToStateEvent(user, room));
+        var evt = room.SetStateInternal(new StateEvent() { Type = eventType, StateKey = stateKey, RawContent = request }.ToStateEvent(user, room));
         evt.Type = eventType;
         evt.StateKey = stateKey;
         return new EventIdResponse() {
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
index 3d23660..afd69d1 100644
--- a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
@@ -124,6 +124,122 @@ public class RoomTimelineController(
 
         return evt;
     }
+    
+    [HttpGet("relations/{eventId}")]
+    public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) {
+        var token = tokenService.GetAccessToken(HttpContext);
+        var user = await userStore.GetUserByToken(token);
+
+        var room = roomStore.GetRoomById(roomId);
+        if (room == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Room not found"
+            };
+
+        if (!room.JoinedMembers.Any(x => x.StateKey == user.UserId))
+            throw new MatrixException() {
+                ErrorCode = "M_FORBIDDEN",
+                Error = "User is not in the room"
+            };
+
+        var evt = room.Timeline.SingleOrDefault(x => x.EventId == eventId);
+        if (evt == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Event not found"
+            };
+
+        var matchingEvents = await GetRelationsInternal(roomId, eventId, dir, from, limit, recurse, to);
+
+        return new() {
+            Chunk = matchingEvents.ToList()
+        };
+    }
+    
+    [HttpGet("relations/{eventId}/{relationType}")]
+    public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, string relationType, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) {
+        var token = tokenService.GetAccessToken(HttpContext);
+        var user = await userStore.GetUserByToken(token);
+
+        var room = roomStore.GetRoomById(roomId);
+        if (room == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Room not found"
+            };
+
+        if (!room.JoinedMembers.Any(x => x.StateKey == user.UserId))
+            throw new MatrixException() {
+                ErrorCode = "M_FORBIDDEN",
+                Error = "User is not in the room"
+            };
+
+        var evt = room.Timeline.SingleOrDefault(x => x.EventId == eventId);
+        if (evt == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Event not found"
+            };
+
+        var matchingEvents = await GetRelationsInternal(roomId, eventId, dir, from, limit, recurse, to);
+
+        return new() {
+            Chunk = matchingEvents.ToList()
+        };
+    }
+    
+    [HttpGet("relations/{eventId}/{relationType}/{eventType}")]
+    public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, string relationType, string eventType, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) {
+        var token = tokenService.GetAccessToken(HttpContext);
+        var user = await userStore.GetUserByToken(token);
+
+        var room = roomStore.GetRoomById(roomId);
+        if (room == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Room not found"
+            };
+
+        if (!room.JoinedMembers.Any(x => x.StateKey == user.UserId))
+            throw new MatrixException() {
+                ErrorCode = "M_FORBIDDEN",
+                Error = "User is not in the room"
+            };
+
+        var evt = room.Timeline.SingleOrDefault(x => x.EventId == eventId);
+        if (evt == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Event not found"
+            };
+
+        var matchingEvents = await GetRelationsInternal(roomId, eventId, dir, from, limit, recurse, to);
+
+        return new() {
+            Chunk = matchingEvents.ToList()
+        };
+    }
+    
+    private async Task<IEnumerable<StateEventResponse>> GetRelationsInternal(string roomId, string eventId, string dir, string? from, int? limit, bool? recurse, string? to) {
+        var room = roomStore.GetRoomById(roomId);
+        var evt = room.Timeline.SingleOrDefault(x => x.EventId == eventId);
+        if (evt == null)
+            throw new MatrixException() {
+                ErrorCode = "M_NOT_FOUND",
+                Error = "Event not found"
+            };
+
+        var relatedEvents = room.Timeline.Where(x => x.RawContent?["m.relates_to"]?["event_id"]?.GetValue<string>() == eventId);
+        if (dir == "b") {
+            relatedEvents = relatedEvents.TakeLast(limit ?? 100);
+        }
+        else if (dir == "f") {
+            relatedEvents = relatedEvents.Take(limit ?? 100);
+        }
+        
+        return relatedEvents;
+    }
 
 #region Commands
 
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
index 6849ff8..c24e6e9 100644
--- a/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
@@ -28,42 +28,9 @@ public class RoomsController(ILogger<RoomsController> logger, TokenService token
                 Error = "No such user"
             };
 
-        var room = new RoomStore.Room($"!{Guid.NewGuid()}:{tokenService.GenerateServerName(HttpContext)}");
-        var createEvent = room.SetStateInternal(new() {
-            Type = RoomCreateEventContent.EventId,
-            RawContent = new() {
-                ["creator"] = user.UserId
-            }
-        });
-        foreach (var (key, value) in request.CreationContent) {
-            createEvent.RawContent[key] = value.DeepClone();
-        }
-
-        if (!string.IsNullOrWhiteSpace(request.Name))
-            room.SetStateInternal(new StateEvent() {
-                Type = RoomNameEventContent.EventId,
-                TypedContent = new RoomNameEventContent() {
-                    Name = request.Name
-                }
-            });
-
-        if (!string.IsNullOrWhiteSpace(request.RoomAliasName))
-            room.SetStateInternal(new StateEvent() {
-                Type = RoomCanonicalAliasEventContent.EventId,
-                TypedContent = new RoomCanonicalAliasEventContent() {
-                    Alias = $"#{request.RoomAliasName}:localhost"
-                }
-            });
-
-        if (request.InitialState is { Count: > 0 }) {
-            foreach (var stateEvent in request.InitialState) {
-                room.SetStateInternal(stateEvent);
-            }
-        }
+        // var room = new RoomStore.Room($"!{Guid.NewGuid()}:{tokenService.GenerateServerName(HttpContext)}");
+        var room = roomStore.CreateRoom(request, user);
 
-        room.AddUser(user.UserId);
-
-        // user.Rooms.Add(room.RoomId, room);
         return new() {
             RoomId = room.RoomId
         };
@@ -122,9 +89,13 @@ public class RoomsController(ILogger<RoomsController> logger, TokenService token
             replacement_room = room.RoomId
         };
     }
-    
+
+    public class ReasonBody {
+        [JsonPropertyName("reason")]
+        public string? Reason { get; set; }
+    }
     [HttpPost("rooms/{roomId}/leave")] // TODO: implement
-    public async Task<object> LeaveRoom(string roomId) {
+    public async Task<object> LeaveRoom(string roomId, [FromBody] ReasonBody body) {
         var token = tokenService.GetAccessTokenOrNull(HttpContext);
         if (token == null)
             throw new MatrixException() {
@@ -145,11 +116,17 @@ public class RoomsController(ILogger<RoomsController> logger, TokenService token
                 ErrorCode = "M_NOT_FOUND",
                 Error = "Room not found"
             };
+        
+        room.SetStateInternal(new() {
+            Type = RoomMemberEventContent.EventId,
+            TypedContent = new RoomMemberEventContent() {
+                Membership = "leave",
+                Reason = body.Reason
+            },
+            StateKey = user.UserId
+        });
 
-        // room.RemoveUser(user.UserId);
-
-        // room.SetStateInternal(new StateEventResponse() { });
-
+        logger.LogTrace($"User {user.UserId} left room {room.RoomId}");
         return new {
             room_id = room.RoomId
         };
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs
index c717ba5..98c41da 100644
--- a/Tests/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs
+++ b/Tests/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs
@@ -46,7 +46,7 @@ public class ProfileController(ILogger<ProfileController> logger, TokenService t
                 ErrorCode = "M_NOT_FOUND",

                 Error = "User not found."

             };

-        user.Profile[key] = value[key];

+        user.Profile[key] = value[key]?.AsObject();

         return value;

     }

 }
\ No newline at end of file