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
|