From 508c694c3d551cddb3b15c1b0d4787dae3c00530 Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 2 May 2024 07:20:13 +0200 Subject: HomeserverEmulator work --- .../Controllers/DirectoryController.cs | 34 ++++++ .../Controllers/Media/MediaController.cs | 86 ++++++--------- .../Controllers/Rooms/RoomStateController.cs | 7 +- .../Controllers/Rooms/RoomTimelineController.cs | 116 +++++++++++++++++++++ .../Controllers/Rooms/RoomsController.cs | 59 ++++------- .../Controllers/Users/ProfileController.cs | 2 +- 6 files changed, 205 insertions(+), 99 deletions(-) create mode 100644 Tests/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs (limited to 'Tests/LibMatrix.HomeserverEmulator/Controllers') 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 logger, RoomStore roomStore) : ControllerBase { + [HttpGet("client/v3/directory/room/{alias}")] + public async Task 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 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 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 logger, TokenServi } [HttpPut("{eventType}")] - public async Task SetState(string roomId, string eventType, [FromBody] StateEvent request) { + public async Task SetState(string roomId, string eventType, [FromBody] JsonObject? request) { return await SetState(roomId, eventType, "", request); } [HttpPut("{eventType}/{stateKey}")] - public async Task SetState(string roomId, string eventType, string stateKey, [FromBody] StateEvent request) { + public async Task 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 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 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 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 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> 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() == 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 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 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 LeaveRoom(string roomId) { + public async Task 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 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 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 -- cgit 1.4.1