about summary refs log tree commit diff
path: root/MatrixContentFilter/Commands
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-10-04 19:51:44 +0200
committerRory& <root@rory.gay>2024-10-04 19:51:44 +0200
commitc8f7ef7c1d2bd705a5442c0dc591b8e5a50673a5 (patch)
tree9b951c6e2c120ec370ce8318238aadbdda880a89 /MatrixContentFilter/Commands
downloadMatrixContentFilter-master.tar.xz
Initial commit HEAD master
Diffstat (limited to 'MatrixContentFilter/Commands')
-rw-r--r--MatrixContentFilter/Commands/CheckHistoryCommand.cs74
-rw-r--r--MatrixContentFilter/Commands/ConfigureCommand.cs38
-rw-r--r--MatrixContentFilter/Commands/ConfigureSubCommands/ControlRoomConfigureSubcommand.cs18
-rw-r--r--MatrixContentFilter/Commands/DumpEventCommand.cs31
-rw-r--r--MatrixContentFilter/Commands/GetConfigCommand.cs56
-rw-r--r--MatrixContentFilter/Commands/NewRoomCommand.cs24
-rw-r--r--MatrixContentFilter/Commands/RedactCommand.cs99
7 files changed, 340 insertions, 0 deletions
diff --git a/MatrixContentFilter/Commands/CheckHistoryCommand.cs b/MatrixContentFilter/Commands/CheckHistoryCommand.cs
new file mode 100644
index 0000000..1e98545
--- /dev/null
+++ b/MatrixContentFilter/Commands/CheckHistoryCommand.cs
@@ -0,0 +1,74 @@
+using LibMatrix.Helpers;
+using LibMatrix.Homeservers;
+using LibMatrix.Utilities.Bot.Interfaces;
+using MatrixContentFilter.Abstractions;
+using MatrixContentFilter.Services;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixContentFilter.Commands;
+
+public class CheckHistoryCommand(
+    ConfigurationService filterConfigService,
+    IEnumerable<IContentFilter> filters,
+    AsyncMessageQueue msgQueue,
+    InfoCacheService infoCache
+) : ICommand {
+    public string Name { get; } = "checkhistory";
+    public string[]? Aliases { get; } = ["check"];
+    public string Description { get; } = "Re-apply filters to last x messages (default: 100)";
+    public bool Unlisted { get; } = false;
+
+    public async Task Invoke(CommandContext ctx) {
+        var count = 100;
+        if (ctx.Args.Length > 0) {
+            if (!int.TryParse(ctx.Args[0], out count)) {
+                await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice").WithBody($"'{count}' is not a valid number!").Build());
+                return;
+            }
+        }
+
+        msgQueue.EnqueueMessageAsync(filterConfigService.LogRoom,
+            new MessageBuilder("m.notice").WithBody($"Re-applying filters to last {count} messages in ")
+                .WithMention(ctx.Room.RoomId, await infoCache.GetRoomNameAsync(ctx.Room.RoomId)).Build());
+
+        await foreach (var resp in ctx.Room.GetManyMessagesAsync(limit: count, chunkSize: Math.Min(count, 250))) {
+            foreach (var filter in filters) {
+                await filter.ProcessEventListAsync(resp.Chunk);
+            }
+        }
+    }
+
+    // /// <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>
+    // protected override async Task ExecuteAsync(CancellationToken cancellationToken) {
+    //     while (!cancellationToken.IsCancellationRequested) {
+    //         await Task.Delay(10000, cancellationToken);
+    //         var rooms = await hs.GetJoinedRooms();
+    //         rooms.RemoveAll(x => x.RoomId == filterConfigService.LogRoom.RoomId);
+    //         rooms.RemoveAll(x => x.RoomId == filterConfigService.ControlRoom.RoomId);
+    //
+    //         var timelineFilter = new SyncFilter.RoomFilter.StateFilter(notTypes: ["m.room.redaction"], limit: 5000);
+    //         var timelines = rooms.Select(async x => {
+    //             var room = hs.GetRoom(x.RoomId);
+    //             // var sync = await room.GetMessagesAsync(null, 1500, filter: timelineFilter.ToJson(ignoreNull: true, indent: false).UrlEncode());
+    //             var iter = room.GetManyMessagesAsync(null, 5000, filter: timelineFilter.ToJson(ignoreNull: true, indent: false).UrlEncode(), chunkSize: 250);
+    //             await foreach (var sync in iter) {
+    //                 var tasks = Parallel.ForEachAsync(filters, async (filter, ct) => {
+    //                     try {
+    //                         Console.WriteLine("Processing filter {0} (sanity check, chunk[s={1}])", filter.GetType().FullName, sync.Chunk.Count);
+    //                         await filter.ProcessEventListAsync(sync.Chunk);
+    //                     }
+    //                     catch (Exception e) {
+    //                         logger.LogError(e, "Error processing sync with filter {filter}", filter.GetType().FullName);
+    //                         msgQueue.EnqueueMessageAsync(filterConfigService.LogRoom, new MessageBuilder("m.notice")
+    //                             .WithBody($"Error processing sync with filter {filter.GetType().FullName}: {e.Message}").Build());
+    //                     }
+    //                 });
+    //
+    //                 await tasks;
+    //             }
+    //         }).ToList();
+    //         await Task.WhenAll(timelines);
+    //     }
+    // }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/ConfigureCommand.cs b/MatrixContentFilter/Commands/ConfigureCommand.cs
new file mode 100644
index 0000000..756fc14
--- /dev/null
+++ b/MatrixContentFilter/Commands/ConfigureCommand.cs
@@ -0,0 +1,38 @@
+using System.Text;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.Utilities.Bot.Commands;
+using LibMatrix.Utilities.Bot.Interfaces;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MatrixContentFilter.Commands;
+
+public class ConfigureCommand(IServiceProvider svcs) : ICommandGroup {
+    public string Name { get; } = "configure";
+    public string[]? Aliases { get; } = ["config", "cfg"];
+    public string Description { get; }
+    public bool Unlisted { get; } = true;
+
+    public async Task Invoke(CommandContext ctx) {
+        var commands = svcs.GetServices<ICommand>().Where(x => x.GetType().IsAssignableTo(typeof(ICommand<>).MakeGenericType(GetType()))).ToList();
+
+        if (ctx.Args.Length == 0) {
+            await ctx.Room.SendMessageEventAsync(HelpCommand.GenerateCommandList(commands).Build());
+        }
+        else {
+            var subcommand = ctx.Args[0];
+            var command = commands.FirstOrDefault(x => x.Name == subcommand || x.Aliases?.Contains(subcommand) == true);
+            if (command == null) {
+                await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent("m.notice", "Unknown subcommand"));
+                return;
+            }
+
+            await command.Invoke(new CommandContext {
+                Room = ctx.Room,
+                MessageEvent = ctx.MessageEvent,
+                CommandName = ctx.CommandName,
+                Args = ctx.Args.Skip(1).ToArray(),
+                Homeserver = ctx.Homeserver
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/ConfigureSubCommands/ControlRoomConfigureSubcommand.cs b/MatrixContentFilter/Commands/ConfigureSubCommands/ControlRoomConfigureSubcommand.cs
new file mode 100644
index 0000000..5ff4f9d
--- /dev/null
+++ b/MatrixContentFilter/Commands/ConfigureSubCommands/ControlRoomConfigureSubcommand.cs
@@ -0,0 +1,18 @@
+using LibMatrix.Helpers;
+using LibMatrix.Utilities.Bot.Interfaces;
+
+namespace MatrixContentFilter.Commands.ConfigureSubCommands;
+
+public class ControlRoomConfigureSubCommand : ICommand<ConfigureCommand> {
+    public string Name { get; } = "controlroom";
+    public string[]? Aliases { get; }
+    public string Description { get; } = "Configure the control room";
+    public bool Unlisted { get; }
+
+    public async Task Invoke(CommandContext ctx) {
+        if (ctx.Args.Length == 0) {
+            await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice").WithBody("meow").Build());
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/DumpEventCommand.cs b/MatrixContentFilter/Commands/DumpEventCommand.cs
new file mode 100644
index 0000000..5131e19
--- /dev/null
+++ b/MatrixContentFilter/Commands/DumpEventCommand.cs
@@ -0,0 +1,31 @@
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Filters;
+using LibMatrix.Helpers;
+using LibMatrix.Homeservers;
+using LibMatrix.Utilities.Bot.Interfaces;
+using MatrixContentFilter.Abstractions;
+using MatrixContentFilter.Services;
+using MatrixContentFilter.Services.AsyncActionQueues;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixContentFilter.Commands;
+
+public class DumpEventCommand(
+    ConfigurationService filterConfigService,
+    AsyncMessageQueue msgQueue,
+    InfoCacheService infoCache,
+    ConfigurationService cfgService,
+    AbstractAsyncActionQueue actionQueue
+) : ICommand {
+    public string Name { get; } = "dump";
+    public string[]? Aliases { get; } = [];
+    public string Description { get; } = "Dump event by ID";
+    public bool Unlisted { get; } = false;
+
+    public async Task Invoke(CommandContext ctx) {
+        var evt = await ctx.Room.GetEventAsync(ctx.Args[0]);
+        await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice").WithBody(evt.ToJson(ignoreNull: true)).Build());
+    }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/GetConfigCommand.cs b/MatrixContentFilter/Commands/GetConfigCommand.cs
new file mode 100644
index 0000000..bac00ca
--- /dev/null
+++ b/MatrixContentFilter/Commands/GetConfigCommand.cs
@@ -0,0 +1,56 @@
+using System.Text;
+using ArcaneLibs.Attributes;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.Helpers;
+using LibMatrix.Utilities.Bot.Commands;
+using LibMatrix.Utilities.Bot.Interfaces;
+using MatrixContentFilter.EventTypes;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MatrixContentFilter.Commands;
+
+public class GetConfigCommand(IServiceProvider svcs) : ICommand {
+    public string Name { get; } = "getconfig";
+    public string[]? Aliases { get; } = [];
+    public string Description { get; } = "Get the current configuration, optionally takes a room ID";
+    public bool Unlisted { get; } = false;
+
+    public async Task Invoke(CommandContext ctx) {
+        var room = ctx.Room;
+        if (ctx.Args.Length > 0) {
+            try {
+                room = ctx.Homeserver.GetRoom(ctx.Args[0]);
+            }
+            catch {
+                await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice").WithBody("Invalid room ID").Build());
+                return;
+            }
+        }
+
+        var defaults = await ctx.Homeserver.GetAccountDataAsync<FilterConfiguration>(FilterConfiguration.EventId);
+        var config = await room.GetRoomAccountDataOrNullAsync<FilterConfiguration>(FilterConfiguration.EventId);
+        var msb = new MessageBuilder("m.notice").WithColoredBody("#FFCC00", "Default configuration:")
+            .WithTable(tb => {
+                foreach (var prop in defaults.GetType().GetProperties()) {
+                    var key = prop.GetFriendlyName();
+                    var val = prop.GetValue(defaults);
+
+                    tb = tb.WithRow(rb => {
+                        rb.WithCell(key);
+                        rb.WithCell(val?.ToJson() ?? "null");
+                    });
+                }
+            });
+
+        if (config == null) {
+            msb = msb.WithBody("No configuration set for this room, using defaults");
+        }
+        else {
+            msb = msb.WithBody("Room overrides (additive):")
+                .WithCodeBlock(config.ToJson(ignoreNull: true), "json");
+        }
+
+        await room.SendMessageEventAsync(msb.Build());
+    }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/NewRoomCommand.cs b/MatrixContentFilter/Commands/NewRoomCommand.cs
new file mode 100644
index 0000000..b8becd4
--- /dev/null
+++ b/MatrixContentFilter/Commands/NewRoomCommand.cs
@@ -0,0 +1,24 @@
+using System.Text;
+using ArcaneLibs.Attributes;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.Helpers;
+using LibMatrix.Utilities.Bot.Commands;
+using LibMatrix.Utilities.Bot.Interfaces;
+using MatrixContentFilter.EventTypes;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MatrixContentFilter.Commands;
+
+public class NewRoomCommand(IServiceProvider svcs) : ICommand {
+    public string Name { get; } = "newroom";
+    public string[]? Aliases { get; } = ["nr"];
+    public string Description { get; } = "Create a new room";
+    public bool Unlisted { get; } = false;
+
+    public async Task Invoke(CommandContext ctx) {
+        await ctx.Homeserver.CreateRoom(new() {
+            Invite = [ctx.MessageEvent.Sender!]
+        });
+    }
+}
\ No newline at end of file
diff --git a/MatrixContentFilter/Commands/RedactCommand.cs b/MatrixContentFilter/Commands/RedactCommand.cs
new file mode 100644
index 0000000..6b2f8b6
--- /dev/null
+++ b/MatrixContentFilter/Commands/RedactCommand.cs
@@ -0,0 +1,99 @@
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Filters;
+using LibMatrix.Helpers;
+using LibMatrix.Homeservers;
+using LibMatrix.Utilities.Bot.Interfaces;
+using MatrixContentFilter.Abstractions;
+using MatrixContentFilter.Services;
+using MatrixContentFilter.Services.AsyncActionQueues;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixContentFilter.Commands;
+
+public class RedactCommand(
+    ConfigurationService filterConfigService,
+    AsyncMessageQueue msgQueue,
+    InfoCacheService infoCache,
+    ConfigurationService cfgService,
+    AbstractAsyncActionQueue actionQueue
+) : ICommand {
+    public string Name { get; } = "redact";
+    public string[]? Aliases { get; } = [];
+    public string Description { get; } = "Redact last x messages from user (default: 500)";
+    public bool Unlisted { get; } = false;
+
+    public async Task Invoke(CommandContext ctx) {
+        var count = 500;
+
+        if (ctx.Args.Length == 0) {
+            await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice")
+                .WithBody("Please provide a user ID to redact messages from. (Make sure that it isn't formatted! Do not autocomplete!)").Build());
+            return;
+        }
+
+        var mxid = ctx.Args[0];
+        if (ctx.Args.Length > 1) {
+            if (!int.TryParse(ctx.Args[1], out count)) {
+                await ctx.Room.SendMessageEventAsync(new MessageBuilder("m.notice").WithBody($"'{count}' is not a valid number!").Build());
+                return;
+            }
+        }
+
+        var displayName = await infoCache.GetDisplayNameAsync(ctx.Room.RoomId, ctx.MessageEvent.Sender);
+        var roomName = await infoCache.GetRoomNameAsync(ctx.Room.RoomId);
+
+        msgQueue.EnqueueMessageAsync(filterConfigService.LogRoom,
+            new MessageBuilder("m.notice").WithBody($"Removing last {count} messages from ").WithMention(mxid)
+                .WithBody(" in ").WithMention(ctx.Room.RoomId, await infoCache.GetRoomNameAsync(ctx.Room.RoomId)).Build());
+        var hourglassReaction = await ctx.Room.SendTimelineEventAsync("m.reaction", new RoomMessageReactionEventContent() {
+            RelatesTo = new() {
+                EventId = ctx.MessageEvent.EventId,
+                RelationType = "m.annotation",
+                Key = "\u23f3" //hour glass emoji
+            }
+        });
+        
+        await foreach (var resp in ctx.Room.GetManyMessagesAsync(limit: count, chunkSize: Math.Min(count, 250)
+                           ,filter: new SyncFilter.RoomFilter.StateFilter(types: [RoomMemberEventContent.EventId, RoomMessageEventContent.EventId], senders: [mxid])
+                               .ToJson(indent: false, ignoreNull: true).UrlEncode())
+                           ) {
+            foreach (var msg in resp.Chunk) {
+                if (msg.Sender != mxid) continue;
+                if (msg is not { Type: RoomMemberEventContent.EventId or RoomMessageEventContent.EventId }) continue;
+                if (msg.RawContent is not { Count: > 0 }) continue;
+
+                await actionQueue.EqueueActionAsync(msg.EventId, async () => {
+                    while (true) {
+                        try {
+                            await ctx.Room.RedactEventAsync(msg.EventId ?? throw new ArgumentException("Event ID is null?"), "Message removed by moderator.");
+                            break;
+                        }
+                        catch (Exception e) {
+                            msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice")
+                                .WithBody($"Error redacting message in {ctx.Room.RoomId}!")
+                                .WithCollapsibleSection("Error data", msb => msb.WithCodeBlock(e.ToString(), "csharp"))
+                                .Build());
+                        }
+                    }
+
+                    msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice")
+                        .WithBody($"Message sent by ").WithMention(msg.Sender, displayName).WithBody(" in ").WithMention(ctx.Room.RoomId, roomName)
+                        .WithBody(" was removed in request by ").WithMention(ctx.Room.RoomId, roomName).WithBody("!").WithNewline()
+                        .WithCollapsibleSection("Message data", msb => msb.WithCodeBlock(msg.RawContent.ToJson(ignoreNull: true), "json"))
+                        .Build());
+                });
+            }
+            
+            await ctx.Room.RedactEventAsync(hourglassReaction.EventId);
+            await ctx.Room.SendTimelineEventAsync("m.reaction", new RoomMessageReactionEventContent() {
+                RelatesTo = new() {
+                    EventId = ctx.MessageEvent.EventId,
+                    RelationType = "m.annotation",
+                    Key = "\u2714\ufe0f" //check mark emoji
+                }
+            });
+        }
+    }
+}
\ No newline at end of file