about summary refs log tree commit diff
diff options
context:
space:
mode:
m---------LibMatrix0
-rw-r--r--MiniUtils.sln.DotSettings.user7
-rw-r--r--MiniUtils/Commands/DeleteRoomCommand.cs36
-rw-r--r--MiniUtils/Commands/DetectStateSplitCommand.cs72
-rw-r--r--MiniUtils/Commands/DumpTimelineCommand.cs38
-rw-r--r--MiniUtils/Commands/ExperimentalFeaturesCommand.cs5
-rw-r--r--MiniUtils/Commands/IgnoreCommand.cs4
-rw-r--r--MiniUtils/Commands/JoinCommand.cs26
-rw-r--r--MiniUtils/Commands/MakePolicyListCommand.cs1
-rw-r--r--MiniUtils/Commands/MscCommand.cs1
-rw-r--r--MiniUtils/Commands/OpsCommand.cs25
-rw-r--r--MiniUtils/Commands/RedactCommand.cs29
-rw-r--r--MiniUtils/Commands/SpamCommand.cs7
-rw-r--r--MiniUtils/MiniUtilsConfiguration.cs5
-rw-r--r--MiniUtils/Program.cs11
-rw-r--r--MiniUtils/Services/AutoTombstoneFollowerService.cs85
-rw-r--r--MiniUtils/Services/IgnoreListManager.cs20
17 files changed, 314 insertions, 58 deletions
diff --git a/LibMatrix b/LibMatrix
-Subproject dae1a25664606415e054f3e3b20bbbfabdbb0e9
+Subproject 28adb35ab9b6905eebcd83b6caa1b12d49b26be
diff --git a/MiniUtils.sln.DotSettings.user b/MiniUtils.sln.DotSettings.user

