diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 93736a3..bc1bc90 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -232,8 +232,11 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?");
}
- public async IAsyncEnumerable<StateEventResponse> GetMembersEnumerableAsync(bool joinedOnly = true) {
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
+ 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
});
@@ -242,13 +245,16 @@ public class GenericRoom {
foreach (var resp in result.Chunk ?? []) {
if (resp.Type != "m.room.member") continue;
- if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
yield return resp;
}
}
- public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(bool joinedOnly = true) {
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
+ 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
});
@@ -258,13 +264,23 @@ public class GenericRoom {
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;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
members.Add(resp);
}
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) =>
@@ -393,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() {
@@ -586,4 +611,4 @@ public class GenericRoom {
public class RoomIdResponse {
[JsonPropertyName("room_id")]
public string RoomId { get; set; }
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index 4563ed3..0c74be5 100644
--- a/LibMatrix/RoomTypes/SpaceRoom.cs
+++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -17,7 +17,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];
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
index e0784c4..8419518 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
@@ -162,7 +162,7 @@ public class RoomMembershipTests : TestBed<TestFixture> {
await otherUser.GetRoom(room.RoomId).JoinAsync(reason: "Unit test!");
}
- var states = await room.GetMembersListAsync(false);
+ var states = await room.GetMembersListAsync();
Assert.Equal(count + 1, states.Count);
await room.LeaveAsync();
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
index fa9812f..7801ed0 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
@@ -300,7 +300,7 @@ public class RoomTests : TestBed<TestFixture> {
});
await room.InviteUsersAsync(users.Select(u => u.UserId));
- var members = await room.GetMembersListAsync(false);
+ var members = await room.GetMembersListAsync();
Assert.NotNull(members);
Assert.NotEmpty(members);
Assert.All(members, Assert.NotNull);
diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
index da83cfa..d60dc92 100644
--- a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
@@ -10,4 +10,5 @@ public class LibMatrixBotConfiguration {
public List<string> Prefixes { get; set; }
public bool MentionPrefix { get; set; }
public string? LogRoom { get; set; }
+ public bool SelfCommandsOnly { get; set; } = true;
}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
index 9fe460b..745b86e 100644
--- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
@@ -20,6 +20,7 @@ public class CommandListenerHostedService : IHostedService {
private Task? _listenerTask;
private CancellationTokenSource _cts = new();
+ private long _startupTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
public CommandListenerHostedService(AuthenticatedHomeserverGeneric hs, ILogger<CommandListenerHostedService> logger, IServiceProvider services,
LibMatrixBotConfiguration config, Func<CommandResult, Task>? commandResultHandler = null) {
@@ -43,16 +44,20 @@ public class CommandListenerHostedService : IHostedService {
private async Task? Run(CancellationToken cancellationToken) {
_logger.LogInformation("Starting command listener!");
- var filter = await _hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.libmatrix.utilities.bot.command_listener_syncfilter.dev2", new SyncFilter() {
- AccountData = new SyncFilter.EventFilter(notTypes: ["*"], limit: 1),
- Presence = new SyncFilter.EventFilter(notTypes: ["*"]),
- Room = new SyncFilter.RoomFilter() {
- AccountData = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
- Ephemeral = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
- State = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
- Timeline = new SyncFilter.RoomFilter.StateFilter(types: ["m.room.message"], notSenders: [_hs.WhoAmI.UserId]),
- }
- });
+ var filter = await _hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.libmatrix.utilities.bot.command_listener_syncfilter.dev3" + (_config.SelfCommandsOnly),
+ new SyncFilter() {
+ AccountData = new SyncFilter.EventFilter(notTypes: ["*"], limit: 1),
+ Presence = new SyncFilter.EventFilter(notTypes: ["*"]),
+ Room = new SyncFilter.RoomFilter() {
+ AccountData = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
+ Ephemeral = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
+ State = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]),
+ Timeline = new SyncFilter.RoomFilter.StateFilter(types: ["m.room.message"],
+ notSenders: _config.SelfCommandsOnly ? null : [_hs.WhoAmI.UserId],
+ senders: _config.SelfCommandsOnly ? [_hs.WhoAmI.UserId] : null
+ ),
+ }
+ });
var syncHelper = new SyncHelper(_hs, _logger) {
Timeout = 30_000,
@@ -62,9 +67,13 @@ public class CommandListenerHostedService : IHostedService {
syncHelper.SyncReceivedHandlers.Add(async sync => {
_logger.LogInformation("Sync received!");
foreach (var roomResp in sync.Rooms?.Join ?? []) {
- if (roomResp.Value.Timeline?.Events is null or { Count: > 5 }) continue;
+ // if (roomResp.Value.Timeline?.Events is null or { Count: > 5 }) continue;
+ if (roomResp.Value.Timeline?.Events is null) continue;
foreach (var @event in roomResp.Value.Timeline.Events) {
@event.RoomId = roomResp.Key;
+ if (_config.SelfCommandsOnly && @event.Sender != _hs.WhoAmI.UserId) continue;
+ if (@event.OriginServerTs < _startupTime) continue; // ignore events older than startup time
+
try {
// var room = _hs.GetRoom(@event.RoomId);
// _logger.LogInformation(eventResponse.ToJson(indent: false));
@@ -143,8 +152,8 @@ public class CommandListenerHostedService : IHostedService {
.FirstOrDefault(commandWithoutPrefix.StartsWith);
var args =
usedCommand == null || commandWithoutPrefix.Length <= usedCommand.Length
- ? []
- : commandWithoutPrefix[(usedCommand.Length + 1)..].Split(' ');
+ ? []
+ : commandWithoutPrefix[(usedCommand.Length + 1)..].Split(' ').SelectMany(x=>x.Split('\n')).ToArray();
var ctx = new CommandContext {
Room = room,
MessageEvent = evt,
|