From 46df5b8e335754f1582fc4d41d9546808ed8ee66 Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Fri, 29 Sep 2023 19:38:00 +0200 Subject: Unit tests, small refactors --- LibMatrix/EventTypes/Spec/RoomMessageEventData.cs | 2 + .../EventTypes/Spec/State/RoomCreateEventData.cs | 4 ++ .../Spec/State/RoomPowerLevelEventData.cs | 16 ++--- .../EventTypes/Spec/State/SpaceChildEventData.cs | 2 +- LibMatrix/Helpers/SyncHelper.cs | 16 ++--- .../Homeservers/AuthenticatedHomeserverGeneric.cs | 38 ++++++++---- .../AuthenticatedHomeserverMxApiExtended.cs | 3 +- .../Homeservers/AuthenticatedHomeserverSynapse.cs | 2 +- LibMatrix/Homeservers/RemoteHomeServer.cs | 49 ++++++++++++++-- LibMatrix/Responses/CreateRoomRequest.cs | 2 +- LibMatrix/Responses/LoginResponse.cs | 19 ++++-- LibMatrix/RoomTypes/GenericRoom.cs | 68 ++++++++++++++-------- LibMatrix/RoomTypes/SpaceRoom.cs | 35 +++++++---- LibMatrix/Services/HomeserverProviderService.cs | 30 +++++----- LibMatrix/StateEvent.cs | 32 +++++++++- 15 files changed, 221 insertions(+), 97 deletions(-) (limited to 'LibMatrix') diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs index b76b176..f8ee58b 100644 --- a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs +++ b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs @@ -28,4 +28,6 @@ public class RoomMessageEventContent : EventContent { /// [JsonPropertyName("url")] public string? Url { get; set; } + + public string? FileName { get; set; } } diff --git a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs index e409f3a..c5bf14e 100644 --- a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs @@ -8,12 +8,16 @@ namespace LibMatrix.EventTypes.Spec.State; public class RoomCreateEventContent : EventContent { [JsonPropertyName("room_version")] public string? RoomVersion { get; set; } + [JsonPropertyName("creator")] public string? Creator { get; set; } + [JsonPropertyName("m.federate")] public bool? Federate { get; set; } + [JsonPropertyName("predecessor")] public RoomCreatePredecessor? Predecessor { get; set; } + [JsonPropertyName("type")] public string? Type { get; set; } diff --git a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs index 1a5d5f5..2ae9593 100644 --- a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs @@ -7,34 +7,34 @@ namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.power_levels")] public class RoomPowerLevelEventContent : EventContent { [JsonPropertyName("ban")] - public long? Ban { get; set; } // = 50; + public long? Ban { get; set; } = 50; [JsonPropertyName("events_default")] - public long EventsDefault { get; set; } // = 0; + public long EventsDefault { get; set; } = 0; [JsonPropertyName("events")] public Dictionary? Events { get; set; } // = null!; [JsonPropertyName("invite")] - public long? Invite { get; set; } // = 50; + public long? Invite { get; set; } = 0; [JsonPropertyName("kick")] - public long? Kick { get; set; } // = 50; + public long? Kick { get; set; } = 50; [JsonPropertyName("notifications")] public NotificationsPL? NotificationsPl { get; set; } // = null!; [JsonPropertyName("redact")] - public long? Redact { get; set; } // = 50; + public long? Redact { get; set; } = 50; [JsonPropertyName("state_default")] - public long? StateDefault { get; set; } // = 50; + public long? StateDefault { get; set; } = 50; [JsonPropertyName("users")] public Dictionary? Users { get; set; } // = null!; [JsonPropertyName("users_default")] - public long? UsersDefault { get; set; } // = 0; + public long? UsersDefault { get; set; } = 0; [Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)] [JsonIgnore] @@ -47,7 +47,7 @@ public class RoomPowerLevelEventContent : EventContent { } public bool IsUserAdmin(string userId) { - return Users.TryGetValue(userId, out var level) && level >= Events.Max(x=>x.Value); + return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value); } public bool UserHasPermission(string userId, string eventType) { diff --git a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs index a13ba2e..0a897dc 100644 --- a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs @@ -9,7 +9,7 @@ public class SpaceChildEventContent : EventContent { [JsonPropertyName("auto_join")] public bool? AutoJoin { get; set; } [JsonPropertyName("via")] - public string[]? Via { get; set; } + public List? Via { get; set; } [JsonPropertyName("suggested")] public bool? Suggested { get; set; } } diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 386fd4d..74972a1 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -16,10 +16,6 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) { string? setPresence = "online", SyncFilter? filter = null, CancellationToken? cancellationToken = null) { - // var outFileName = "sync-" + - // (await storageService.CacheStorageProvider.GetAllKeysAsync()).Count( - // x => x.StartsWith("sync")) + - // ".json"; var url = $"/_matrix/client/v3/sync?timeout={timeout}&set_presence={setPresence}"; if (!string.IsNullOrWhiteSpace(since)) url += $"&since={since}"; if (filter is not null) url += $"&filter={filter.ToJson(ignoreNull: true, indent: false)}"; @@ -28,19 +24,17 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) { try { var req = await homeserver._httpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None); - // var res = await JsonSerializer.DeserializeAsync(await req.Content.ReadAsStreamAsync()); - #if DEBUG && false - var jsonObj = await req.Content.ReadFromJsonAsync(); try { - await _homeServer._httpClient.PostAsJsonAsync( - "http://localhost:5116/validate/" + typeof(SyncResult).AssemblyQualifiedName, jsonObj); + await homeserver._httpClient.PostAsync( + "http://localhost:5116/validate/" + typeof(SyncResult).AssemblyQualifiedName, + new StreamContent(await req.Content.ReadAsStreamAsync())); } + catch (Exception e) { Console.WriteLine("[!!] Checking sync response failed: " + e); } - - var res = jsonObj.Deserialize(); + var res = await req.Content.ReadFromJsonAsync(); return res; #else return await req.Content.ReadFromJsonAsync(); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index b881e6c..f70dd39 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -1,3 +1,4 @@ +using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; @@ -12,18 +13,30 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; public class AuthenticatedHomeserverGeneric : RemoteHomeServer { - public AuthenticatedHomeserverGeneric(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain) { + public AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : base(baseUrl) { AccessToken = accessToken.Trim(); SyncHelper = new SyncHelper(this); + + _httpClient.Timeout = TimeSpan.FromMinutes(15); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } public virtual SyncHelper SyncHelper { get; init; } - public virtual WhoAmIResponse WhoAmI { get; set; } = null!; - public virtual string UserId => WhoAmI.UserId; + private WhoAmIResponse? _whoAmI; + + public WhoAmIResponse? WhoAmI => _whoAmI ??= _httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami").Result; + public string UserId => WhoAmI.UserId; + + // public virtual async Task WhoAmI() { + // if (_whoAmI is not null) return _whoAmI; + // _whoAmI = await _httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); + // return _whoAmI; + // } + public virtual string AccessToken { get; set; } public virtual GenericRoom GetRoom(string roomId) { - if(roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId)); + if (roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId)); return new GenericRoom(this, roomId); } @@ -50,7 +63,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { } public virtual async Task CreateRoom(CreateRoomRequest creationEvent) { - creationEvent.CreationContent["creator"] = UserId; + creationEvent.CreationContent["creator"] = WhoAmI.UserId; var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); @@ -61,9 +74,10 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { var room = GetRoom((await res.Content.ReadFromJsonAsync())!["room_id"]!.ToString()); - foreach (var user in creationEvent.Invite) { - await room.InviteUser(user); - } + if (creationEvent.Invite is not null) + foreach (var user in creationEvent.Invite) { + await room.InviteUserAsync(user); + } return room; } @@ -77,6 +91,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { } #region Utility Functions + public virtual async IAsyncEnumerable GetJoinedRoomsByType(string type) { var rooms = await GetJoinedRooms(); var tasks = rooms.Select(async room => { @@ -92,6 +107,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { if (result is not null) yield return result; } } + #endregion #region Account Data @@ -104,11 +120,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { // } // // return await res.Content.ReadFromJsonAsync(); - return await _httpClient.GetFromJsonAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}"); + return await _httpClient.GetFromJsonAsync($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}"); } public virtual async Task SetAccountData(string key, object data) { - var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}", data); + var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}", data); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to set account data: {await res.Content.ReadAsStringAsync()}"); throw new InvalidDataException($"Failed to set account data: {await res.Content.ReadAsStringAsync()}"); @@ -116,4 +132,6 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { } #endregion + + } diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs index 5319f46..a420d71 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs @@ -4,5 +4,4 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverMxApiExtended(string canonicalHomeServerDomain, string accessToken) : AuthenticatedHomeserverGeneric(canonicalHomeServerDomain, - accessToken); +public class AuthenticatedHomeserverMxApiExtended(string baseUrl, string accessToken) : AuthenticatedHomeserverGeneric(baseUrl, accessToken); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index ae26f69..e355d2d 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -102,7 +102,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { } } - public AuthenticatedHomeserverSynapse(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain, accessToken) { + public AuthenticatedHomeserverSynapse(string baseUrl, string accessToken) : base(baseUrl, accessToken) { Admin = new(this); } } diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index ab3ab51..d10c837 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -1,19 +1,22 @@ using System.Net.Http.Json; +using System.Text.Json; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; using LibMatrix.Responses; +using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class RemoteHomeServer(string canonicalHomeServerDomain) { - // _httpClient.Timeout = TimeSpan.FromSeconds(5); +public class RemoteHomeServer(string baseUrl) { private Dictionary _profileCache { get; set; } = new(); - public string HomeServerDomain { get; } = canonicalHomeServerDomain.Trim(); - public string FullHomeServerDomain { get; set; } - public MatrixHttpClient _httpClient { get; set; } = new(); + public string BaseUrl { get; } = baseUrl.Trim(); + public MatrixHttpClient _httpClient { get; set; } = new() { + BaseAddress = new Uri(new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl).Result ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromSeconds(120) + }; public async Task GetProfileAsync(string mxid) { if (mxid is null) throw new ArgumentNullException(nameof(mxid)); @@ -46,6 +49,42 @@ public class RemoteHomeServer(string canonicalHomeServerDomain) { if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson()); return data; } + +#region Authentication + + public async Task LoginAsync(string username, string password, string? deviceName = null) { + var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/login", new { + type = "m.login.password", + identifier = new { + type = "m.id.user", + user = username + }, + password = password, + initial_device_display_name = deviceName + }); + var data = await resp.Content.ReadFromJsonAsync(); + if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToJson()); + return data; + } + + public async Task RegisterAsync(string username, string password, string? deviceName = null) { + var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/register", new { + kind = "user", + auth = new { + type = "m.login.dummy" + }, + username, + password, + initial_device_display_name = deviceName ?? "LibMatrix" + }, new JsonSerializerOptions() { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + var data = await resp.Content.ReadFromJsonAsync(); + if (!resp.IsSuccessStatusCode) Console.WriteLine("Register: " + data.ToJson()); + return data; + } + +#endregion } public class AliasResult { diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs index 381271b..511b3da 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs @@ -40,7 +40,7 @@ public class CreateRoomRequest { public JsonObject CreationContent { get; set; } = new(); [JsonPropertyName("invite")] - public List Invite { get; set; } + public List? Invite { get; set; } /// /// For use only when you can't use the CreationContent property diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs index 175f337..eb53c0a 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs @@ -1,19 +1,30 @@ using System.Text.Json.Serialization; +using LibMatrix.Homeservers; +using LibMatrix.Services; namespace LibMatrix.Responses; public class LoginResponse { [JsonPropertyName("access_token")] - public string AccessToken { get; set; } + public string AccessToken { get; set; } = null!; [JsonPropertyName("device_id")] - public string DeviceId { get; set; } + public string DeviceId { get; set; } = null!; + + private string? _homeserver; [JsonPropertyName("home_server")] - public string Homeserver { get; set; } + public string Homeserver { + get => _homeserver ?? UserId.Split(':', 2).Last(); + protected init => _homeserver = value; + } [JsonPropertyName("user_id")] - public string UserId { get; set; } + public string UserId { get; set; } = null!; + + public async Task GetAuthenticatedHomeserver(string? proxy = null) { + return new AuthenticatedHomeserverGeneric(proxy ?? await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver), AccessToken); + } } public class LoginRequest { [JsonPropertyName("type")] diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index ab748fe..78a0873 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -28,14 +28,6 @@ public class GenericRoom { public string RoomId { get; set; } - [Obsolete("", true)] - public async Task GetStateAsync(string type, string stateKey = "") { - var url = $"/_matrix/client/v3/rooms/{RoomId}/state"; - if (!string.IsNullOrEmpty(type)) url += $"/{type}"; - if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}"; - return await _httpClient.GetFromJsonAsync(url); - } - public async IAsyncEnumerable GetFullStateAsync() { var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/state"); var result = @@ -68,7 +60,7 @@ public class GenericRoom { } catch (MatrixException e) { // if (e is not { ErrorCodode: "M_NOT_FOUND" }) { - throw; + throw; // } // Console.WriteLine(e); @@ -202,21 +194,18 @@ public class GenericRoom { return resu; } - public async Task SendFileAsync(string eventType, string fileName, Stream fileStream) { - var content = new MultipartFormDataContent(); - content.Add(new StreamContent(fileStream), "file", fileName); - var res = await - ( - await _httpClient.PutAsync( - $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), - content - ) - ) - .Content.ReadFromJsonAsync(); - return res; + public async Task SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file") { + var url = await Homeserver.UploadFile(fileName, fileStream); + var content = new RoomMessageEventContent() { + MessageType = messageType, + Url = url, + Body = fileName, + FileName = fileName, + }; + return await SendTimelineEventAsync("m.room.message", content); } - public async Task GetRoomAccountData(string key) { + public async Task GetRoomAccountDataAsync(string key) { var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}"); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to get room account data: {await res.Content.ReadAsStringAsync()}"); @@ -226,7 +215,7 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync(); } - public async Task SetRoomAccountData(string key, object data) { + public async Task SetRoomAccountDataAsync(string key, object data) { var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to set room account data: {await res.Content.ReadAsStringAsync()}"); @@ -236,7 +225,7 @@ public class GenericRoom { public readonly SpaceRoom AsSpace; - public async Task GetEvent(string eventId) { + public async Task GetEventAsync(string eventId) { return await _httpClient.GetFromJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); } @@ -246,9 +235,38 @@ public class GenericRoom { $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync())!; } - public async Task InviteUser(string userId, string? reason = null) { + public async Task InviteUserAsync(string userId, string? reason = null) { await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason)); } + +#region Disband room + + public async Task DisbandRoomAsync() { + var states = GetFullStateAsync(); + List stateTypeIgnore = new() { + "m.room.create", + "m.room.power_levels", + "m.room.join_rules", + "m.room.history_visibility", + "m.room.guest_access", + "m.room.member", + }; + await foreach (var state in states) { + if (state is null || state.RawContent is not { Count: > 0 }) continue; + if (state.Type == "m.room.member" && state.StateKey != Homeserver.UserId) + try { + await BanAsync(state.StateKey, "Disbanding room"); + } + catch (MatrixException e) { + if (e.ErrorCode != "M_FORBIDDEN") throw; + } + + if (stateTypeIgnore.Contains(state.Type)) continue; + await SendStateEventAsync(state.Type, state.StateKey, new()); + } + } + +#endregion } public class RoomIdResponse { diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs index a43ae82..4a8e247 100644 --- a/LibMatrix/RoomTypes/SpaceRoom.cs +++ b/LibMatrix/RoomTypes/SpaceRoom.cs @@ -1,26 +1,37 @@ using ArcaneLibs.Extensions; using LibMatrix.Homeservers; +using Microsoft.Extensions.Logging; namespace LibMatrix.RoomTypes; -public class SpaceRoom : GenericRoom { - private new readonly AuthenticatedHomeserverGeneric _homeserver; +public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) { private readonly GenericRoom _room; - public SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : base(homeserver, roomId) { - _homeserver = homeserver; - } - - private static SemaphoreSlim _semaphore = new(1, 1); public async IAsyncEnumerable GetChildrenAsync(bool includeRemoved = false) { - // await _semaphore.WaitAsync(); var rooms = new List(); 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); + if (stateEvent!.Type != "m.space.child") continue; + if (stateEvent.RawContent!.ToJson() != "{}" || includeRemoved) + yield return Homeserver.GetRoom(stateEvent.StateKey); + } + } + + public async Task AddChildAsync(GenericRoom room) { + var members = room.GetMembersAsync(true); + Dictionary memberCountByHs = new(); + await foreach (var member in members) { + var server = member.StateKey.Split(':')[1]; + if (memberCountByHs.ContainsKey(server)) memberCountByHs[server]++; + else memberCountByHs[server] = 1; } - // _semaphore.Release(); + + var resp = await SendStateEventAsync("m.space.child", room.RoomId, new { + via = memberCountByHs + .OrderByDescending(x => x.Value) + .Select(x => x.Key) + .Take(10) + }); + return resp; } } diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index 49167fa..666d2a2 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -9,29 +9,29 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Services; public class HomeserverProviderService { - private readonly TieredStorageService _tieredStorageService; private readonly ILogger _logger; private readonly HomeserverResolverService _homeserverResolverService; - public HomeserverProviderService(TieredStorageService tieredStorageService, - ILogger logger, HomeserverResolverService homeserverResolverService) { - _tieredStorageService = tieredStorageService; + public HomeserverProviderService(ILogger logger, HomeserverResolverService homeserverResolverService) { _logger = logger; _homeserverResolverService = homeserverResolverService; - logger.LogDebug("New HomeserverProviderService created with TieredStorageService<{}>!", - string.Join(", ", tieredStorageService.GetType().GetProperties().Select(x => x.Name))); } private static Dictionary _authenticatedHomeserverSemaphore = new(); private static Dictionary _authenticatedHomeServerCache = new(); + private static Dictionary _remoteHomeserverSemaphore = new(); + private static Dictionary _remoteHomeServerCache = new(); + public async Task GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null) { var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver + accessToken, _ => new SemaphoreSlim(1, 1)); await sem.WaitAsync(); - if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) { - sem.Release(); - return _authenticatedHomeServerCache[homeserver + accessToken]; + lock (_authenticatedHomeServerCache) { + if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) { + sem.Release(); + return _authenticatedHomeServerCache[homeserver + accessToken]; + } } var domain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); @@ -45,12 +45,11 @@ public class HomeserverProviderService { hs = new AuthenticatedHomeserverGeneric(homeserver, accessToken); } - hs.FullHomeServerDomain = domain; hs._httpClient = hc; hs._httpClient.Timeout = TimeSpan.FromMinutes(15); hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"))!; + // (() => hs.WhoAmI) = (await hs._httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"))!; lock(_authenticatedHomeServerCache) _authenticatedHomeServerCache[homeserver + accessToken] = hs; @@ -60,11 +59,10 @@ public class HomeserverProviderService { } public async Task GetRemoteHomeserver(string homeserver, string? proxy = null) { - var hs = new RemoteHomeServer(homeserver); - hs.FullHomeServerDomain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); - hs._httpClient.Dispose(); - hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; - hs._httpClient.Timeout = TimeSpan.FromSeconds(120); + var hs = new RemoteHomeServer(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)); + // hs._httpClient.Dispose(); + // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; + // hs._httpClient.Timeout = TimeSpan.FromSeconds(120); return hs; } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index 97348a5..b42bd64 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using ArcaneLibs; using ArcaneLibs.Extensions; using LibMatrix.EventTypes; @@ -48,7 +49,7 @@ public class StateEvent { return null; } - set => RawContent = JsonSerializer.Deserialize(JsonSerializer.Serialize(value)); + set => RawContent = JsonSerializer.Deserialize(JsonSerializer.Serialize(value, value.GetType())); } [JsonPropertyName("state_key")] @@ -120,3 +121,32 @@ public class StateEvent { [JsonIgnore] public string cdtype => TypedContent.GetType().Name; } + +/* +public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoResolver +{ + public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + { + JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); + + Type baseType = typeof(EventContent); + if (jsonTypeInfo.Type == baseType) { + jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { + TypeDiscriminatorPropertyName = "type", + IgnoreUnrecognizedTypeDiscriminators = true, + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType, + + DerivedTypes = StateEvent.KnownStateEventTypesByName.Select(x => new JsonDerivedType(x.Value, x.Key)).ToList() + + // DerivedTypes = new ClassCollector() + // .ResolveFromAllAccessibleAssemblies() + // .SelectMany(t => t.GetCustomAttributes() + // .Select(a => new JsonDerivedType(t, attr.EventName)); + + }; + } + + return jsonTypeInfo; + } +} +*/ -- cgit 1.4.1