index 2946aec..f543792 100644 --- a/MiniUtils.sln.DotSettings.user +++ b/MiniUtils.sln.DotSettings.user
@@ -1,13 +1,18 @@ <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACallSiteFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ffc2027f7e776fc105cddb56b1a25eeb3895b3ae6f3aac854d786e63bd01f75e2_003FCallSiteFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConfigurationBinder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8feb37d1c2bc4bb7aba846da979b825aaf20_003F20_003Fa7159e0d_003FConfigurationBinder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultInterpolatedStringHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff1b929573c264d7a81f261ae2f951019d19e00_003F38_003F707b550d_003FDefaultInterpolatedStringHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe443585619b64bcd8252486eca6648c078a00_003F5c_003F555f35de_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003F97_003Ffb30ee1f_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff1b929573c264d7a81f261ae2f951019d19e00_003Fc1_003F72e4c91c_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHashtable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003Fd2_003F20632896_003FHashtable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpResponseMessage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdc2c4f9d0a9d4546b54cbaedf35715951a1e00_003F2f_003F4be0d618_003FHttpResponseMessage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003F65_003Fb77a719c_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff1b929573c264d7a81f261ae2f951019d19e00_003F2f_003F707c45aa_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObjectExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8fd5e96d6574456095123be1ecfbdfa914200_003Fe2_003F3561a383_003FObjectExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARune_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff1b929573c264d7a81f261ae2f951019d19e00_003F50_003F2cf1d657_003FRune_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionHostedServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff13297a632424a6abffea4dd75a36a75d128_003Ff9_003Fb35aae11_003FServiceCollectionHostedServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003F86_003F8b4aa64e_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> - <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATaskAwaiter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003F10_003F04572d58_003FTaskAwaiter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff1b929573c264d7a81f261ae2f951019d19e00_003Fc5_003Fbce9c992_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATaskAwaiter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea51ca5e833244688d7ca912cfc70784d19c00_003F10_003F04572d58_003FTaskAwaiter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe443585619b64bcd8252486eca6648c078a00_003Faf_003Fe6779903_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file diff --git a/MiniUtils/Commands/DeleteRoomCommand.cs b/MiniUtils/Commands/DeleteRoomCommand.cs
index 41dbfb3..8d02dfb 100644 --- a/MiniUtils/Commands/DeleteRoomCommand.cs +++ b/MiniUtils/Commands/DeleteRoomCommand.cs
@@ -1,9 +1,4 @@ -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Common; -using LibMatrix.EventTypes.Spec.State.RoomInfo; -using LibMatrix.Helpers; -using LibMatrix.Responses; -using LibMatrix.RoomTypes; +using LibMatrix.Homeservers; using LibMatrix.Utilities.Bot.Interfaces; namespace MiniUtils.Commands; @@ -18,30 +13,9 @@ public class DeleteRoomCommand() : ICommand { public bool Unlisted => false; public async Task Invoke(CommandContext ctx) { - var creationContent = new CreateRoomRequest() { - Name = ctx.Args[0], - RoomAliasName = ctx.Args[0], - Visibility = "private", - CreationContent = new() { - { "type", PolicyRoom.TypeName }, - { "room_version", 11 } - }, - PowerLevelContentOverride = new RoomPowerLevelEventContent() { - EventsDefault = 50, - Invite = 50 - }, - InitialState = [ - new() { - Type = MjolnirShortcodeEventContent.EventId, - StateKey = "", - TypedContent = new MjolnirShortcodeEventContent() { - Shortcode = ctx.Args[0] - } - } - ] - }; - - var result = await ctx.Homeserver.CreateRoom(creationContent); - await ctx.Room.SendMessageEventAsync(new MessageBuilder().WithMention($"#{ctx.Args[0]}:{ctx.Homeserver.ServerName}").Build()); + if (ctx.Homeserver is not AuthenticatedHomeserverSynapse synapse) return; + var res = await synapse.Admin.DeleteRoom(ctx.Args[0], new() { + Purge = true + }, waitForCompletion: false); } } \ No newline at end of file diff --git a/MiniUtils/Commands/DetectStateSplitCommand.cs b/MiniUtils/Commands/DetectStateSplitCommand.cs new file mode 100644
index 0000000..6180f20 --- /dev/null +++ b/MiniUtils/Commands/DetectStateSplitCommand.cs
@@ -0,0 +1,72 @@ +using System.Collections.Frozen; +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.Helpers; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot.Interfaces; + +namespace MiniUtils.Commands; + +public class DetectStateSplitCommand(MiniUtilsConfiguration config, HomeserverProviderService hsProvider) : ICommand { + public string Name => "detect state split"; + + public string[]? Aliases => ["dss"]; + + public string Description => "Detect room splits"; + + public bool Unlisted => false; + + public async Task Invoke(CommandContext ctx) { + var profile = config.ExternalProfiles[ctx.Args[0]]; + var rhs = await hsProvider.GetAuthenticatedWithToken(profile.Homeserver, profile.AccessToken, enableServer: false, useGeneric: true); + var localStateTask = ctx.Room.GetFullStateAsListAsync(); + var remoteStateTask = rhs.GetRoom(ctx.Room.RoomId).GetFullStateAsListAsync(); + + var localState = await localStateTask; + var remoteState = await remoteStateTask; + + var keySet = localState.Concat(remoteState) + .Select(x => (x.Type, x.StateKey)) + .ToFrozenSet(); + + var differences = 0; + foreach (var keyPair in keySet) { + var local = localState.FirstOrDefault(x => x.Type == keyPair.Type && x.StateKey == keyPair.StateKey); + var remote = remoteState.FirstOrDefault(x => x.Type == keyPair.Type && x.StateKey == keyPair.StateKey); + + if (local == null) { + await ctx.Room.SendMessageEventAsync(new MessageBuilder() + .WithCollapsibleSection($"Missing {keyPair.Type} {keyPair.StateKey} locally", b => + b.WithCodeBlock(remote.ToJson(ignoreNull: true), "json") + ).Build()); + differences++; + continue; + } + + if (remote == null) { + await ctx.Room.SendMessageEventAsync(new MessageBuilder() + .WithCollapsibleSection($"Missing {keyPair.Type} {keyPair.StateKey} remotely", b => + b.WithCodeBlock(local.ToJson(ignoreNull: true), "json") + ).Build()); + differences++; + continue; + } + + if (!StateEvent.Equals(local, remote)) { + await ctx.Room.SendMessageEventAsync(new MessageBuilder() + .WithCollapsibleSection($"Different {keyPair.Type} {keyPair.StateKey}", b => + b.WithCodeBlock(local.ToJson(ignoreNull: true), "json") + .WithCodeBlock(remote.ToJson(ignoreNull: true), "json") + ).Build()); + differences++; + } + } + + if (differences > 0) + await ctx.Room.SendMessageEventAsync(new MessageBuilder().WithBody($"No differences found").Build()); + else + await ctx.Room.SendMessageEventAsync(new MessageBuilder().WithBody($"Found {differences} differences!").Build()); + + // await ctx.Room.SendMessageEventAsync(new MessageBuilder().WithCodeBlock(rhs.WhoAmI.ToJson()).Build()); + } +} \ No newline at end of file diff --git a/MiniUtils/Commands/DumpTimelineCommand.cs b/MiniUtils/Commands/DumpTimelineCommand.cs new file mode 100644
index 0000000..4ee53c9 --- /dev/null +++ b/MiniUtils/Commands/DumpTimelineCommand.cs
@@ -0,0 +1,38 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using LibMatrix; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot.Interfaces; + +namespace MiniUtils.Commands; + +public class DumpTimelineCommand(MiniUtilsConfiguration config, HomeserverProviderService hsProvider) : ICommand { + public string Name => "dump timeline"; + + public string[]? Aliases => ["dt"]; + + public string Description => "Dump timeline"; + + public bool Unlisted => false; + + public async Task Invoke(CommandContext ctx) { + MessagesResponse res; + if (ctx.Args.Length < 1) { + res = await ctx.Room.GetMessagesAsync(limit: 250); + } + else { + var profile = config.ExternalProfiles[ctx.Args[0]]; + var rhs = await hsProvider.GetAuthenticatedWithToken(profile.Homeserver, profile.AccessToken, enableServer: false, useGeneric: true); + res = await rhs.GetRoom(ctx.Room.RoomId).GetMessagesAsync(limit: 250); + } + + var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, res, new JsonSerializerOptions() { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true + }); + ms.Seek(0, SeekOrigin.Begin); + + await ctx.Room.SendFileAsync("timeline.json", ms, contentType: "application/json"); + } +} \ No newline at end of file diff --git a/MiniUtils/Commands/ExperimentalFeaturesCommand.cs b/MiniUtils/Commands/ExperimentalFeaturesCommand.cs
index de5d035..8ae40d3 100644 --- a/MiniUtils/Commands/ExperimentalFeaturesCommand.cs +++ b/MiniUtils/Commands/ExperimentalFeaturesCommand.cs
@@ -1,9 +1,4 @@ -using System.Globalization; -using System.Net.Http.Json; -using System.Text; -using System.Text.Json.Nodes; using System.Text.RegularExpressions; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; using LibMatrix.Helpers; using LibMatrix.Utilities.Bot.Interfaces; diff --git a/MiniUtils/Commands/IgnoreCommand.cs b/MiniUtils/Commands/IgnoreCommand.cs
index 4b3fe86..4206b72 100644 --- a/MiniUtils/Commands/IgnoreCommand.cs +++ b/MiniUtils/Commands/IgnoreCommand.cs
@@ -51,6 +51,10 @@ public class IgnoreCommand(IgnoreListManager ignoreListManager) : ICommand { var count = await ignoreListManager.MoveList(true, itemsToEnable); await ctx.Room.SendReactionAsync(ctx.MessageEvent.EventId!, $"{Emojis.RightArrowWithTail} {count}"); } + else if (ctx.Args is ["add", .. var itemsToAdd]) { + var count = await ignoreListManager.AddList(itemsToAdd); + await ctx.Room.SendReactionAsync(ctx.MessageEvent.EventId!, $"{Emojis.RightArrowWithTail} {count}"); + } } private async Task Summarize(CommandContext ctx, IgnoredUserListEventContentWithDisabled ignoreList) { diff --git a/MiniUtils/Commands/JoinCommand.cs b/MiniUtils/Commands/JoinCommand.cs new file mode 100644
index 0000000..1ed51b6 --- /dev/null +++ b/MiniUtils/Commands/JoinCommand.cs
@@ -0,0 +1,26 @@ +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot.Interfaces; + +namespace MiniUtils.Commands; + +public class JoinCommand(MiniUtilsConfiguration config, HomeserverProviderService hsProvider) : ICommand { + public string Name => "join"; + + public string[]? Aliases => []; + + public string Description => "Redact all user's events"; + + public bool Unlisted => false; + public async Task Invoke(CommandContext ctx) { + var profile = config.ExternalProfiles[ctx.Args[0]]; + var rhs = await hsProvider.GetAuthenticatedWithToken(profile.Homeserver, profile.AccessToken, enableServer: false, useGeneric: true); + + var joinRules = await ctx.Room.GetJoinRuleAsync(); + if (joinRules?.JoinRule is not RoomJoinRulesEventContent.JoinRules.Public) { + await ctx.Room.InviteUserAsync(rhs.UserId); + } + + _ = rhs.GetRoom(ctx.Room.RoomId).JoinAsync([ctx.Homeserver.ServerName]); + } +} \ No newline at end of file diff --git a/MiniUtils/Commands/MakePolicyListCommand.cs b/MiniUtils/Commands/MakePolicyListCommand.cs
index 0498712..40b0695 100644 --- a/MiniUtils/Commands/MakePolicyListCommand.cs +++ b/MiniUtils/Commands/MakePolicyListCommand.cs
@@ -1,4 +1,3 @@ -using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Common; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; diff --git a/MiniUtils/Commands/MscCommand.cs b/MiniUtils/Commands/MscCommand.cs
index 62f1bd7..89e9aec 100644 --- a/MiniUtils/Commands/MscCommand.cs +++ b/MiniUtils/Commands/MscCommand.cs
@@ -1,4 +1,3 @@ -using LibMatrix.Extensions; using LibMatrix.Helpers; using LibMatrix.Utilities.Bot.Interfaces; using MiniUtils.Utilities; diff --git a/MiniUtils/Commands/OpsCommand.cs b/MiniUtils/Commands/OpsCommand.cs new file mode 100644
index 0000000..eef6c6f --- /dev/null +++ b/MiniUtils/Commands/OpsCommand.cs
@@ -0,0 +1,25 @@ +using LibMatrix.Helpers; +using LibMatrix.Utilities.Bot.Interfaces; +using MiniUtils.Services; + +namespace MiniUtils.Commands; + +public class OpsCommand(IgnoreListManager ignoreListManager) : ICommand { + public string Name => "ops"; + + public string[]? Aliases => ["admins", "mods"]; + + public string Description => "Ping all the mods"; + + public bool Unlisted => true; + + public async Task Invoke(CommandContext ctx) { + var pls = await ctx.Room.GetPowerLevelsAsync(); + var msb = new MessageBuilder(); + foreach (var pl in pls.Users.Where(x => x.Value >= pls.Kick)) { + msb = msb.WithMention(pl.Key).WithBody(" "); + } + + await ctx.Room.SendMessageEventAsync(msb.Build()); + } +} \ No newline at end of file diff --git a/MiniUtils/Commands/RedactCommand.cs b/MiniUtils/Commands/RedactCommand.cs
index cba06c9..e84191e 100644 --- a/MiniUtils/Commands/RedactCommand.cs +++ b/MiniUtils/Commands/RedactCommand.cs
@@ -1,6 +1,6 @@ using System.Collections.Frozen; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec; +using LibMatrix; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; @@ -19,10 +19,6 @@ public class RedactCommand(IgnoreListManager ignoreListManager) : ICommand { public string Description => "Redact all user's events"; public bool Unlisted => false; - private const string ThumbsUp = "\ud83d\udc4d\ufe0e"; - private const string Recycle = "\u267b\ufe0e"; - private const string Bullseye = "\u25ce\ufe0e"; - private const string RightArrowWithTail = "\u21a3\ufe0e"; public async Task Invoke(CommandContext ctx) { if (ctx.Args is ["banned"]) @@ -42,8 +38,7 @@ public class RedactCommand(IgnoreListManager ignoreListManager) : ICommand { foreach (var chunk in resp.Chunk.Chunk(49)) { foreach (var evt in chunk) { if (!senders.Contains(evt.Sender!)) continue; - if (evt is { StateKey: not null, Type: not RoomMemberEventContent.EventId }) continue; - if (evt is { RawContent: null or { Count: 0 } }) continue; + if(!await IsRedactionNeeded(ctx.Room, evt.EventId!, evt)) continue; tasks.Add(RedactEvent(ctx.Room, evt.EventId!)); count++; } @@ -64,6 +59,26 @@ public class RedactCommand(IgnoreListManager ignoreListManager) : ICommand { // await ctx.Room.SendReactionAsync(ctx.MessageEvent.EventId!, $"{Emojis.Recycle} {count}"); } + private async Task<bool> IsRedactionNeeded(GenericRoom roomId, string eventId, StateEventResponse? evt = null) { + evt ??= await roomId.GetEventAsync(eventId); + + // Ignore room member state events + if (evt is { StateKey: not null, Type: not RoomMemberEventContent.EventId }) return false; + + // Ignore redaction events + if (evt is { Type: RoomRedactionEventContent.EventId }) return false; + + // Ignore empty events + if (evt is { RawContent: null or { Count: 0 } }) return false; + + // Ignore redacted events + if (evt.Unsigned?.ContainsKey("redacted_because") == true) return false; + + + + throw new NotImplementedException("Redaction check not implemented"); + } + private async Task RedactEvent(GenericRoom room, string eventId) { bool success; do { diff --git a/MiniUtils/Commands/SpamCommand.cs b/MiniUtils/Commands/SpamCommand.cs
index 9f475eb..742ae3b 100644 --- a/MiniUtils/Commands/SpamCommand.cs +++ b/MiniUtils/Commands/SpamCommand.cs
@@ -1,8 +1,3 @@ -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.RoomTypes; using LibMatrix.Utilities.Bot.Interfaces; @@ -18,7 +13,7 @@ public class SpamCommand(IgnoreListManager ignoreListManager) : ICommand { public string Description => "Redact all user's events"; - public bool Unlisted => false; + public bool Unlisted => true; public async Task Invoke(CommandContext ctx) { var tasks = Enumerable.Range(0, 10000) diff --git a/MiniUtils/MiniUtilsConfiguration.cs b/MiniUtils/MiniUtilsConfiguration.cs
index 292c12e..b1f439e 100644 --- a/MiniUtils/MiniUtilsConfiguration.cs +++ b/MiniUtils/MiniUtilsConfiguration.cs
@@ -1,6 +1,11 @@ +using LibMatrix.Responses; + namespace MiniUtils; public class MiniUtilsConfiguration { public MiniUtilsConfiguration(IConfiguration config) => config.GetRequiredSection("MiniUtils").Bind(this); + public string? GithubToken { get; set; } + public required Dictionary<string, LoginResponse> ExternalProfiles { get; set; } + public bool FollowTombstones { get; set; } } \ No newline at end of file diff --git a/MiniUtils/Program.cs b/MiniUtils/Program.cs
index 37308ea..acf1902 100644 --- a/MiniUtils/Program.cs +++ b/MiniUtils/Program.cs
@@ -8,18 +8,17 @@ using MiniUtils.Utilities; var builder = Host.CreateApplicationBuilder(args); -builder.Services.AddSingleton<MiniUtilsConfiguration>(); -builder.Services.AddSingleton<MscInfoProvider>(); -builder.Services.AddSingleton<IgnoreListManager>(); builder.Services.AddRoryLibMatrixServices() .AddMatrixBot() - // .WithInviteHandler<RoomInviteHandler>(); .AddCommandHandler() .DiscoverAllCommands(); -// builder.Services.AddHostedService<PolicyListFetcher>(); -// builder.Services.AddHostedService<PolicyExecutor>(); +builder.Services.AddSingleton<MiniUtilsConfiguration>(); +builder.Services.AddSingleton<MscInfoProvider>(); +builder.Services.AddSingleton<IgnoreListManager>(); + builder.Services.AddHostedService<MiniUtilsWorker>(); +builder.Services.AddHostedService<AutoTombstoneFollowerService>(); // builder.Services.AddSingleton<PolicyStore>(); diff --git a/MiniUtils/Services/AutoTombstoneFollowerService.cs b/MiniUtils/Services/AutoTombstoneFollowerService.cs new file mode 100644
index 0000000..0b9a444 --- /dev/null +++ b/MiniUtils/Services/AutoTombstoneFollowerService.cs
@@ -0,0 +1,85 @@ +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Filters; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; + +namespace MiniUtils.Services; + +public class AutoTombstoneFollowerService( + AuthenticatedHomeserverGeneric hs, + ILogger<AutoTombstoneFollowerService> logger, + MiniUtilsConfiguration config +) + : IHostedService { + private Task? _listenerTask; + private readonly CancellationTokenSource _cts = new(); + + /// <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) { + if (!config.FollowTombstones) return Task.CompletedTask; + _listenerTask = Run(_cts.Token); + logger.LogInformation("Tombstone follower started (StartAsync)!"); + return Task.CompletedTask; + } + + private async Task? Run(CancellationToken cancellationToken) { + logger.LogInformation("Starting Tombstone listener!"); + var filter = await hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.miniutils.services.tombstone_follower", + 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(types: [RoomTombstoneEventContent.EventId]), + Timeline = new SyncFilter.RoomFilter.StateFilter(types: [RoomTombstoneEventContent.EventId]), + } + }); + + var syncHelper = new SyncHelper(hs, logger) { + FilterId = filter, + UseMsc4222StateAfter = true + }; + + syncHelper.SyncReceivedHandlers.Add(async sync => { + logger.LogInformation("Sync received!"); + var joinedRooms = await hs.GetJoinedRooms(); + foreach (var roomResp in sync.Rooms?.Join ?? []) { + if (roomResp.Value.StateAfter?.Events is null) continue; + foreach (var @event in roomResp.Value.StateAfter.Events) { + if (@event is not { Type: RoomTombstoneEventContent.EventId, StateKey: not null }) continue; + var replacement = @event.ContentAs<RoomTombstoneEventContent>()!.ReplacementRoom; + if (string.IsNullOrWhiteSpace(replacement)) { + logger.LogError("[{}] Tombstone event with no replacement room!", roomResp.Key); + continue; + } + + var room = hs.GetRoom(roomResp.Key); + if (joinedRooms.Any(x => x.RoomId == replacement)) { + // logger.LogWarning("[{}] Replacement room {} is already joined!", roomResp.Key, replacement); + continue; + } + + await room.JoinAsync(reason: "Following tombstone", homeservers: [replacement.Split(':', 2)[1]]); + await Task.Delay(1000, cancellationToken); + joinedRooms = await hs.GetJoinedRooms(); + } + } + }); + + await syncHelper.RunSyncLoopAsync(cancellationToken: _cts.Token); + } + + /// <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!"); + if (_listenerTask is null) { + logger.LogError("Could not shut down command listener task because it was null!"); + return; + } + + await _cts.CancelAsync(); + } +} \ No newline at end of file diff --git a/MiniUtils/Services/IgnoreListManager.cs b/MiniUtils/Services/IgnoreListManager.cs
index c42dd02..3b6dc96 100644 --- a/MiniUtils/Services/IgnoreListManager.cs +++ b/MiniUtils/Services/IgnoreListManager.cs
@@ -64,4 +64,24 @@ public class IgnoreListManager(AuthenticatedHomeserverGeneric homeserver) { Lock.Release(); return moved; } + + public async Task<int> AddList(string[] itemsToAdd) { + int added = 0; + await Lock.WaitAsync(); + var ignoreList = await homeserver.GetAccountDataOrNullAsync<IgnoredUserListEventContentWithDisabled>(IgnoredUserListEventContent.EventId) ?? new(); + foreach (var item in itemsToAdd) { + if (ignoreList.IgnoredUsers.ContainsKey(item)) continue; + if (ignoreList.DisabledIgnoredUsers.Remove(item, out var value)) { + ignoreList.IgnoredUsers.Add(item, value); + added++; + continue; + } + ignoreList.IgnoredUsers.Add(item, new()); + added++; + } + if (added > 0) + await homeserver.SetAccountDataAsync(IgnoredUserListEventContent.EventId, ignoreList); + Lock.Release(); + return added; + } } \ No newline at end of file