diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index f0b35f9..54f5937 100644
--- a/LibMatrix/Homeservers/RemoteHomeServer.cs
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -3,6 +3,7 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
+using ArcaneLibs.Collections;
using ArcaneLibs.Extensions;
using LibMatrix.Extensions;
using LibMatrix.Responses;
@@ -22,13 +23,13 @@ public class RemoteHomeserver {
// Timeout = TimeSpan.FromSeconds(300) // TODO: Re-implement this
};
- if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName);
if (!string.IsNullOrWhiteSpace(wellKnownUris.Server))
FederationClient = new FederationClient(WellKnownUris.Server!, proxy);
Auth = new(this);
}
- private Dictionary<string, object> _profileCache { get; set; } = new();
+ // private Dictionary<string, object> _profileCache { get; set; } = new();
+ private SemaphoreCache<UserProfileResponse> _profileCache { get; set; } = new();
public string ServerNameOrUrl { get; }
public string? Proxy { get; }
@@ -40,27 +41,12 @@ public class RemoteHomeserver {
public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; }
- public async Task<UserProfileResponse> GetProfileAsync(string mxid, bool useCache = false) {
- if (mxid is null) throw new ArgumentNullException(nameof(mxid));
- if (useCache && _profileCache.TryGetValue(mxid, out var value)) {
- if (value is SemaphoreSlim s) await s.WaitAsync();
- if (value is UserProfileResponse p) return p;
- }
-
- _profileCache[mxid] = new SemaphoreSlim(1);
-
- var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}");
- var data = await resp.Content.ReadFromJsonAsync<UserProfileResponse>();
- if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
- _profileCache[mxid] = data ?? throw new InvalidOperationException($"Could not get profile for {mxid}");
-
- return data;
- }
-
// TODO: Do we need to support retrieving individual profile properties? Is there any use for that besides just getting the full profile?
+ public async Task<UserProfileResponse> GetProfileAsync(string mxid) =>
+ await ClientHttpClient.GetFromJsonAsync<UserProfileResponse>($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}");
public async Task<ClientVersionsResponse> GetClientVersionsAsync() {
- var resp = await ClientHttpClient.GetAsync($"/_matrix/client/versions");
+ var resp = await ClientHttpClient.GetAsync("/_matrix/client/versions");
var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data);
return data ?? throw new InvalidOperationException("ClientVersionsResponse is null");
@@ -74,15 +60,54 @@ public class RemoteHomeserver {
return data ?? throw new InvalidOperationException($"Could not resolve alias {alias}");
}
- public Task<PublicRoomDirectoryResult> GetPublicRoomsAsync(int limit = 100, string? server = null, string? since = null) =>
- ClientHttpClient.GetFromJsonAsync<PublicRoomDirectoryResult>(buildUriWithParams("/_matrix/client/v3/publicRooms", (nameof(limit), true, limit),
- (nameof(server), !string.IsNullOrWhiteSpace(server), server), (nameof(since), !string.IsNullOrWhiteSpace(since), since)));
+ public async Task<PublicRoomDirectoryResult> GetPublicRoomsAsync(int limit = 100, string? server = null, string? since = null, string? thirdPartyInstanceId = null,
+ bool? includeAllNetworks = null, RoomDirectoryFilter? filter = null) {
+ if (thirdPartyInstanceId is null && includeAllNetworks is null && filter is null) {
+ var url = $"/_matrix/client/v3/publicRooms?limit={limit}";
+ if (!string.IsNullOrWhiteSpace(server)) {
+ url += $"&server={server}";
+ }
+
+ if (!string.IsNullOrWhiteSpace(since)) {
+ url += $"&since={since}";
+ }
+
+ return await ClientHttpClient.GetFromJsonAsync<PublicRoomDirectoryResult>(url);
+ }
- // TODO: move this somewhere else
- private string buildUriWithParams(string url, params (string name, bool include, object? value)[] values) {
- return url + "?" + string.Join("&", values.Where(x => x.include));
+ // this technically requires authentication... TODO: move to AuthenticatedHomeserver?
+ var postUrl = "/_matrix/client/v3/publicRooms";
+ if (!string.IsNullOrWhiteSpace(server)) {
+ postUrl += $"?server={HttpUtility.UrlEncode(server)}";
+ }
+
+ var postData = new RoomDirectoryFilteredRequest {
+ Limit = limit,
+ Since = since,
+ ThirdPartyInstanceId = thirdPartyInstanceId,
+ IncludeAllNetworks = includeAllNetworks,
+ Filter = filter
+ };
+
+ return await (await ClientHttpClient.PostAsJsonAsync(postUrl, postData)).EnsureSuccessStatusCode()
+ .Content.ReadFromJsonAsync<PublicRoomDirectoryResult>() ?? throw new InvalidOperationException();
}
+ public async IAsyncEnumerable<PublicRoomDirectoryResult> EnumeratePublicRoomsAsync(int limit = int.MaxValue, string? server = null, string? since = null,
+ string? thirdPartyInstanceId = null, bool? includeAllNetworks = null, RoomDirectoryFilter? filter = null, int chunkSize = 100) {
+ PublicRoomDirectoryResult res;
+ do {
+ res = await GetPublicRoomsAsync(chunkSize, server, since, thirdPartyInstanceId, includeAllNetworks, filter);
+ yield return res;
+ if (res.NextBatch is null || res.NextBatch == since || res.Chunk.Count == 0) break;
+ since = res.NextBatch;
+ } while (limit > 0 && limit-- > 0);
+ }
+
+ public async Task<RoomDirectoryVisibilityResponse> GetRoomDirectoryVisibilityAsync(string roomId)
+ => await (await ClientHttpClient.GetAsync($"/_matrix/client/v3/directory/list/room/{HttpUtility.UrlEncode(roomId)}")).Content
+ .ReadFromJsonAsync<RoomDirectoryVisibilityResponse>() ?? throw new InvalidOperationException();
+
#region Authentication
public async Task<LoginResponse> LoginAsync(string username, string password, string? deviceName = null) {
@@ -117,12 +142,45 @@ public class RemoteHomeserver {
#endregion
- [Obsolete("This call uses the deprecated unauthenticated media endpoints, please switch to the relevant AuthenticatedHomeserver methods instead.", true)]
- public virtual string? ResolveMediaUri(string? mxcUri) => null;
-
public UserInteractiveAuthClient Auth;
}
+public class RoomDirectoryFilteredRequest {
+ [JsonPropertyName("filter")]
+ public RoomDirectoryFilter? Filter { get; set; }
+
+ [JsonPropertyName("include_all_networks")]
+ public bool? IncludeAllNetworks { get; set; }
+
+ [JsonPropertyName("limit")]
+ public int Limit { get; set; }
+
+ [JsonPropertyName("since")]
+ public string? Since { get; set; }
+
+ [JsonPropertyName("third_party_instance_id")]
+ public string? ThirdPartyInstanceId { get; set; }
+}
+
+public class RoomDirectoryFilter {
+ [JsonPropertyName("generic_search_term")]
+ public string? GenericSearchTerm { get; set; }
+
+ [JsonPropertyName("room_types")]
+ public List<string?>? RoomTypes { get; set; }
+}
+
+public class RoomDirectoryVisibilityResponse {
+ [JsonPropertyName("visibility")]
+ public VisibilityValue Visibility { get; set; }
+
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum VisibilityValue {
+ [JsonStringEnumMemberName("public")] Public,
+ [JsonStringEnumMemberName("private")] Private
+ }
+}
+
public class PublicRoomDirectoryResult {
[JsonPropertyName("chunk")]
public List<PublicRoomListItem> Chunk { get; set; }
|