diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject 98913a604fa40d1d850d9ef5666823a64244ba5
+Subproject a2c73b7b89ca8910a0f9a9856bb06a6d5f49ce0
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 6488464..6f2cacc 100644
--- a/LibMatrix/Helpers/SyncHelper.cs
+++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -24,7 +24,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
public string? Since { get; set; }
public int Timeout { get; set; } = 30000;
- public string? SetPresence { get; set; } = "online";
+ public string? SetPresence { get; set; }
/// <summary>
/// Disabling this uses a technically slower code path, useful for checking whether delay comes from waiting for server or deserialising responses
@@ -168,9 +168,11 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
var sw = Stopwatch.StartNew();
if (_filterIsDirty) await UpdateFilterAsync();
- var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}";
+ var url = $"/_matrix/client/v3/sync?timeout={Timeout}";
+ if (!string.IsNullOrWhiteSpace(SetPresence)) url += $"&set_presence={SetPresence}";
if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}";
if (_filterId is not null) url += $"&filter={_filterId}";
+ if (FullState) url += "&full_state=true";
if (UseMsc4222StateAfter) url += "&org.matrix.msc4222.use_state_after=true&use_state_after=true"; // We use both unstable and stable names for compatibility
// logger?.LogInformation("SyncHelper: Calling: {}", url);
@@ -209,6 +211,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
Console.WriteLine(e);
logger?.LogError(e, "Failed to sync!\n{}", e.ToString());
await Task.WhenAll(ExceptionHandlers.Select(x => x.Invoke(e)).ToList());
+ if (e is MatrixException { ErrorCode: MatrixException.ErrorCodes.M_UNKNOWN_TOKEN }) throw;
}
return null;
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/BotCommandInstaller.cs b/Utilities/LibMatrix.Utilities.Bot/BotServiceInstaller.cs
index 56ceb65..ff0bc9e 100644
--- a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/BotServiceInstaller.cs
@@ -1,91 +1,98 @@
-using ArcaneLibs;
-using LibMatrix.Homeservers;
-using LibMatrix.Services;
-using LibMatrix.Utilities.Bot.AppServices;
-using LibMatrix.Utilities.Bot.Interfaces;
-using LibMatrix.Utilities.Bot.Services;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace LibMatrix.Utilities.Bot;
-
-public static class BotCommandInstaller {
- public static BotInstaller AddMatrixBot(this IServiceCollection services) {
- return new BotInstaller(services).AddMatrixBot();
- }
-}
-
-public class BotInstaller(IServiceCollection services) {
- public BotInstaller AddMatrixBot() {
- services.AddSingleton<LibMatrixBotConfiguration>();
-
- services.AddSingleton<AuthenticatedHomeserverGeneric>(x => {
- var config = x.GetService<LibMatrixBotConfiguration>() ?? throw new Exception("No configuration found!");
- var hsProvider = x.GetService<HomeserverProviderService>() ?? throw new Exception("No homeserver provider found!");
-
- if (x.GetService<AppServiceConfiguration>() is AppServiceConfiguration appsvcConfig)
- config.AccessToken = appsvcConfig.AppserviceToken;
- else if (Environment.GetEnvironmentVariable("LIBMATRIX_ACCESS_TOKEN_PATH") is string path)
- config.AccessTokenPath = path;
-
- if (string.IsNullOrWhiteSpace(config.AccessToken) && string.IsNullOrWhiteSpace(config.AccessTokenPath))
- throw new Exception("Unable to add bot service without an access token or access token path!");
-
- if (!string.IsNullOrWhiteSpace(config.AccessTokenPath)) {
- var token = File.ReadAllText(config.AccessTokenPath);
- config.AccessToken = token.Trim();
- }
-
- var hs = hsProvider.GetAuthenticatedWithToken(config.Homeserver, config.AccessToken).Result;
-
- return hs;
- });
-
- return this;
- }
-
- public BotInstaller AddCommandHandler() {
- Console.WriteLine("Adding command handler...");
- services.AddHostedService<CommandListenerHostedService>();
- return this;
- }
-
- public BotInstaller DiscoverAllCommands() {
- foreach (var commandClass in ClassCollector<ICommand>.ResolveFromAllAccessibleAssemblies()) {
- Console.WriteLine($"Adding command {commandClass.Name}");
- services.AddScoped(typeof(ICommand), commandClass);
- }
-
- return this;
- }
-
- public BotInstaller AddCommands(IEnumerable<Type> commandClasses) {
- foreach (var commandClass in commandClasses) {
- if (!commandClass.IsAssignableTo(typeof(ICommand)))
- throw new Exception($"Type {commandClass.Name} is not assignable to ICommand!");
- Console.WriteLine($"Adding command {commandClass.Name}");
- services.AddScoped(typeof(ICommand), commandClass);
- }
-
- return this;
- }
-
- public BotInstaller WithInviteHandler(Func<RoomInviteContext, Task> inviteHandler) {
- services.AddSingleton(inviteHandler);
- services.AddHostedService<InviteHandlerHostedService>();
- services.AddSingleton<InviteHandlerHostedService.InviteListenerSyncConfiguration>();
- return this;
- }
-
- public BotInstaller WithInviteHandler<T>() where T : class, IRoomInviteHandler {
- services.AddSingleton<T>();
- services.AddSingleton<Func<RoomInviteContext, Task>>(sp => sp.GetRequiredService<T>().HandleInviteAsync);
- services.AddHostedService<InviteHandlerHostedService>();
- services.AddSingleton<InviteHandlerHostedService.InviteListenerSyncConfiguration>();
- return this;
- }
-
- public BotInstaller WithCommandResultHandler(Func<CommandResult, Task> commandResultHandler) {
- services.AddSingleton(commandResultHandler);
- return this;
- }
+using ArcaneLibs;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
+using LibMatrix.Utilities.Bot.AppServices;
+using LibMatrix.Utilities.Bot.Interfaces;
+using LibMatrix.Utilities.Bot.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace LibMatrix.Utilities.Bot;
+
+public static class BotServiceInstallerExtensions {
+ public static BotServiceInstaller AddMatrixBot(this IServiceCollection services) {
+ return new BotServiceInstaller(services).AddMatrixBot();
+ }
+}
+
+public class BotServiceInstaller(IServiceCollection services) {
+ public BotServiceInstaller AddMatrixBot() {
+ services.AddSingleton<LibMatrixBotConfiguration>();
+
+ services.AddSingleton<AuthenticatedHomeserverGeneric>(x => {
+ var config = x.GetService<LibMatrixBotConfiguration>() ?? throw new Exception("No configuration found!");
+ var hsProvider = x.GetService<HomeserverProviderService>() ?? throw new Exception("No homeserver provider found!");
+
+ if (x.GetService<AppServiceConfiguration>() is AppServiceConfiguration appsvcConfig)
+ config.AccessToken = appsvcConfig.AppserviceToken;
+ else if (Environment.GetEnvironmentVariable("LIBMATRIX_ACCESS_TOKEN_PATH") is string path)
+ config.AccessTokenPath = path;
+
+ if (string.IsNullOrWhiteSpace(config.AccessToken) && string.IsNullOrWhiteSpace(config.AccessTokenPath))
+ throw new Exception("Unable to add bot service without an access token or access token path!");
+
+ if (!string.IsNullOrWhiteSpace(config.AccessTokenPath)) {
+ var token = File.ReadAllText(config.AccessTokenPath);
+ config.AccessToken = token.Trim();
+ }
+
+ var hs = hsProvider.GetAuthenticatedWithToken(config.Homeserver, config.AccessToken).Result;
+
+ return hs;
+ });
+
+ return this;
+ }
+
+ public BotServiceInstaller AddCommandHandler() {
+ Console.WriteLine("Adding command handler...");
+ services.AddSingleton(s => s.GetRequiredService<LibMatrixBotConfiguration>().CommandListener
+ ?? throw new Exception("Command handling is enabled, but configuration is missing the LibMatrixBot:CommandListener configuration section!")
+ );
+ services.AddHostedService<CommandListenerHostedService>();
+ return this;
+ }
+
+ public BotServiceInstaller DiscoverAllCommands() {
+ foreach (var commandClass in ClassCollector<ICommand>.ResolveFromAllAccessibleAssemblies()) {
+ Console.WriteLine($"Adding command {commandClass.Name}");
+ services.AddScoped(typeof(ICommand), commandClass);
+ }
+
+ return this;
+ }
+
+ public BotServiceInstaller AddCommands(IEnumerable<Type> commandClasses) {
+ foreach (var commandClass in commandClasses) {
+ if (!commandClass.IsAssignableTo(typeof(ICommand)))
+ throw new Exception($"Type {commandClass.Name} is not assignable to ICommand!");
+ Console.WriteLine($"Adding command {commandClass.Name}");
+ services.AddScoped(typeof(ICommand), commandClass);
+ }
+
+ return this;
+ }
+
+ public BotServiceInstaller WithCommandResultHandler(Func<CommandResult, Task> commandResultHandler) {
+ services.AddSingleton(commandResultHandler);
+ return this;
+ }
+
+ public BotServiceInstaller WithInviteHandler(Func<RoomInviteContext, Task> inviteHandler) {
+ services.AddSingleton(inviteHandler);
+ services.AddSingleton(s => s.GetRequiredService<LibMatrixBotConfiguration>().InviteListener
+ ?? throw new Exception("Invite handling is enabled, but configuration is missing the LibMatrixBot:InviteListener configuration section!")
+ );
+ services.AddHostedService<InviteHandlerHostedService>();
+ return this;
+ }
+
+ public BotServiceInstaller WithInviteHandler<T>() where T : class, IRoomInviteHandler {
+ services.AddSingleton<T>();
+ services.AddSingleton<Func<RoomInviteContext, Task>>(sp => sp.GetRequiredService<T>().HandleInviteAsync);
+ services.AddSingleton(s => s.GetRequiredService<LibMatrixBotConfiguration>().InviteListener
+ ?? throw new Exception("Invite handling is enabled, but configuration is missing the LibMatrixBot:InviteListener configuration section!")
+ );
+ services.AddHostedService<InviteHandlerHostedService>();
+ return this;
+ }
}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.Utilities.Bot/Configuration/CommandListenerConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/Configuration/CommandListenerConfiguration.cs
new file mode 100644
index 0000000..e3026cd
--- /dev/null
+++ b/Utilities/LibMatrix.Utilities.Bot/Configuration/CommandListenerConfiguration.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics.CodeAnalysis;
+using LibMatrix.Filters;
+
+namespace LibMatrix.Utilities.Bot.Configuration;
+
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Configuration")]
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Configuration")]
+public class CommandListenerSyncConfiguration {
+ // public SyncFilter? Filter { get; set; }
+ public TimeSpan? MinimumSyncTime { get; set; }
+ public int? Timeout { get; set; }
+ public string? Presence { get; set; }
+ // public bool InitialSyncOnStartup { get; set; }
+}
+
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Configuration")]
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Configuration")]
+public class CommandListenerConfiguration {
+ public CommandListenerSyncConfiguration SyncConfiguration { get; set; } = new();
+
+ public required List<string> Prefixes { get; set; }
+ public bool MentionPrefix { get; set; }
+ public bool SelfCommandsOnly { get; set; }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.Utilities.Bot/Configuration/InviteListenerConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/Configuration/InviteListenerConfiguration.cs
new file mode 100644
index 0000000..7fce400
--- /dev/null
+++ b/Utilities/LibMatrix.Utilities.Bot/Configuration/InviteListenerConfiguration.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+using LibMatrix.Filters;
+
+namespace LibMatrix.Utilities.Bot.Configuration;
+
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Configuration")]
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Configuration")]
+public class InviteListenerSyncConfiguration {
+ public SyncFilter? Filter { get; set; }
+ public TimeSpan? MinimumSyncTime { get; set; }
+ public int? Timeout { get; set; }
+ public string? Presence { get; set; }
+ public bool InitialSyncOnStartup { get; set; }
+}
+
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Configuration")]
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Configuration")]
+public class InviteListenerConfiguration {
+ public InviteListenerSyncConfiguration SyncConfiguration { get; set; } = new();
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/Configuration/LibMatrixBotConfiguration.cs
index da83cfa..cd272e0 100644
--- a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/Configuration/LibMatrixBotConfiguration.cs
@@ -1,3 +1,4 @@
+using LibMatrix.Utilities.Bot.Configuration;
using Microsoft.Extensions.Configuration;
namespace LibMatrix.Utilities.Bot;
@@ -7,7 +8,10 @@ public class LibMatrixBotConfiguration {
public string Homeserver { get; set; }
public string? AccessToken { get; set; }
public string? AccessTokenPath { get; set; }
- public List<string> Prefixes { get; set; }
- public bool MentionPrefix { get; set; }
public string? LogRoom { get; set; }
+
+ public string? Presence { get; set; }
+
+ public InviteListenerConfiguration? InviteListener { get; set; }
+ public CommandListenerConfiguration? CommandListener { get; set; }
}
\ 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..4c6b462 100644
--- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
@@ -1,9 +1,11 @@
+using System.Collections.Frozen;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec;
using LibMatrix.EventTypes.Spec.State.RoomInfo;
using LibMatrix.Filters;
using LibMatrix.Helpers;
using LibMatrix.Homeservers;
+using LibMatrix.Utilities.Bot.Configuration;
using LibMatrix.Utilities.Bot.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -11,87 +13,91 @@ using Microsoft.Extensions.Logging;
namespace LibMatrix.Utilities.Bot.Services;
-public class CommandListenerHostedService : IHostedService {
- private readonly AuthenticatedHomeserverGeneric _hs;
- private readonly ILogger<CommandListenerHostedService> _logger;
- private readonly IEnumerable<ICommand> _commands;
- private readonly LibMatrixBotConfiguration _config;
- private readonly Func<CommandResult, Task>? _commandResultHandler;
+public class CommandListenerHostedService(
+ AuthenticatedHomeserverGeneric hs,
+ ILogger<CommandListenerHostedService> logger,
+ IServiceProvider services,
+ LibMatrixBotConfiguration botConfig,
+ CommandListenerConfiguration config,
+ Func<CommandResult, Task>? commandResultHandler = null
+)
+ : IHostedService {
+ private FrozenSet<ICommand> _commands = null!;
private Task? _listenerTask;
private CancellationTokenSource _cts = new();
-
- public CommandListenerHostedService(AuthenticatedHomeserverGeneric hs, ILogger<CommandListenerHostedService> logger, IServiceProvider services,
- LibMatrixBotConfiguration config, Func<CommandResult, Task>? commandResultHandler = null) {
- logger.LogInformation("{} instantiated!", GetType().Name);
- _hs = hs;
- _logger = logger;
- _config = config;
- _commandResultHandler = commandResultHandler;
- _logger.LogInformation("Getting commands...");
- _commands = services.GetServices<ICommand>();
- _logger.LogInformation("Got {} commands!", _commands.Count());
- }
+ private long _startupTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
/// <summary>Triggered when the application host is ready to start the service.</summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
public Task StartAsync(CancellationToken cancellationToken) {
_listenerTask = Run(_cts.Token);
- _logger.LogInformation("Command listener started (StartAsync)!");
+ logger.LogInformation("Getting commands...");
+ _commands = services.GetServices<ICommand>().ToFrozenSet();
+ logger.LogInformation("Got {} commands!", _commands.Count);
+ logger.LogInformation("Command listener started (StartAsync)!");
return Task.CompletedTask;
}
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 syncHelper = new SyncHelper(_hs, _logger) {
- Timeout = 30_000,
- FilterId = filter
+ logger.LogInformation("Starting command listener!");
+ 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) {
+ FilterId = filter,
+ Timeout = config.SyncConfiguration.Timeout ?? 30_000,
+ MinimumDelay = config.SyncConfiguration.MinimumSyncTime ?? TimeSpan.Zero,
+ SetPresence = config.SyncConfiguration.Presence ?? botConfig.Presence,
+
};
syncHelper.SyncReceivedHandlers.Add(async sync => {
- _logger.LogInformation("Sync received!");
+ 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) 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));
if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message })
if (message is { MessageType: "m.text" }) {
var usedPrefix = await GetUsedPrefix(@event);
if (usedPrefix is null) return;
var res = await InvokeCommand(@event, usedPrefix);
- await (_commandResultHandler?.Invoke(res) ?? HandleResult(res));
+ await (commandResultHandler?.Invoke(res) ?? HandleResult(res));
}
}
catch (Exception e) {
- _logger.LogError(e, "Error in command listener!");
+ logger.LogError(e, "Error in command listener!");
Console.WriteLine(@event.ToJson(ignoreNull: false, indent: true));
var fakeResult = new CommandResult() {
Result = CommandResult.CommandResultType.Failure_Exception,
Exception = e,
Success = false,
Context = new() {
- Homeserver = _hs,
+ Homeserver = hs,
CommandName = "[CommandListener.SyncHandler]",
- Room = _hs.GetRoom(roomResp.Key),
+ Room = hs.GetRoom(roomResp.Key),
Args = [],
MessageEvent = @event
}
};
- await (_commandResultHandler?.Invoke(fakeResult) ?? HandleResult(fakeResult));
+ await (commandResultHandler?.Invoke(fakeResult) ?? HandleResult(fakeResult));
}
}
}
@@ -103,9 +109,9 @@ public class CommandListenerHostedService : IHostedService {
/// <summary>Triggered when the application host is performing a graceful shutdown.</summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
public async Task StopAsync(CancellationToken cancellationToken) {
- _logger.LogInformation("Shutting down command listener!");
+ logger.LogInformation("Shutting down command listener!");
if (_listenerTask is null) {
- _logger.LogError("Could not shut down command listener task because it was null!");
+ logger.LogError("Could not shut down command listener task because it was null!");
return;
}
@@ -115,12 +121,12 @@ public class CommandListenerHostedService : IHostedService {
private async Task<string?> GetUsedPrefix(StateEventResponse evt) {
var messageContent = evt.TypedContent as RoomMessageEventContent;
var message = messageContent!.BodyWithoutReplyFallback;
- var prefix = _config.Prefixes.OrderByDescending(x => x.Length).FirstOrDefault(message.StartsWith);
- if (prefix is null && _config.MentionPrefix) {
- var profile = await _hs.GetProfileAsync(_hs.WhoAmI.UserId);
- var roomProfile = await _hs.GetRoom(evt.RoomId!).GetStateAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, _hs.WhoAmI.UserId);
- if (message.StartsWith(_hs.WhoAmI.UserId + ": ")) prefix = profile.DisplayName + ": "; // `@bot:server.xyz: `
- else if (message.StartsWith(_hs.WhoAmI.UserId + " ")) prefix = profile.DisplayName + " "; // `@bot:server.xyz `
+ var prefix = config.Prefixes.OrderByDescending(x => x.Length).FirstOrDefault(message.StartsWith);
+ if (prefix is null && config.MentionPrefix) {
+ var profile = await hs.GetProfileAsync(hs.WhoAmI.UserId);
+ var roomProfile = await hs.GetRoom(evt.RoomId!).GetStateAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, hs.WhoAmI.UserId);
+ if (message.StartsWith(hs.WhoAmI.UserId + ": ")) prefix = profile.DisplayName + ": "; // `@bot:server.xyz: `
+ else if (message.StartsWith(hs.WhoAmI.UserId + " ")) prefix = profile.DisplayName + " "; // `@bot:server.xyz `
else if (!string.IsNullOrWhiteSpace(roomProfile?.DisplayName) && message.StartsWith(roomProfile.DisplayName + ": "))
prefix = roomProfile.DisplayName + ": "; // `local bot: `
else if (!string.IsNullOrWhiteSpace(roomProfile?.DisplayName) && message.StartsWith(roomProfile.DisplayName + " "))
@@ -134,7 +140,7 @@ public class CommandListenerHostedService : IHostedService {
private async Task<CommandResult> InvokeCommand(StateEventResponse evt, string usedPrefix) {
var message = evt.TypedContent as RoomMessageEventContent;
- var room = _hs.GetRoom(evt.RoomId!);
+ var room = hs.GetRoom(evt.RoomId!);
var commandWithoutPrefix = message.BodyWithoutReplyFallback[usedPrefix.Length..].Trim();
var usedCommand = _commands
@@ -143,12 +149,12 @@ 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,
- Homeserver = _hs,
+ Homeserver = hs,
Args = args,
CommandName = usedCommand ?? commandWithoutPrefix.Split(' ')[0]
};
diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs
index ad78779..99491a8 100644
--- a/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs
+++ b/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using LibMatrix.Filters;
using LibMatrix.Helpers;
using LibMatrix.Homeservers;
+using LibMatrix.Utilities.Bot.Configuration;
using LibMatrix.Utilities.Bot.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
@@ -12,16 +13,17 @@ namespace LibMatrix.Utilities.Bot.Services;
public class InviteHandlerHostedService(
ILogger<InviteHandlerHostedService> logger,
AuthenticatedHomeserverGeneric hs,
- InviteHandlerHostedService.InviteListenerSyncConfiguration listenerSyncConfiguration,
+ LibMatrixBotConfiguration botConfig,
+ InviteListenerConfiguration config,
Func<RoomInviteContext, Task> inviteHandler
) : IHostedService {
private Task? _listenerTask;
private CancellationTokenSource _cts = new();
private readonly SyncHelper _syncHelper = new(hs, logger) {
- Timeout = listenerSyncConfiguration.Timeout ?? 30_000,
- MinimumDelay = listenerSyncConfiguration.MinimumSyncTime ?? new(0),
- SetPresence = listenerSyncConfiguration.Presence ?? "online"
+ Timeout = config.SyncConfiguration.Timeout ?? 30_000,
+ MinimumDelay = config.SyncConfiguration.MinimumSyncTime ?? TimeSpan.Zero,
+ SetPresence = config.SyncConfiguration.Presence ?? botConfig.Presence
};
/// <summary>Triggered when the application host is ready to start the service.</summary>
@@ -34,8 +36,8 @@ public class InviteHandlerHostedService(
private async Task? Run(CancellationToken cancellationToken) {
logger.LogInformation("Starting invite listener!");
var nextBatchFile = $"inviteHandler.{hs.WhoAmI.UserId}.{hs.WhoAmI.DeviceId}.nextBatch";
- if (listenerSyncConfiguration.Filter is not null) {
- _syncHelper.Filter = listenerSyncConfiguration.Filter;
+ if (config.SyncConfiguration.Filter is not null) {
+ _syncHelper.Filter = config.SyncConfiguration.Filter;
}
else {
_syncHelper.FilterId = await hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.libmatrix.utilities.bot.invite_listener_syncfilter.dev0", new SyncFilter() {
@@ -51,7 +53,7 @@ public class InviteHandlerHostedService(
});
}
- if (File.Exists(nextBatchFile) && !listenerSyncConfiguration.InitialSyncOnStartup) {
+ if (File.Exists(nextBatchFile) && !config.SyncConfiguration.InitialSyncOnStartup) {
_syncHelper.Since = await File.ReadAllTextAsync(nextBatchFile, cancellationToken);
}
@@ -70,7 +72,7 @@ public class InviteHandlerHostedService(
await inviteHandler(inviteEventArgs);
});
- if (!listenerSyncConfiguration.InitialSyncOnStartup)
+ if (!config.SyncConfiguration.InitialSyncOnStartup)
_syncHelper.SyncReceivedHandlers.Add(sync => File.WriteAllTextAsync(nextBatchFile, sync.NextBatch, cancellationToken));
await _syncHelper.RunSyncLoopAsync(cancellationToken: _cts.Token);
}
@@ -86,17 +88,4 @@ public class InviteHandlerHostedService(
await _cts.CancelAsync();
}
-
-
-
- [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Configuration")]
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Configuration")]
- public class InviteListenerSyncConfiguration {
- public InviteListenerSyncConfiguration(IConfiguration config) => config.GetSection("LibMatrixBot:InviteHandler:SyncConfiguration").Bind(this);
- public SyncFilter? Filter { get; set; }
- public TimeSpan? MinimumSyncTime { get; set; }
- public int? Timeout { get; set; }
- public string? Presence { get; set; }
- public bool InitialSyncOnStartup { get; set; }
- }
}
\ No newline at end of file
|