diff options
-rw-r--r-- | LibMatrix/Extensions/HttpClientExtensions.cs | 16 | ||||
-rw-r--r-- | LibMatrix/Helpers/SyncHelper.cs | 9 | ||||
-rw-r--r-- | LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs | 46 | ||||
-rw-r--r-- | LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs | 10 | ||||
-rw-r--r-- | LibMatrix/Homeservers/RemoteHomeServer.cs | 36 | ||||
-rw-r--r-- | LibMatrix/Interfaces/EventContent.cs | 2 | ||||
-rw-r--r-- | LibMatrix/LibMatrix.csproj | 3 | ||||
-rw-r--r-- | LibMatrix/MatrixException.cs | 3 | ||||
-rw-r--r-- | LibMatrix/Responses/LoginResponse.cs | 4 | ||||
-rw-r--r-- | LibMatrix/RoomTypes/GenericRoom.cs | 76 | ||||
-rw-r--r-- | LibMatrix/Services/HomeserverProviderService.cs | 44 | ||||
-rw-r--r-- | LibMatrix/Services/HomeserverResolverService.cs | 31 | ||||
-rw-r--r-- | LibMatrix/Services/TieredStorageService.cs | 11 | ||||
-rw-r--r-- | LibMatrix/StateEvent.cs | 12 | ||||
-rw-r--r-- | Tests/LibMatrix.Tests/Tests/ResolverTest.cs | 2 | ||||
-rw-r--r-- | Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs | 8 |
16 files changed, 184 insertions, 129 deletions
diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs index 5bb0dc2..913864e 100644 --- a/LibMatrix/Extensions/HttpClientExtensions.cs +++ b/LibMatrix/Extensions/HttpClientExtensions.cs @@ -53,11 +53,21 @@ public class MatrixHttpClient : HttpClient { Console.WriteLine(e); } - var a = await base.SendAsync(request, cancellationToken); - if (a.IsSuccessStatusCode) return a; + HttpResponseMessage responseMessage; + try { + responseMessage = await base.SendAsync(request, cancellationToken); + } + catch (Exception e) { + typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance) + ?.SetValue(request, 0); + await Task.Delay(2500); + return await SendAsync(request, cancellationToken); + } + + if (responseMessage.IsSuccessStatusCode) return responseMessage; //error handling - var content = await a.Content.ReadAsStringAsync(cancellationToken); + var content = await responseMessage.Content.ReadAsStringAsync(cancellationToken); if (content.Length == 0) throw new MatrixException() { ErrorCode = "M_UNKNOWN", diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index fb7fad2..a63b8bb 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -15,12 +15,13 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg public bool FullState { get; set; } = false; public bool IsInitialSync { get; set; } = true; - + public async Task<SyncResponse?> SyncAsync(CancellationToken? cancellationToken = null) { if (homeserver is null) { Console.WriteLine("Null passed as homeserver for SyncHelper!"); throw new ArgumentNullException("Null passed as homeserver for SyncHelper!"); } + var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}"; if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; if (Filter is not null) url += $"&filter={Filter.ToJson(ignoreNull: true, indent: false)}"; @@ -45,7 +46,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg while (!cancellationToken?.IsCancellationRequested ?? true) { var sync = await SyncAsync(cancellationToken); if (sync is null) continue; - Since = string.IsNullOrWhiteSpace(sync?.NextBatch) ? Since : sync.NextBatch; + if (!string.IsNullOrWhiteSpace(sync?.NextBatch)) Since = sync.NextBatch; yield return sync; } } @@ -73,7 +74,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg var tasks = SyncReceivedHandlers.Select(x => x(syncResponse)).ToList(); await Task.WhenAll(tasks); - if (syncResponse.AccountData is { Events: { Count: > 0 } }) { + if (syncResponse.AccountData is { Events.Count: > 0 }) { foreach (var accountDataEvent in syncResponse.AccountData.Events) { tasks = AccountDataReceivedHandlers.Select(x => x(accountDataEvent)).ToList(); await Task.WhenAll(tasks); @@ -124,4 +125,4 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg /// Event fired when an account data event is received /// </summary> public List<Func<StateEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); -} +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index c3684a1..37696eb 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -13,43 +13,41 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : RemoteHomeserver(baseUrl) { - public static async Task<T> Create<T>(string baseUrl, string accessToken) where T : AuthenticatedHomeserverGeneric { - var instance = Activator.CreateInstance(typeof(T), baseUrl, accessToken) as T +public class AuthenticatedHomeserverGeneric(string serverName, string accessToken) : RemoteHomeserver(serverName) { + public static async Task<T> Create<T>(string serverName, string accessToken, string? proxy = null) where T : AuthenticatedHomeserverGeneric { + var instance = Activator.CreateInstance(typeof(T), serverName, accessToken) as T ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}"); - var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); - + HomeserverResolverService.WellKnownUris? urls = null; + if(proxy is null) + urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(serverName); + instance.ClientHttpClient = new() { - BaseAddress = new Uri(urls.client - ?? throw new InvalidOperationException("Failed to resolve homeserver")), + BaseAddress = new Uri(proxy ?? urls?.Client + ?? throw new InvalidOperationException("Failed to resolve homeserver")), Timeout = TimeSpan.FromMinutes(15), DefaultRequestHeaders = { Authorization = new AuthenticationHeaderValue("Bearer", accessToken) } }; instance.ServerHttpClient = new() { - BaseAddress = new Uri(urls.server - ?? throw new InvalidOperationException("Failed to resolve homeserver")), + BaseAddress = new Uri(proxy ?? urls?.Server + ?? throw new InvalidOperationException("Failed to resolve homeserver")), Timeout = TimeSpan.FromMinutes(15), DefaultRequestHeaders = { Authorization = new AuthenticationHeaderValue("Bearer", accessToken) } }; + instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"); + + if (proxy is not null) { + instance.ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName); + instance.ServerHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName); + } + return instance; } - // Activator.CreateInstance(baseUrl, accessToken) { - // _httpClient = new() { - // BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) - // ?? throw new InvalidOperationException("Failed to resolve homeserver")), - // Timeout = TimeSpan.FromMinutes(15), - // DefaultRequestHeaders = { - // Authorization = new AuthenticationHeaderValue("Bearer", accessToken) - // } - // } - // }; - public WhoAmIResponse? WhoAmI { get; set; } public string? UserId => WhoAmI?.UserId; public string? UserLocalpart => UserId?.Split(":")[0][1..]; @@ -176,12 +174,6 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) #endregion - public string? ResolveMediaUri(string? mxcUri) { - if (mxcUri is null) return null; - if (mxcUri.StartsWith("https://")) return mxcUri; - return $"{ClientHttpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); - } - public async Task UpdateProfileAsync(UserProfileResponse? newProfile, bool preserveCustomRoomProfile = true) { if (newProfile is null) return; Console.WriteLine($"Updating profile for {WhoAmI.UserId} to {newProfile.ToJson(ignoreNull: true)} (preserving room profiles: {preserveCustomRoomProfile})"); @@ -247,7 +239,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) if (sync.Rooms is null) break; List<Task> tasks = new(); foreach (var (roomId, roomData) in sync.Rooms.Join) { - if (roomData.State is { Events: { Count: > 0 } }) { + if (roomData.State is { Events.Count: > 0 }) { var incommingRoomProfile = roomData.State?.Events?.FirstOrDefault(x => x.Type == "m.room.member" && x.StateKey == WhoAmI.UserId)?.TypedContent as RoomMemberEventContent; if (incommingRoomProfile is null) continue; diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index 0910cbe..15e5b65 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -6,11 +6,7 @@ namespace LibMatrix.Homeservers; public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { public readonly SynapseAdminApi Admin; - public class SynapseAdminApi { - private readonly AuthenticatedHomeserverGeneric _authenticatedHomeserver; - - public SynapseAdminApi(AuthenticatedHomeserverGeneric authenticatedHomeserver) => _authenticatedHomeserver = authenticatedHomeserver; - + public class SynapseAdminApi(AuthenticatedHomeserverSynapse authenticatedHomeserver) { public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) { AdminRoomListingResult? res = null; var i = 0; @@ -23,7 +19,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); - res = await _authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<AdminRoomListingResult>(url); + res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<AdminRoomListingResult>(url); totalRooms ??= res?.TotalRooms; Console.WriteLine(res.ToJson(false)); foreach (var room in res.Rooms) { @@ -101,7 +97,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { } } - public AuthenticatedHomeserverSynapse(string baseUrl, string accessToken) : base(baseUrl, accessToken) { + public AuthenticatedHomeserverSynapse(string serverName, string accessToken) : base(serverName, accessToken) { Admin = new(this); } } diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index 0757f6e..55a3a02 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -10,24 +10,31 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; public class RemoteHomeserver(string baseUrl) { - public static async Task<RemoteHomeserver> Create(string baseUrl) { - var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); - return new RemoteHomeserver(baseUrl) { - ClientHttpClient = new() { - BaseAddress = new Uri(urls.client ?? throw new InvalidOperationException("Failed to resolve homeserver")), - Timeout = TimeSpan.FromSeconds(120) - }, - ServerHttpClient = new() { - BaseAddress = new Uri(urls.server ?? throw new InvalidOperationException("Failed to resolve homeserver")), - Timeout = TimeSpan.FromSeconds(120) - } + public static async Task<RemoteHomeserver> Create(string baseUrl, string? proxy = null) { + var homeserver = new RemoteHomeserver(baseUrl); + homeserver.WellKnownUris = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); + homeserver.ClientHttpClient = new() { + BaseAddress = new Uri(proxy ?? homeserver.WellKnownUris.Client ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromSeconds(120) + }; + homeserver.ServerHttpClient = new() { + BaseAddress = new Uri(proxy ?? homeserver.WellKnownUris.Server ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromSeconds(120) }; + + if (proxy is not null) { + homeserver.ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); + homeserver.ServerHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); + } + + return homeserver; } private Dictionary<string, object> _profileCache { get; set; } = new(); public string BaseUrl { get; } = baseUrl; public MatrixHttpClient ClientHttpClient { get; set; } public MatrixHttpClient ServerHttpClient { get; set; } + public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } public async Task<UserProfileResponse> GetProfileAsync(string mxid) { if (mxid is null) throw new ArgumentNullException(nameof(mxid)); @@ -100,6 +107,13 @@ public class RemoteHomeserver(string baseUrl) { public async Task<ServerVersionResponse> GetServerVersionAsync() { return await ServerHttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version"); } + + + public string? ResolveMediaUri(string? mxcUri) { + if (mxcUri is null) return null; + if (mxcUri.StartsWith("https://")) return mxcUri; + return $"{ClientHttpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); + } } public class ServerVersionResponse { diff --git a/LibMatrix/Interfaces/EventContent.cs b/LibMatrix/Interfaces/EventContent.cs index 5cf0503..51671dd 100644 --- a/LibMatrix/Interfaces/EventContent.cs +++ b/LibMatrix/Interfaces/EventContent.cs @@ -13,7 +13,7 @@ public abstract class EventContent { [JsonPropertyName("m.in_reply_to")] public EventInReplyTo? InReplyTo { get; set; } - public abstract class EventInReplyTo { + public class EventInReplyTo { [JsonPropertyName("event_id")] public string EventId { get; set; } diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj index 805695b..e2e1433 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj @@ -5,6 +5,9 @@ <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <LangVersion>preview</LangVersion> + + <Optimize>true</Optimize> + <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> </PropertyGroup> <ItemGroup> diff --git a/LibMatrix/MatrixException.cs b/LibMatrix/MatrixException.cs index 3aaad19..f127abf 100644 --- a/LibMatrix/MatrixException.cs +++ b/LibMatrix/MatrixException.cs @@ -17,6 +17,9 @@ public class MatrixException : Exception { public int? RetryAfterMs { get; set; } public string RawContent { get; set; } + + public string? GetAsJson() => new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true); + public override string Message => $"{ErrorCode}: {ErrorCode switch { diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs index a9ef3be..82004fc 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs @@ -24,8 +24,8 @@ public class LoginResponse { public string UserId { get; set; } = null!; public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); - return await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(proxy ?? urls.client, AccessToken); + // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); + return await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); } } public class LoginRequest { diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 96bcefd..700e530 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -1,7 +1,9 @@ +using System.Diagnostics; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; using System.Web; +using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; @@ -96,22 +98,23 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?"); } - public async IAsyncEnumerable<StateEventResponse> GetMembersAsync(bool joinedOnly = true) { - // var res = GetFullStateAsync(); - // await foreach (var member in res) { - // if (member?.Type != "m.room.member") continue; - // if (joinedOnly && (member.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue; - // yield return member; - // } + var sw = Stopwatch.StartNew(); var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members"); + Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}"); var resText = await res.Content.ReadAsStringAsync(); - var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync()); + Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}"); + var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { + TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default + }); + Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); foreach (var resp in result.Chunk) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && (resp.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue; yield return resp; } + + Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}"); } #region Utility shortcuts @@ -153,7 +156,7 @@ public class GenericRoom { public async Task<RoomPowerLevelEventContent?> GetPowerLevelsAsync() => await GetStateAsync<RoomPowerLevelEventContent>("m.room.power_levels"); - public async Task<string> GetNameOrFallbackAsync() { + public async Task<string> GetNameOrFallbackAsync(int maxMemberNames = 2) { try { return await GetNameAsync(); } @@ -166,8 +169,9 @@ public class GenericRoom { memberList.Add((member.TypedContent is RoomMemberEventContent memberEvent ? memberEvent.DisplayName : "") ?? ""); memberCount = memberList.Count; memberList.RemoveAll(string.IsNullOrWhiteSpace); - if (memberList.Count >= 3) - return string.Join(", ", memberList.Take(2)) + " and " + (memberCount - 2) + " others."; + memberList = memberList.OrderBy(x => x).ToList(); + if (memberList.Count > maxMemberNames) + return string.Join(", ", memberList.Take(maxMemberNames)) + " and " + (memberCount - maxMemberNames) + " others."; return string.Join(", ", memberList); } catch { @@ -176,8 +180,15 @@ public class GenericRoom { } } + public async Task InviteUsersAsync(IEnumerable<string> users, string? reason = null, bool skipExisting = true) { + var tasks = users.Select(x => InviteUserAsync(x, reason, skipExisting)).ToList(); + await Task.WhenAll(tasks); + } + #endregion +#region Simple calls + public async Task ForgetAsync() => await _httpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null); @@ -198,6 +209,16 @@ public class GenericRoom { await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban", new UserIdAndReason { UserId = userId }); + public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) { + if (skipExisting && await GetStateAsync<RoomMemberEventContent>("m.room.member", userId) is not null) + return; + await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason)); + } + +#endregion + +#region Events + public async Task<EventIdResponse?> SendStateEventAsync(string eventType, object content) => await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content)) .Content.ReadFromJsonAsync<EventIdResponse>(); @@ -243,8 +264,6 @@ public class GenericRoom { } } - public readonly SpaceRoom AsSpace; - public async Task<T> GetEventAsync<T>(string eventId) { return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); } @@ -255,12 +274,30 @@ public class GenericRoom { $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync<EventIdResponse>())!; } - public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) { - if (skipExisting && await GetStateAsync<RoomMemberEventContent>("m.room.member", userId) is not null) - return; - await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason)); +#endregion + +#region Utilities + + public async Task<Dictionary<string, List<string>>> GetMembersByHomeserverAsync(bool joinedOnly = true) { + if (Homeserver is AuthenticatedHomeserverMxApiExtended mxaeHomeserver) + return await Homeserver.ClientHttpClient.GetFromJsonAsync<Dictionary<string, List<string>>>( + $"/_matrix/client/v3/rooms/{RoomId}/members_by_homeserver?joined_only={joinedOnly}"); + Dictionary<string, List<string>> roomHomeservers = new(); + var members = GetMembersAsync(); + await foreach (var member in members) { + string memberHs = member.StateKey.Split(':', 2)[1]; + roomHomeservers.TryAdd(memberHs, new()); + roomHomeservers[memberHs].Add(member.StateKey); + } + + Console.WriteLine($"Finished processing {RoomId}"); + return roomHomeservers; } +#endregion + + public readonly SpaceRoom AsSpace; + #region Disband room public async Task DisbandRoomAsync() { @@ -289,11 +326,6 @@ public class GenericRoom { } #endregion - - public async Task InviteUsersAsync(IEnumerable<string> users, string? reason = null, bool skipExisting = true) { - var tasks = users.Select(x => InviteUserAsync(x, reason, skipExisting)).ToList(); - await Task.WhenAll(tasks); - } } public class RoomIdResponse { diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index a43f518..5c8827c 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -6,15 +6,7 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Services; -public class HomeserverProviderService { - private readonly ILogger<HomeserverProviderService> _logger; - private readonly HomeserverResolverService _homeserverResolverService; - - public HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) { - _logger = logger; - _homeserverResolverService = homeserverResolverService; - } - +public class HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) { private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new(); private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeserverCache = new(); @@ -22,40 +14,44 @@ public class HomeserverProviderService { private static Dictionary<string, RemoteHomeserver> _remoteHomeserverCache = new(); public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null) { - var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver + accessToken, _ => new SemaphoreSlim(1, 1)); + var cacheKey = homeserver + accessToken + proxy; + var sem = _authenticatedHomeserverSemaphore.GetOrCreate(cacheKey, _ => new SemaphoreSlim(1, 1)); await sem.WaitAsync(); + AuthenticatedHomeserverGeneric? hs; lock (_authenticatedHomeserverCache) { - if (_authenticatedHomeserverCache.ContainsKey(homeserver + accessToken)) { + if (_authenticatedHomeserverCache.TryGetValue(cacheKey, out hs)) { sem.Release(); - return _authenticatedHomeserverCache[homeserver + accessToken]; + return hs; } } // var domain = proxy ?? (await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)).client; - var rhs = await RemoteHomeserver.Create(homeserver); - var serverVersion = await rhs.GetServerVersionAsync(); - + var rhs = await RemoteHomeserver.Create(homeserver, proxy); + var clientVersions = await rhs.GetClientVersionsAsync(); + if(proxy is not null) + Console.WriteLine($"Homeserver {homeserver} proxied via {proxy}..."); + Console.WriteLine($"{homeserver}: " + clientVersions.ToJson()); - AuthenticatedHomeserverGeneric hs; - if (true) { - hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverMxApiExtended>(homeserver, accessToken); - } + if (clientVersions.UnstableFeatures.TryGetValue("gay.rory.mxapiextensions.v0", out bool a) && a) + hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverMxApiExtended>(homeserver, accessToken, proxy); else { - hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverSynapse>(homeserver, accessToken); + var serverVersion = await rhs.GetServerVersionAsync(); + if (serverVersion is { Server.Name: "Synapse" }) + hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverSynapse>(homeserver, accessToken, proxy); + else + hs = await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(homeserver, accessToken, proxy); } - // (() => hs.WhoAmI) = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!; - lock (_authenticatedHomeserverCache) - _authenticatedHomeserverCache[homeserver + accessToken] = hs; + _authenticatedHomeserverCache[cacheKey] = hs; sem.Release(); return hs; } public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null) { - var hs = await RemoteHomeserver.Create(proxy ?? homeserver); + var hs = await RemoteHomeserver.Create(homeserver, proxy); // hs._httpClient.Dispose(); // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.ServerName) }; // hs._httpClient.Timeout = TimeSpan.FromSeconds(120); diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs index 06771b0..c8b6bb7 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Text.Json; using ArcaneLibs.Extensions; using LibMatrix.Extensions; @@ -8,13 +9,11 @@ namespace LibMatrix.Services; public class HomeserverResolverService(ILogger<HomeserverResolverService>? logger = null) { private readonly MatrixHttpClient _httpClient = new(); - private static readonly Dictionary<string, (string, string)> _wellKnownCache = new(); - private static readonly Dictionary<string, SemaphoreSlim> _wellKnownSemaphores = new(); + private static readonly ConcurrentDictionary<string, WellKnownUris> _wellKnownCache = new(); + private static readonly ConcurrentDictionary<string, SemaphoreSlim> _wellKnownSemaphores = new(); - public async Task<(string client, string server)> ResolveHomeserverFromWellKnown(string homeserver) { + public async Task<WellKnownUris> ResolveHomeserverFromWellKnown(string homeserver) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); - // if(!_wellKnownSemaphores.ContainsKey(homeserver)) - // _wellKnownSemaphores[homeserver] = new(1, 1); _wellKnownSemaphores.TryAdd(homeserver, new(1, 1)); await _wellKnownSemaphores[homeserver].WaitAsync(); if (_wellKnownCache.TryGetValue(homeserver, out var known)) { @@ -23,11 +22,11 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge } logger?.LogInformation("Resolving homeserver: {}", homeserver); - var res = ( - await _tryResolveFromClientWellknown(homeserver), - await _tryResolveFromServerWellknown(homeserver) - ); - _wellKnownCache.Add(homeserver, res!); + var res = new WellKnownUris { + Client = await _tryResolveFromClientWellknown(homeserver), + Server = await _tryResolveFromServerWellknown(homeserver) + }; + _wellKnownCache.TryAdd(homeserver, res); _wellKnownSemaphores[homeserver].Release(); return res; } @@ -54,6 +53,11 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge return hs; } + // fallback: most servers host these on the same location + var clientUrl = await _tryResolveFromClientWellknown(homeserver); + if (clientUrl is not null && await _httpClient.CheckSuccessStatus($"{clientUrl}/_matrix/federation/v1/version")) + return clientUrl; + logger?.LogInformation("No server well-known..."); return null; } @@ -62,7 +66,12 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); if (mxc is null) throw new ArgumentNullException(nameof(mxc)); if (!mxc.StartsWith("mxc://")) throw new InvalidDataException("mxc must start with mxc://"); - homeserver = (await ResolveHomeserverFromWellKnown(homeserver)).client; + homeserver = (await ResolveHomeserverFromWellKnown(homeserver)).Client; return mxc.Replace("mxc://", $"{homeserver}/_matrix/media/v3/download/"); } + + public class WellKnownUris { + public string? Client { get; set; } + public string? Server { get; set; } + } } diff --git a/LibMatrix/Services/TieredStorageService.cs b/LibMatrix/Services/TieredStorageService.cs index f242785..280340e 100644 --- a/LibMatrix/Services/TieredStorageService.cs +++ b/LibMatrix/Services/TieredStorageService.cs @@ -2,12 +2,7 @@ using LibMatrix.Interfaces.Services; namespace LibMatrix.Services; -public class TieredStorageService { - public IStorageProvider? CacheStorageProvider { get; } - public IStorageProvider? DataStorageProvider { get; } - - public TieredStorageService(IStorageProvider? cacheStorageProvider, IStorageProvider? dataStorageProvider) { - CacheStorageProvider = cacheStorageProvider; - DataStorageProvider = dataStorageProvider; - } +public class TieredStorageService(IStorageProvider? cacheStorageProvider, IStorageProvider? dataStorageProvider) { + public IStorageProvider? CacheStorageProvider { get; } = cacheStorageProvider; + public IStorageProvider? DataStorageProvider { get; } = dataStorageProvider; } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index dbb3401..3e8c4b5 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -36,6 +36,7 @@ public class StateEvent { return eventType ?? typeof(UnknownEventContent); } + [JsonIgnore] public EventContent TypedContent { get { if(Type == "m.receipt") { @@ -134,6 +135,7 @@ public class StateEvent { public string cdtype => TypedContent.GetType().Name; } + public class StateEventResponse : StateEvent { [JsonPropertyName("origin_server_ts")] public ulong OriginServerTs { get; set; } @@ -150,8 +152,8 @@ public class StateEventResponse : StateEvent { [JsonPropertyName("event_id")] public string EventId { get; set; } - [JsonPropertyName("user_id")] - public string UserId { get; set; } + // [JsonPropertyName("user_id")] + // public string UserId { get; set; } [JsonPropertyName("replaces_state")] public new string ReplacesState { get; set; } @@ -177,6 +179,12 @@ public class StateEventResponse : StateEvent { } } +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ChunkedStateEventResponse))] +internal partial class ChunkedStateEventResponseSerializerContext : JsonSerializerContext +{ +} + public class EventList { [JsonPropertyName("events")] public List<StateEventResponse>? Events { get; set; } = new(); diff --git a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs index cece41b..804ad6c 100644 --- a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs +++ b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs @@ -21,7 +21,7 @@ public class ResolverTest : TestBed<TestFixture> { public async Task ResolveServer() { foreach (var (domain, expected) in _config.ExpectedHomeserverMappings) { var server = await _resolver.ResolveHomeserverFromWellKnown(domain); - Assert.Equal(expected, server.client); + Assert.Equal(expected, server.Client); } } diff --git a/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs b/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs index 4dbee54..81753d1 100644 --- a/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs +++ b/Utilities/LibMatrix.DebugDataValidationApi/Controllers/ValidationController.cs @@ -6,12 +6,8 @@ namespace LibMatrix.DebugDataValidationApi.Controllers; [ApiController] [Route("/")] -public class ValidationController : ControllerBase { - private readonly ILogger<ValidationController> _logger; - - public ValidationController(ILogger<ValidationController> logger) { - _logger = logger; - } +public class ValidationController(ILogger<ValidationController> logger) : ControllerBase { + private readonly ILogger<ValidationController> _logger = logger; [HttpPost("/validate/{type}")] public Task<bool> Get([FromRoute] string type, [FromBody] JsonElement content) { |