diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs
new file mode 100644
index 0000000..cbe5b0a
--- /dev/null
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics;
+using System.Net.Http.Json;
+using ArcaneLibs.Collections;
+using LibMatrix.Extensions;
+
+namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
+
+public class BaseWellKnownResolver<T> where T : class, new() {
+ internal static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<T>> WellKnownCache = new() {
+ StoreNulls = false
+ };
+
+ internal static readonly MatrixHttpClient HttpClient = new();
+
+ internal async Task<WellKnownResolverService.WellKnownResolutionResult<T>> TryGetWellKnownFromUrl(string url,
+ WellKnownResolverService.WellKnownSource source) {
+ var sw = Stopwatch.StartNew();
+ try {
+ var request = await HttpClient.GetAsync(url);
+ sw.Stop();
+ var result = new WellKnownResolverService.WellKnownResolutionResult<T> {
+ Content = await request.Content.ReadFromJsonAsync<T>(),
+ Source = source,
+ SourceUri = url,
+ Warnings = []
+ };
+
+ if (sw.ElapsedMilliseconds > 1000) {
+ // logger.LogWarning($"Support well-known resolution took {sw.ElapsedMilliseconds}ms: {url}");
+ result.Warnings.Add(new() {
+ Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.SlowResponse,
+ Message = $"Well-known resolution took {sw.ElapsedMilliseconds}ms"
+ });
+ }
+
+ return result;
+ }
+ catch (Exception e) {
+ return new WellKnownResolverService.WellKnownResolutionResult<T> {
+ Source = source,
+ SourceUri = url,
+ Warnings = [
+ new() {
+ Exception = e,
+ Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.Exception,
+ Message = e.Message
+ }
+ ]
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
new file mode 100644
index 0000000..f8de38d
--- /dev/null
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+using ArcaneLibs.Collections;
+using LibMatrix.Extensions;
+using Microsoft.Extensions.Logging;
+using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown;
+using ResultType =
+ LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown?>;
+
+namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
+
+public class ClientWellKnownResolver(ILogger<ClientWellKnownResolver> logger, WellKnownResolverConfiguration configuration)
+ : BaseWellKnownResolver<ClientWellKnown> {
+ private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> ClientWellKnownCache = new() {
+ StoreNulls = false
+ };
+
+ private static readonly MatrixHttpClient HttpClient = new();
+
+ public Task<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) {
+ config ??= configuration;
+ return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => {
+ logger.LogTrace($"Resolving client well-known: {homeserver}");
+
+ WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown> result =
+ await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/client", WellKnownResolverService.WellKnownSource.Https);
+ if (result.Content != null) return result;
+
+
+ return result;
+ });
+ }
+}
+
+public class ClientWellKnown {
+ [JsonPropertyName("m.homeserver")]
+ public WellKnownHomeserver Homeserver { get; set; }
+
+ public class WellKnownHomeserver {
+ [JsonPropertyName("base_url")]
+ public required string BaseUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
new file mode 100644
index 0000000..a99185c
--- /dev/null
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
@@ -0,0 +1,38 @@
+using System.Text.Json.Serialization;
+using ArcaneLibs.Collections;
+using LibMatrix.Extensions;
+using Microsoft.Extensions.Logging;
+using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown;
+using ResultType =
+ LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown?>;
+
+namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
+
+public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, WellKnownResolverConfiguration configuration)
+ : BaseWellKnownResolver<ServerWellKnown> {
+ private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> ClientWellKnownCache = new() {
+ StoreNulls = false
+ };
+
+ private static readonly MatrixHttpClient HttpClient = new();
+
+ public Task<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) {
+ config ??= configuration;
+ return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => {
+ logger.LogTrace($"Resolving client well-known: {homeserver}");
+
+ WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown> result =
+ await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/server", WellKnownResolverService.WellKnownSource.Https);
+ if (result.Content != null) return result;
+
+
+ return result;
+ });
+ }
+}
+
+
+public class ServerWellKnown {
+ [JsonPropertyName("m.server")]
+ public string Homeserver { get; set; }
+}
\ No newline at end of file
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs
new file mode 100644
index 0000000..99313db
--- /dev/null
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs
@@ -0,0 +1,44 @@
+using System.Diagnostics;
+using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Logging;
+using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown;
+using ResultType = LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<
+ LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown?
+>;
+
+namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
+
+public class SupportWellKnownResolver(ILogger<SupportWellKnownResolver> logger, WellKnownResolverConfiguration configuration) : BaseWellKnownResolver<WellKnownType> {
+ public Task<ResultType> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) {
+ config ??= configuration;
+ return WellKnownCache.TryGetOrAdd(homeserver, async () => {
+ logger.LogTrace($"Resolving support well-known: {homeserver}");
+
+ ResultType result = await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/support", WellKnownResolverService.WellKnownSource.Https);
+ if (result.Content != null)
+ return result;
+
+ return null;
+ });
+ }
+}
+
+public class SupportWellKnown {
+ [JsonPropertyName("contacts")]
+ public List<WellKnownContact>? Contacts { get; set; }
+
+ [JsonPropertyName("support_page")]
+ public Uri? SupportPage { get; set; }
+
+ public class WellKnownContact {
+ [JsonPropertyName("email_address")]
+ public string? EmailAddress { get; set; }
+
+ [JsonPropertyName("matrix_id")]
+ public string? MatrixId { get; set; }
+
+ [JsonPropertyName("role")]
+ public required string Role { get; set; }
+ }
+}
\ No newline at end of file
|