diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 349ccb5..fd4db4d 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -1,5 +1,4 @@
using System.Collections.Frozen;
-using System.Diagnostics;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -8,13 +7,11 @@ using System.Web;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes;
using LibMatrix.EventTypes.Spec;
-using LibMatrix.EventTypes.Spec.State;
using LibMatrix.EventTypes.Spec.State.RoomInfo;
using LibMatrix.Filters;
using LibMatrix.Helpers;
using LibMatrix.Homeservers;
-using LibMatrix.Services;
-using Microsoft.Extensions.Logging.Abstractions;
+using LibMatrix.Responses;
namespace LibMatrix.RoomTypes;
@@ -26,8 +23,6 @@ public class GenericRoom {
throw new ArgumentException("Room ID cannot be null or whitespace", nameof(roomId));
Homeserver = homeserver;
RoomId = roomId;
- // if (GetType() != typeof(SpaceRoom))
- if (GetType() == typeof(GenericRoom)) AsSpace = new SpaceRoom(homeserver, RoomId);
}
public string RoomId { get; set; }
@@ -106,7 +101,7 @@ public class GenericRoom {
Console.WriteLine("WARNING: Homeserver does not support getting event ID from state events, falling back to sync");
var sh = new SyncHelper(Homeserver);
var emptyFilter = new SyncFilter.EventFilter(types: [], limit: 1, senders: [], notTypes: ["*"]);
- var emptyStateFilter = new SyncFilter.RoomFilter.StateFilter(types: [], limit: 1, senders: [], notTypes: ["*"], rooms:[]);
+ var emptyStateFilter = new SyncFilter.RoomFilter.StateFilter(types: [], limit: 1, senders: [], notTypes: ["*"], rooms: []);
sh.Filter = new() {
Presence = emptyFilter,
AccountData = emptyFilter,
@@ -121,10 +116,11 @@ public class GenericRoom {
var sync = await sh.SyncAsync();
var state = sync.Rooms.Join[RoomId].State.Events;
var stateEvent = state.FirstOrDefault(x => x.Type == type && x.StateKey == stateKey);
- if (stateEvent is null) throw new LibMatrixException() {
- ErrorCode = LibMatrixException.ErrorCodes.M_NOT_FOUND,
- Error = "State event not found in sync response"
- };
+ if (stateEvent is null)
+ throw new LibMatrixException() {
+ ErrorCode = LibMatrixException.ErrorCodes.M_NOT_FOUND,
+ Error = "State event not found in sync response"
+ };
return stateEvent.EventId;
}
@@ -137,16 +133,17 @@ public class GenericRoom {
return await GetStateEventAsync(type, stateKey);
}
catch (MatrixException e) {
- if (e.ErrorCode == "M_NOT_FOUND") return default;
+ if (e.ErrorCode == "M_NOT_FOUND") return null;
throw;
}
}
- public async Task<MessagesResponse> GetMessagesAsync(string from = "", int? limit = null, string dir = "b", string 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 Homeserver.ClientHttpClient.GetFromJsonAsync<MessagesResponse>(url);
return res;
}
@@ -154,8 +151,8 @@ public class GenericRoom {
/// <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, int chunkSize = 100) {
+ public async IAsyncEnumerable<MessagesResponse> GetManyMessagesAsync(string from = "", int limit = int.MaxValue, string dir = "b", string filter = "", bool includeState = true,
+ bool fixForward = false, int chunkSize = 250) {
if (dir == "f" && fixForward) {
var concat = new List<MessagesResponse>();
while (true) {
@@ -207,7 +204,7 @@ public class GenericRoom {
public async Task<string?> GetNameAsync() => (await GetStateOrNullAsync<RoomNameEventContent>("m.room.name"))?.Name;
- public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null, bool checkIfAlreadyMember = true) {
+ public async Task<RoomIdResponse> JoinAsync(IEnumerable<string>? homeservers = null, string? reason = null, bool checkIfAlreadyMember = true) {
if (checkIfAlreadyMember)
try {
var ser = await GetStateEventOrNullAsync(RoomMemberEventContent.EventId, Homeserver.UserId);
@@ -216,68 +213,74 @@ public class GenericRoom {
RoomId = RoomId
};
}
- catch { } //ignore
+ catch {
+ // ignored
+ }
var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}";
- Console.WriteLine($"Calling {joinUrl} with {homeservers?.Length ?? 0} via's...");
- if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':', 2)[1] };
- var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", homeservers);
+
+ var materialisedHomeservers = homeservers as string[] ?? homeservers?.ToArray() ?? [];
+ if (!materialisedHomeservers.Any()) materialisedHomeservers = [RoomId.Split(':', 2)[1]];
+
+ Console.WriteLine($"Calling {joinUrl} with {materialisedHomeservers.Length} via(s)...");
+
+ var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", materialisedHomeservers);
+
var res = await Homeserver.ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new {
reason
});
return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?");
}
- public async IAsyncEnumerable<StateEventResponse> GetMembersEnumerableAsync(bool joinedOnly = true) {
- // var sw = Stopwatch.StartNew();
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
- // if (sw.ElapsedMilliseconds > 1000)
- // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}");
- // else sw.Restart();
- // var resText = await res.Content.ReadAsStringAsync();
- // Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}");
+ public async IAsyncEnumerable<StateEventResponse> GetMembersEnumerableAsync(string? membership = null) {
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/members";
+ var isMembershipSet = !string.IsNullOrWhiteSpace(membership);
+ if (isMembershipSet) url += $"?membership={membership}";
+ var res = await Homeserver.ClientHttpClient.GetAsync(url);
var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() {
TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default
});
- // if (sw.ElapsedMilliseconds > 100)
- // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}");
- // else sw.Restart();
- foreach (var resp in result.Chunk) {
- if (resp?.Type != "m.room.member") continue;
- if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
+
+ if (result is null) throw new Exception("Failed to deserialise members response");
+
+ foreach (var resp in result.Chunk ?? []) {
+ if (resp.Type != "m.room.member") continue;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
yield return resp;
}
-
- // if (sw.ElapsedMilliseconds > 100)
- // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}");
}
- public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(bool joinedOnly = true) {
- // var sw = Stopwatch.StartNew();
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
- // if (sw.ElapsedMilliseconds > 1000)
- // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}");
- // else sw.Restart();
- // var resText = await res.Content.ReadAsStringAsync();
- // Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}");
+ public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(string? membership = null) {
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/members";
+ var isMembershipSet = !string.IsNullOrWhiteSpace(membership);
+ if (isMembershipSet) url += $"?membership={membership}";
+ var res = await Homeserver.ClientHttpClient.GetAsync(url);
var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() {
TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default
});
- // if (sw.ElapsedMilliseconds > 100)
- // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}");
- // else sw.Restart();
+
+ if (result is null) throw new Exception("Failed to deserialise members response");
+
var members = new List<StateEventResponse>();
- foreach (var resp in result.Chunk) {
- if (resp?.Type != "m.room.member") continue;
- if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
+ foreach (var resp in result.Chunk ?? []) {
+ if (resp.Type != "m.room.member") continue;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
members.Add(resp);
}
- // if (sw.ElapsedMilliseconds > 100)
- // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}");
return members.ToFrozenSet();
}
+ public async IAsyncEnumerable<string> GetMemberIdsEnumerableAsync(string? membership = null) {
+ await foreach (var evt in GetMembersEnumerableAsync(membership))
+ yield return evt.StateKey!;
+ }
+
+ public async Task<FrozenSet<string>> GetMemberIdsListAsync(string? membership = null) {
+ var members = await GetMembersListAsync(membership);
+ return members.Select(x => x.StateKey!).ToFrozenSet();
+ }
+
#region Utility shortcuts
public Task<EventIdResponse> SendMessageEventAsync(RoomMessageEventContent content) =>
@@ -285,7 +288,7 @@ public class GenericRoom {
public async Task<List<string>?> GetAliasesAsync() {
var res = await GetStateAsync<RoomAliasEventContent>("m.room.aliases");
- return res.Aliases;
+ return res?.Aliases;
}
public Task<RoomCanonicalAliasEventContent?> GetCanonicalAliasAsync() =>
@@ -317,16 +320,17 @@ public class GenericRoom {
public Task<RoomPowerLevelEventContent?> GetPowerLevelsAsync() =>
GetStateAsync<RoomPowerLevelEventContent>("m.room.power_levels");
- [Obsolete("This method will be merged into GetNameAsync() in the future.")]
public async Task<string> GetNameOrFallbackAsync(int maxMemberNames = 2) {
try {
- return await GetNameAsync();
+ var name = await GetNameAsync();
+ if (!string.IsNullOrEmpty(name)) return name;
+ throw new();
}
catch {
try {
var alias = await GetCanonicalAliasAsync();
- if (alias?.Alias is not null) return alias.Alias;
- throw new Exception("No name or alias");
+ if (!string.IsNullOrWhiteSpace(alias?.Alias)) return alias.Alias;
+ throw new Exception("No alias");
}
catch {
try {
@@ -334,7 +338,8 @@ public class GenericRoom {
var memberList = new List<string>();
var memberCount = 0;
await foreach (var member in members)
- memberList.Add(member.RawContent?["displayname"]?.GetValue<string>() ?? "");
+ if (member.StateKey != Homeserver.UserId)
+ memberList.Add(member.RawContent?["displayname"]?.GetValue<string>() ?? "");
memberCount = memberList.Count;
memberList.RemoveAll(string.IsNullOrWhiteSpace);
memberList = memberList.OrderBy(x => x).ToList();
@@ -374,9 +379,9 @@ public class GenericRoom {
await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban",
new UserIdAndReason { UserId = userId, Reason = reason });
- public async Task UnbanAsync(string userId) =>
+ public async Task UnbanAsync(string userId, string? reason = null) =>
await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban",
- new UserIdAndReason { UserId = userId });
+ new UserIdAndReason { UserId = userId, Reason = reason });
public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) {
if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
@@ -393,7 +398,7 @@ public class GenericRoom {
.Content.ReadFromJsonAsync<EventIdResponse>();
public async Task<EventIdResponse?> SendStateEventAsync(string eventType, string stateKey, object content) =>
- await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content))
+ await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType.UrlEncode()}/{stateKey.UrlEncode()}", content))
.Content.ReadFromJsonAsync<EventIdResponse>();
public async Task<EventIdResponse> SendTimelineEventAsync(string eventType, TimelineEventContent content) {
@@ -404,6 +409,15 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<EventIdResponse>() ?? throw new Exception("Failed to send event");
}
+ public async Task<EventIdResponse> SendReactionAsync(string eventId, string key) =>
+ await SendTimelineEventAsync("m.reaction", new RoomMessageReactionEventContent() {
+ RelatesTo = new() {
+ RelationType = "m.annotation",
+ EventId = eventId,
+ Key = key
+ }
+ });
+
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() {
@@ -429,6 +443,16 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<T>();
}
+ public async Task<T?> GetRoomAccountDataOrNullAsync<T>(string key) {
+ try {
+ return await GetRoomAccountDataAsync<T>(key);
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == "M_NOT_FOUND") return default;
+ throw;
+ }
+ }
+
public async Task SetRoomAccountDataAsync(string key, object data) {
var res = await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data);
if (!res.IsSuccessStatusCode) {
@@ -437,13 +461,65 @@ public class GenericRoom {
}
}
- public Task<StateEventResponse> GetEventAsync(string eventId) =>
- Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}");
+ public Task<StateEventResponse> GetEventAsync(string eventId, bool includeUnredactedContent = false) =>
+ Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}?fi.mau.msc2815.include_unredacted_content={includeUnredactedContent}");
- public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string reason) {
+ public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string? reason = null) {
var data = new { reason };
- return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(
- $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync<EventIdResponse>())!;
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid().ToString()}";
+ while (true) {
+ try {
+ return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(url, data)).Content.ReadFromJsonAsync<EventIdResponse>())!;
+ }
+ catch (MatrixException e) {
+ if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw;
+ throw;
+ }
+ }
+ }
+
+#endregion
+
+#region Ephemeral Events
+
+ /// <summary>
+ /// This tells the server that the user is typing for the next N milliseconds where
+ /// N is the value specified in the timeout key. Alternatively, if typing is false,
+ /// it tells the server that the user has stopped typing.
+ /// </summary>
+ /// <param name="typing">Whether the user is typing or not.</param>
+ /// <param name="timeout">The length of time in milliseconds to mark this user as typing.</param>
+ public async Task SendTypingNotificationAsync(bool typing, int timeout = 30000) {
+ await Homeserver.ClientHttpClient.PutAsJsonAsync(
+ $"/_matrix/client/v3/rooms/{RoomId}/typing/{Homeserver.UserId}", new JsonObject {
+ ["timeout"] = typing ? timeout : null,
+ ["typing"] = typing
+ }, new JsonSerializerOptions {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ });
+ }
+
+ /// <summary>
+ /// Updates the marker for the given receipt type to the event ID specified.
+ /// </summary>
+ /// <param name="eventId">The event ID to acknowledge up to.</param>
+ /// <param name="threadId">
+ /// The root thread event’s ID (or main) for which thread this receipt is intended to be under.
+ /// If not specified, the read receipt is unthreaded (default).
+ /// </param>
+ /// <param name="isPrivate">
+ /// If set to true, a receipt type of m.read.private is sent instead of m.read, which marks the
+ /// room as "read" only for the current user
+ /// </param>
+ public async Task SendReadReceiptAsync(string eventId, string? threadId = null, bool isPrivate = false) {
+ var request = new JsonObject();
+ if (threadId != null)
+ request.Add("thread_id", threadId);
+ await Homeserver.ClientHttpClient.PostAsJsonAsync(
+ $"/_matrix/client/v3/rooms/{RoomId}/receipt/m.read{(isPrivate ? ".private" : "")}/{eventId}", request,
+ new JsonSerializerOptions {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ });
}
#endregion
@@ -510,7 +586,7 @@ public class GenericRoom {
var uri = new Uri(path, UriKind.Relative);
if (dir == "b" || dir == "f") uri = uri.AddQuery("dir", dir);
- else if(!string.IsNullOrWhiteSpace(dir)) throw new ArgumentException("Invalid direction", nameof(dir));
+ else if (!string.IsNullOrWhiteSpace(dir)) throw new ArgumentException("Invalid direction", nameof(dir));
if (!string.IsNullOrEmpty(from)) uri = uri.AddQuery("from", from);
if (chunkLimit is not null) uri = uri.AddQuery("limit", chunkLimit.Value.ToString());
if (recurse is not null) uri = uri.AddQuery("recurse", recurse.Value.ToString());
@@ -528,10 +604,11 @@ public class GenericRoom {
}
}
- public readonly SpaceRoom AsSpace;
+ public SpaceRoom AsSpace() => new SpaceRoom(Homeserver, RoomId);
+ public PolicyRoom AsPolicyRoom() => new PolicyRoom(Homeserver, RoomId);
}
public class RoomIdResponse {
[JsonPropertyName("room_id")]
- public string RoomId { get; set; } = null!;
+ public string RoomId { get; set; }
}
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/PolicyRoom.cs b/LibMatrix/RoomTypes/PolicyRoom.cs
new file mode 100644
index 0000000..c6eec63
--- /dev/null
+++ b/LibMatrix/RoomTypes/PolicyRoom.cs
@@ -0,0 +1,51 @@
+using System.Collections.Frozen;
+using LibMatrix.EventTypes;
+using LibMatrix.EventTypes.Spec.State.Policy;
+using LibMatrix.Homeservers;
+
+namespace LibMatrix.RoomTypes;
+
+public class PolicyRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) {
+ public const string TypeName = "support.feline.policy.lists.msc.v1";
+
+ public static readonly FrozenSet<string> UserPolicyEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>().ToFrozenSet();
+ public static readonly FrozenSet<string> ServerPolicyEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>().ToFrozenSet();
+ public static readonly FrozenSet<string> RoomPolicyEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>().ToFrozenSet();
+ public static readonly FrozenSet<string> SpecPolicyEventTypes = [..UserPolicyEventTypes, ..ServerPolicyEventTypes, ..RoomPolicyEventTypes];
+
+ public async IAsyncEnumerable<StateEventResponse> GetPoliciesAsync() {
+ var fullRoomState = GetFullStateAsync();
+ await foreach (var eventResponse in fullRoomState) {
+ if (SpecPolicyEventTypes.Contains(eventResponse!.Type)) {
+ yield return eventResponse;
+ }
+ }
+ }
+
+ public async IAsyncEnumerable<StateEventResponse> GetUserPoliciesAsync() {
+ var fullRoomState = GetPoliciesAsync();
+ await foreach (var eventResponse in fullRoomState) {
+ if (UserPolicyEventTypes.Contains(eventResponse!.Type)) {
+ yield return eventResponse;
+ }
+ }
+ }
+
+ public async IAsyncEnumerable<StateEventResponse> GetServerPoliciesAsync() {
+ var fullRoomState = GetPoliciesAsync();
+ await foreach (var eventResponse in fullRoomState) {
+ if (ServerPolicyEventTypes.Contains(eventResponse!.Type)) {
+ yield return eventResponse;
+ }
+ }
+ }
+
+ public async IAsyncEnumerable<StateEventResponse> GetRoomPoliciesAsync() {
+ var fullRoomState = GetPoliciesAsync();
+ await foreach (var eventResponse in fullRoomState) {
+ if (RoomPolicyEventTypes.Contains(eventResponse!.Type)) {
+ yield return eventResponse;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index b40ccc6..96abd77 100644
--- a/LibMatrix/RoomTypes/SpaceRoom.cs
+++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -1,9 +1,12 @@
using ArcaneLibs.Extensions;
using LibMatrix.Homeservers;
+using LibMatrix.Responses;
namespace LibMatrix.RoomTypes;
public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) {
+ public const string TypeName = "m.space";
+
public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) {
// var rooms = new List<GenericRoom>();
var state = GetFullStateAsync();
@@ -15,7 +18,7 @@ public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId)
}
public async Task<EventIdResponse> AddChildAsync(GenericRoom room) {
- var members = room.GetMembersEnumerableAsync(true);
+ var members = room.GetMembersEnumerableAsync("join");
Dictionary<string, int> memberCountByHs = new();
await foreach (var member in members) {
var server = member.StateKey.Split(':')[1];
@@ -31,7 +34,7 @@ public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId)
});
return resp;
}
-
+
public async Task<EventIdResponse> AddChildByIdAsync(string id) {
return await AddChildAsync(Homeserver.GetRoom(id));
}
|