diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 55899de..916780e 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -4,6 +4,7 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Web;
+using ArcaneLibs.Collections;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec;
using LibMatrix.EventTypes.Spec.State.RoomInfo;
@@ -13,6 +14,7 @@ using LibMatrix.Homeservers.Extensions.NamedCaches;
using LibMatrix.Responses;
using LibMatrix.RoomTypes;
using LibMatrix.Services;
+using LibMatrix.StructuredData;
using LibMatrix.Utilities;
namespace LibMatrix.Homeservers;
@@ -145,7 +147,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
await Task.Delay(1000);
}
}
- }).ToAsyncEnumerable();
+ }).ToAsyncResultEnumerable();
await foreach (var result in tasks)
if (result is not null)
@@ -215,7 +217,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
if (preserveCustomRoomProfile) {
var rooms = await GetJoinedRooms();
- var roomProfiles = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncEnumerable();
+ var roomProfiles = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncResultEnumerable();
targetSyncCount = rooms.Count;
await foreach (var (roomId, currentRoomProfile) in roomProfiles)
try {
@@ -288,14 +290,26 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
public async IAsyncEnumerable<KeyValuePair<string, RoomMemberEventContent>> GetRoomProfilesAsync() {
var rooms = await GetJoinedRooms();
- var results = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncEnumerable();
+ var results = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncResultEnumerable();
await foreach (var res in results) yield return res;
}
public async Task<RoomIdResponse> JoinRoomAsync(string roomId, List<string> homeservers = null, string? reason = null) {
var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(roomId)}";
Console.WriteLine($"Calling {joinUrl} with {homeservers?.Count ?? 0} via's...");
- if (homeservers == null || homeservers.Count == 0) homeservers = new List<string> { roomId.Split(':')[1] };
+ if (homeservers is not { Count: > 0 }) {
+ // Legacy room IDs: !abc:server.xyz
+ if (roomId.Contains(':'))
+ homeservers = [ServerName, roomId.Split(':')[1]];
+ // v12+ room IDs: !<hash>
+ else {
+ homeservers = [ServerName];
+ foreach (var room in await GetJoinedRooms()) {
+ homeservers.Add(await room.GetOriginHomeserverAsync());
+ }
+ }
+ }
+
var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", homeservers);
var res = await ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new {
reason
@@ -397,15 +411,12 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
private Dictionary<string, string>? _namedFilterCache;
private Dictionary<string, SyncFilter> _filterCache = new();
- public async Task<JsonObject?> GetCapabilitiesAsync() {
- var res = await ClientHttpClient.GetAsync("/_matrix/client/v3/capabilities");
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to get capabilities: {await res.Content.ReadAsStringAsync()}");
- throw new InvalidDataException($"Failed to get capabilities: {await res.Content.ReadAsStringAsync()}");
- }
+ private static readonly SemaphoreCache<CapabilitiesResponse> CapabilitiesCache = new();
- return await res.Content.ReadFromJsonAsync<JsonObject>();
- }
+ public async Task<CapabilitiesResponse> GetCapabilitiesAsync() =>
+ await CapabilitiesCache.GetOrAdd(ServerName, async () =>
+ await ClientHttpClient.GetFromJsonAsync<CapabilitiesResponse>("/_matrix/client/v3/capabilities")
+ );
public class HsNamedCaches {
internal HsNamedCaches(AuthenticatedHomeserverGeneric hs) {
@@ -429,7 +440,8 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
try {
// Console.WriteLine($"Trying authenticated media URL: {uri}");
var res = await ClientHttpClient.SendAsync(new() {
- Method = HttpMethod.Head,
+ // Method = HttpMethod.Head, // This apparently doesn't work with Matrix-Media-Repo...
+ Method = HttpMethod.Get,
RequestUri = (new Uri(mxcUri.ToDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute))
});
if (res.IsSuccessStatusCode) {
@@ -445,7 +457,8 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
try {
// Console.WriteLine($"Trying legacy media URL: {uri}");
var res = await ClientHttpClient.SendAsync(new() {
- Method = HttpMethod.Head,
+ // Method = HttpMethod.Head,
+ Method = HttpMethod.Get,
RequestUri = new(mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute)
});
if (res.IsSuccessStatusCode) {
@@ -574,8 +587,87 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
await SetAccountDataAsync(IgnoredUserListEventContent.EventId, ignoredUserList);
}
- private class CapabilitiesResponse {
+ public class CapabilitiesResponse {
[JsonPropertyName("capabilities")]
- public Dictionary<string, object>? Capabilities { get; set; }
+ public CapabilitiesContents Capabilities { get; set; }
+
+ public class CapabilitiesContents {
+ [JsonPropertyName("m.3pid_changes")]
+ public BooleanCapability? ThreePidChanges { get; set; }
+
+ [JsonPropertyName("m.change_password")]
+ public BooleanCapability? ChangePassword { get; set; }
+
+ [JsonPropertyName("m.get_login_token")]
+ public BooleanCapability? GetLoginToken { get; set; }
+
+ [JsonPropertyName("m.room_versions")]
+ public RoomVersionsCapability? RoomVersions { get; set; }
+
+ [JsonPropertyName("m.set_avatar_url")]
+ public BooleanCapability? SetAvatarUrl { get; set; }
+
+ [JsonPropertyName("m.set_displayname")]
+ public BooleanCapability? SetDisplayName { get; set; }
+
+ [JsonPropertyName("gay.rory.bulk_send_events")]
+ public BooleanCapability? BulkSendEvents { get; set; }
+
+ [JsonPropertyName("gay.rory.synapse_admin_extensions.room_list.query_events.v2")]
+ public BooleanCapability? SynapseRoomListQueryEventsV2 { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object>? AdditionalCapabilities { get; set; }
+ }
+
+ public class BooleanCapability {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; }
+ }
+
+ public class RoomVersionsCapability {
+ [JsonPropertyName("default")]
+ public string? Default { get; set; }
+
+ [JsonPropertyName("available")]
+ public Dictionary<string, string>? Available { get; set; }
+ }
+ }
+
+#region Room Directory/aliases
+
+ public async Task SetRoomAliasAsync(string roomAlias, string roomId) {
+ var resp = await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/directory/room/{HttpUtility.UrlEncode(roomAlias)}", new RoomIdResponse() {
+ RoomId = roomId
+ });
+ if (!resp.IsSuccessStatusCode) {
+ Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}");
+ throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}");
+ }
}
+
+ public async Task DeleteRoomAliasAsync(string roomAlias) {
+ var resp = await ClientHttpClient.DeleteAsync("/_matrix/client/v3/directory/room/" + HttpUtility.UrlEncode(roomAlias));
+ if (!resp.IsSuccessStatusCode) {
+ Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}");
+ throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}");
+ }
+ }
+
+ public async Task<RoomAliasesResponse> GetLocalRoomAliasesAsync(string roomId) {
+ var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{HttpUtility.UrlEncode(roomId)}/aliases");
+ if (!resp.IsSuccessStatusCode) {
+ Console.WriteLine($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}");
+ throw new InvalidDataException($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}");
+ }
+
+ return await resp.Content.ReadFromJsonAsync<RoomAliasesResponse>() ?? throw new Exception("Failed to get room aliases?");
+ }
+
+ public class RoomAliasesResponse {
+ [JsonPropertyName("aliases")]
+ public required List<string> Aliases { get; set; }
+ }
+
+#endregion
}
\ No newline at end of file
|