diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs b/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
index 3e4a5cd..558e4fc 100644
--- a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
+++ b/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs
@@ -4,17 +4,17 @@ using LibMatrix.Interfaces;
namespace LibMatrix.EventTypes.Spec.State;
[MatrixEvent(EventName = "m.presence")]
-public class PresenceEventContent : TimelineEventContent {
- [JsonPropertyName("presence")]
- public string Presence { get; set; }
+public class PresenceEventContent : EventContent {
+ [JsonPropertyName("presence"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Presence { get; set; }
[JsonPropertyName("last_active_ago")]
public long LastActiveAgo { get; set; }
[JsonPropertyName("currently_active")]
public bool CurrentlyActive { get; set; }
- [JsonPropertyName("status_msg")]
- public string StatusMessage { get; set; }
- [JsonPropertyName("avatar_url")]
- public string AvatarUrl { get; set; }
- [JsonPropertyName("displayname")]
- public string DisplayName { get; set; }
+ [JsonPropertyName("status_msg"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? StatusMessage { get; set; }
+ [JsonPropertyName("avatar_url"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? AvatarUrl { get; set; }
+ [JsonPropertyName("displayname"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? DisplayName { get; set; }
}
diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
index 1938b3e..8a22489 100644
--- a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
+++ b/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs
@@ -17,16 +17,29 @@ public class RoomMessageEventContent : TimelineEventContent {
public string MessageType { get; set; } = "m.notice";
[JsonPropertyName("formatted_body")]
- public string FormattedBody { get; set; }
+ public string? FormattedBody { get; set; }
[JsonPropertyName("format")]
- public string Format { get; set; }
+ public string? Format { get; set; }
/// <summary>
/// Media URI for this message, if any
/// </summary>
[JsonPropertyName("url")]
public string? Url { get; set; }
-
+
public string? FileName { get; set; }
+
+ [JsonPropertyName("info")]
+ public FileInfoStruct? FileInfo { get; set; }
+
+ public class FileInfoStruct {
+ [JsonPropertyName("mimetype")]
+ public string? MimeType { get; set; }
+ [JsonPropertyName("size")]
+ public long Size { get; set; }
+ [JsonPropertyName("thumbnail_url")]
+ public string? ThumbnailUrl { get; set; }
+ }
+
}
diff --git a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 63f148c..757a9e9 100644
--- a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
+++ b/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -3,10 +3,23 @@ using LibMatrix.Interfaces;
namespace LibMatrix.EventTypes.Spec.State;
-[MatrixEvent(EventName = "m.policy.rule.user")]
-[MatrixEvent(EventName = "m.policy.rule.server")]
-[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server")]
-public class PolicyRuleEventContent : TimelineEventContent {
+//spec
+[MatrixEvent(EventName = "m.policy.rule.server")] //spec
+[MatrixEvent(EventName = "m.room.rule.server")] //???
+[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server")] //legacy
+public class ServerPolicyRuleEventContent : PolicyRuleEventContent { }
+
+[MatrixEvent(EventName = "m.policy.rule.user")] //spec
+[MatrixEvent(EventName = "m.room.rule.user")] //???
+[MatrixEvent(EventName = "org.matrix.mjolnir.rule.user")] //legacy
+public class UserPolicyRuleEventContent : PolicyRuleEventContent { }
+
+[MatrixEvent(EventName = "m.policy.rule.room")] //spec
+[MatrixEvent(EventName = "m.room.rule.room")] //???
+[MatrixEvent(EventName = "org.matrix.mjolnir.rule.room")] //legacy
+public class RoomPolicyRuleEventContent : PolicyRuleEventContent { }
+
+public abstract class PolicyRuleEventContent : EventContent {
/// <summary>
/// Entity this ban applies to, can use * and ? as globs.
/// </summary>
@@ -52,4 +65,4 @@ public static class PolicyRecommendationTypes {
/// Mute this user
/// </summary>
public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs
index 913864e..6f27f71 100644
--- a/LibMatrix/Extensions/HttpClientExtensions.cs
+++ b/LibMatrix/Extensions/HttpClientExtensions.cs
@@ -36,12 +36,11 @@ public class MatrixHttpClient : HttpClient {
return options;
}
- public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
- CancellationToken cancellationToken) {
+ public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null");
if (AssertedUserId is not null) request.RequestUri = request.RequestUri.AddQuery("user_id", AssertedUserId);
- // Console.WriteLine($"Sending request to {request.RequestUri}");
+ Console.WriteLine($"Sending request to {request.RequestUri}");
try {
var webAssemblyEnableStreamingResponseKey =
@@ -60,7 +59,7 @@ public class MatrixHttpClient : HttpClient {
catch (Exception e) {
typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(request, 0);
- await Task.Delay(2500);
+ await Task.Delay(2500, cancellationToken);
return await SendAsync(request, cancellationToken);
}
@@ -123,7 +122,7 @@ public class MatrixHttpClient : HttpClient {
var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine($"Sending PUT {requestUri}");
- Console.WriteLine($"Content: {value.ToJson()}");
+ Console.WriteLine($"Content: {JsonSerializer.Serialize(value, value.GetType(), options)}");
Console.WriteLine($"Type: {value.GetType().FullName}");
request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType(), options),
Encoding.UTF8, "application/json");
diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs
index d252e85..3ebc9a2 100644
--- a/LibMatrix/Helpers/MessageFormatter.cs
+++ b/LibMatrix/Helpers/MessageFormatter.cs
@@ -35,6 +35,11 @@ public static class MessageFormatter {
public static string HtmlFormatMention(string id, string? displayName = null) {
return $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>";
}
+
+ public static string HtmlFormatMessageLink(string roomId, string eventId, string[]? servers = null, string? displayName = null) {
+ if (servers is not { Length: > 0 }) servers = new[] { roomId.Split(':', 2)[1] };
+ return $"<a href=\"https://matrix.to/#/{roomId}/{eventId}?via={string.Join("&via=", servers)}\">{displayName ?? eventId}</a>";
+ }
#region Extension functions
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index e4b7483..288608d 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -3,6 +3,7 @@ using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using System.Web;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec.State;
using LibMatrix.Filters;
@@ -18,7 +19,7 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
var instance = Activator.CreateInstance(typeof(T), serverName, accessToken) as T
?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}");
HomeserverResolverService.WellKnownUris? urls = null;
- if(proxy is null)
+ if (proxy is null)
urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(serverName);
instance.ClientHttpClient = new() {
@@ -78,7 +79,11 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
}
public virtual async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
- var res = await ClientHttpClient.PostAsync($"/_matrix/media/v3/upload?filename={fileName}", new StreamContent(fileStream));
+ var req = new HttpRequestMessage(HttpMethod.Post, $"/_matrix/media/v3/upload?filename={fileName}");
+ req.Content = new StreamContent(fileStream);
+ req.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
+ var res = await ClientHttpClient.SendAsync(req);
+
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
@@ -283,6 +288,17 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
}
}
+ public async Task<RoomIdResponse> JoinRoomAsync(string roomId, List<string> homeservers = null, string? reason = null) {
+ var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(roomId)}";
+ Console.WriteLine($"Calling {join_url} with {homeservers?.Count ?? 0} via's...");
+ if (homeservers == null || homeservers.Count == 0) homeservers = new() { roomId.Split(':')[1] };
+ var fullJoinUrl = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
+ var res = await ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new {
+ reason
+ });
+ return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?");
+ }
+
#region Room Profile Utility
private async Task<KeyValuePair<string, RoomMemberEventContent>> GetOwnRoomProfileWithIdAsync(GenericRoom room) {
diff --git a/LibMatrix/Interfaces/EventContent.cs b/LibMatrix/Interfaces/EventContent.cs
index ec09c7e..1fb6974 100644
--- a/LibMatrix/Interfaces/EventContent.cs
+++ b/LibMatrix/Interfaces/EventContent.cs
@@ -1,21 +1,42 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace LibMatrix.Interfaces;
-public abstract class EventContent {
-
-}
+public abstract class EventContent { }
+
public abstract class TimelineEventContent : EventContent {
[JsonPropertyName("m.relates_to")]
public MessageRelatesTo? RelatesTo { get; set; }
- // [JsonPropertyName("m.new_content")]
- // public TimelineEventContent? NewContent { get; set; }
+ [JsonPropertyName("m.new_content")]
+ public JsonObject? NewContent { get; set; }
+
+ public TimelineEventContent SetReplaceRelation(string eventId) {
+ NewContent = JsonSerializer.SerializeToNode(this, GetType()).AsObject();
+ // NewContent = JsonSerializer.Deserialize(jsonText, GetType());
+ RelatesTo = new() {
+ RelationType = "m.replace",
+ EventId = eventId
+ };
+ return this;
+ }
+
+ public T SetReplaceRelation<T>(string eventId) where T : TimelineEventContent {
+ return SetReplaceRelation(eventId) as T ?? throw new InvalidOperationException();
+ }
public class MessageRelatesTo {
[JsonPropertyName("m.in_reply_to")]
public EventInReplyTo? InReplyTo { get; set; }
+ [JsonPropertyName("event_id")]
+ public string? EventId { get; set; }
+
+ [JsonPropertyName("rel_type")]
+ public string? RelationType { get; set; }
+
public class EventInReplyTo {
[JsonPropertyName("event_id")]
public string EventId { get; set; }
@@ -24,4 +45,4 @@ public abstract class TimelineEventContent : EventContent {
public string RelType { get; set; }
}
}
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index bfb3069..690556f 100644
--- a/LibMatrix/LibMatrix.csproj
+++ b/LibMatrix/LibMatrix.csproj
@@ -11,8 +11,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0"/>
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1"/>
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 4267094..85db517 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -81,9 +81,48 @@ public class CreateRoomRequest {
return errors;
}
+ public static CreateRoomRequest CreatePublic(AuthenticatedHomeserverGeneric hs, string? name = null, string? roomAliasName = null) {
+ var request = new CreateRoomRequest {
+ Name = name ?? "New public Room",
+ Visibility = "public",
+ CreationContent = new(),
+ PowerLevelContentOverride = new() {
+ EventsDefault = 0,
+ UsersDefault = 0,
+ Kick = 50,
+ Ban = 50,
+ Invite = 25,
+ StateDefault = 10,
+ Redact = 50,
+ NotificationsPl = new() {
+ Room = 10
+ },
+ Events = new() {
+ { "m.room.avatar", 50 },
+ { "m.room.canonical_alias", 50 },
+ { "m.room.encryption", 100 },
+ { "m.room.history_visibility", 100 },
+ { "m.room.name", 50 },
+ { "m.room.power_levels", 100 },
+ { "m.room.server_acl", 100 },
+ { "m.room.tombstone", 100 }
+ },
+ Users = new() {
+ {
+ hs.UserId,
+ 101
+ }
+ }
+ },
+ RoomAliasName = roomAliasName,
+ InitialState = new()
+ };
+
+ return request;
+ }
public static CreateRoomRequest CreatePrivate(AuthenticatedHomeserverGeneric hs, string? name = null, string? roomAliasName = null) {
var request = new CreateRoomRequest {
- Name = name ?? "Private Room",
+ Name = name ?? "New private Room",
Visibility = "private",
CreationContent = new(),
PowerLevelContentOverride = new() {
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index b81713a..d26b1f8 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -29,13 +29,16 @@ public class GenericRoom {
public string RoomId { get; set; }
public async IAsyncEnumerable<StateEventResponse?> GetFullStateAsync() {
- var result = _httpClient.GetAsyncEnumerableFromJsonAsync<StateEventResponse>(
- $"/_matrix/client/v3/rooms/{RoomId}/state");
+ var result = _httpClient.GetAsyncEnumerableFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/state");
await foreach (var resp in result) {
yield return resp;
}
}
+ public async Task<List<StateEventResponse>> GetFullStateAsListAsync() {
+ return await _httpClient.GetFromJsonAsync<List<StateEventResponse>>($"/_matrix/client/v3/rooms/{RoomId}/state");
+ }
+
public async Task<T?> GetStateAsync<T>(string type, string stateKey = "") {
var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
if (!string.IsNullOrEmpty(type)) url += $"/{type}";
@@ -77,17 +80,82 @@ public class GenericRoom {
}
}
- public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b",
- string filter = "") {
- var url = $"/_matrix/client/v3/rooms/{RoomId}/messages?from={from}&limit={limit}&dir={dir}";
- if (!string.IsNullOrEmpty(filter)) url += $"&filter={filter}";
+ public async Task<MessagesResponse> GetMessagesAsync(string from = "", int? limit = null, string dir = "b", string filter = "") {
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/messages?dir={dir}";
+ if (!string.IsNullOrWhiteSpace(from)) url += $"&from={from}";
+ if (limit is not null) url += $"&limit={limit}";
+ if (!string.IsNullOrWhiteSpace(filter)) url += $"&filter={filter}";
var res = await _httpClient.GetFromJsonAsync<MessagesResponse>(url);
return res ?? new MessagesResponse();
}
+ /// <summary>
+ /// Same as <see cref="GetMessagesAsync"/>, except keeps fetching more responses until the beginning of the room is found, or the target message limit is reached
+ /// </summary>
+ public async IAsyncEnumerable<MessagesResponse> GetManyMessagesAsync(string from = "", int limit = 100, string dir = "b", string filter = "", bool includeState = true,
+ bool fixForward = false) {
+ if (dir == "f" && fixForward) {
+ var concat = new List<MessagesResponse>();
+ while (true) {
+ var resp = await GetMessagesAsync(from, int.MaxValue, "b", filter);
+ concat.Add(resp);
+ if (!includeState)
+ resp.State.Clear();
+ from = resp.End;
+ if (resp.End is null) break;
+ }
+
+ concat.Reverse();
+ foreach (var eventResponse in concat) {
+ limit -= eventResponse.State.Count + eventResponse.Chunk.Count;
+ while (limit < 0) {
+ if (eventResponse.State.Count > 0 && eventResponse.State.Max(x => x.OriginServerTs) > eventResponse.Chunk.Max(x => x.OriginServerTs))
+ eventResponse.State.Remove(eventResponse.State.MaxBy(x => x.OriginServerTs));
+ else
+ eventResponse.Chunk.Remove(eventResponse.Chunk.MaxBy(x => x.OriginServerTs));
+
+ limit++;
+ }
+
+ eventResponse.Chunk.Reverse();
+ eventResponse.State.Reverse();
+ yield return eventResponse;
+ if (limit <= 0) yield break;
+ }
+ }
+ else {
+ while (limit > 0) {
+ var resp = await GetMessagesAsync(from, limit, dir, filter);
+
+ if (!includeState)
+ resp.State.Clear();
+
+ limit -= resp.Chunk.Count + resp.State.Count;
+ from = resp.End;
+ yield return resp;
+ if (resp.End is null) {
+ Console.WriteLine("End is null");
+ yield break;
+ }
+ }
+ }
+
+ Console.WriteLine("End of GetManyAsync");
+ }
+
public async Task<string?> GetNameAsync() => (await GetStateAsync<RoomNameEventContent>("m.room.name"))?.Name;
- public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null) {
+ public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null, bool checkIfAlreadyMember = true) {
+ if (checkIfAlreadyMember) {
+ try {
+ var ce = await GetCreateEventAsync();
+ return new() {
+ RoomId = RoomId
+ };
+ }
+ catch { } //ignore
+ }
+
var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}";
Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's...");
if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':')[1] };
@@ -235,13 +303,17 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<EventIdResponse>();
}
- public async Task<EventIdResponse?> SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file") {
+ public async Task<EventIdResponse?> SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file", string contentType = "application/octet-stream") {
var url = await Homeserver.UploadFile(fileName, fileStream);
var content = new RoomMessageEventContent() {
MessageType = messageType,
Url = url,
Body = fileName,
FileName = fileName,
+ FileInfo = new() {
+ Size = fileStream.Length,
+ MimeType = contentType
+ }
};
return await SendTimelineEventAsync("m.room.message", content);
}
diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index c8b6bb7..556bf86 100644
--- a/LibMatrix/Services/HomeserverResolverService.cs
+++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -7,7 +7,9 @@ using Microsoft.Extensions.Logging;
namespace LibMatrix.Services;
public class HomeserverResolverService(ILogger<HomeserverResolverService>? logger = null) {
- private readonly MatrixHttpClient _httpClient = new();
+ private readonly MatrixHttpClient _httpClient = new() {
+ Timeout = TimeSpan.FromMilliseconds(10000)
+ };
private static readonly ConcurrentDictionary<string, WellKnownUris> _wellKnownCache = new();
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _wellKnownSemaphores = new();
@@ -20,7 +22,7 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
_wellKnownSemaphores[homeserver].Release();
return known;
}
-
+
logger?.LogInformation("Resolving homeserver: {}", homeserver);
var res = new WellKnownUris {
Client = await _tryResolveFromClientWellknown(homeserver),
@@ -33,11 +35,12 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
private async Task<string?> _tryResolveFromClientWellknown(string homeserver) {
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
- if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) {
+ try {
var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/client");
var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
return hs;
}
+ catch { }
logger?.LogInformation("No client well-known...");
return null;
@@ -45,13 +48,14 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
private async Task<string?> _tryResolveFromServerWellknown(string homeserver) {
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
- if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) {
+ try {
var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server");
var hs = resp.GetProperty("m.server").GetString();
if (!hs.StartsWithAnyOf("http://", "https://"))
hs = $"https://{hs}";
return hs;
}
+ catch { }
// fallback: most servers host these on the same location
var clientUrl = await _tryResolveFromClientWellknown(homeserver);
@@ -74,4 +78,4 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge
public string? Client { get; set; }
public string? Server { get; set; }
}
-}
+}
\ No newline at end of file
|