From a876a055203757c455aa781a06e63f5b50c8a07d Mon Sep 17 00:00:00 2001 From: Jae Lo Presti Date: Wed, 15 Jun 2022 14:20:27 +0300 Subject: [PATCH] Glob kick command (#291) This pull requests adds for glob support in the `!mjolnir kick` command. ## Example ``` !mjolnir kick @*:domain.tld --force ``` This command will kick every user having a mxid matching `domain.tld`. You can also still kick a particular user: ``` !mjolnir kick @user:domain.tld ``` ## Tests: Tested on the Furry Tech room (`vRGLvqJYlFvzpThbxI:matrix.org`) after a spam wave. It kicked over 13k bots in a matter of hours without putting too much strain on the homeserver. For instance, this command was matching `@spam*`: ![image](https://user-images.githubusercontent.com/76598503/167320002-f0575f50-4b54-41d1-8220-f67d72ccaf16.png) Signed-off-by: Jae Lo Presti --- src/commands/CommandHandler.ts | 2 +- src/commands/KickCommand.ts | 54 ++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 79c7b830..ea121676 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -131,7 +131,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st "!mjolnir unban [apply] - Removes an entity from the ban list. If apply is 'true', the users matching the glob will actually be unbanned\n" + "!mjolnir redact [room alias/ID] [limit] - Redacts messages by the sender in the target room (or all rooms), up to a maximum number of events in the backlog (default 1000)\n" + "!mjolnir redact - Redacts a message by permalink\n" + - "!mjolnir kick [room alias/ID] [reason] - Kicks a user in a particular room or all protected rooms\n" + + "!mjolnir kick [room alias/ID] [reason] - Kicks a user or all of those matching a glob in a particular room or all protected rooms\n" + "!mjolnir rules - Lists the rules currently in use by Mjolnir\n" + "!mjolnir sync - Force updates of all lists and re-apply rules\n" + "!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" + diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index deec02cf..786da20d 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -15,14 +15,31 @@ limitations under the License. */ import { Mjolnir } from "../Mjolnir"; -import { LogLevel } from "matrix-bot-sdk"; +import { LogLevel, MatrixGlob, RichReply } from "matrix-bot-sdk"; import config from "../config"; // !mjolnir kick [room] [reason] export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const userId = parts[2]; + let force = false; + const glob = parts[2]; let rooms = [...Object.keys(mjolnir.protectedRooms)]; + + if (parts[parts.length - 1] === "--force") { + force = true; + parts.pop(); + } + + if (config.commands.confirmWildcardBan && /[*?]/.test(glob) && !force) { + let replyMessage = "Wildcard bans require an addition `--force` argument to confirm"; + const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage); + reply["msgtype"] = "m.notice"; + await mjolnir.client.sendMessage(roomId, reply); + return; + } + + const kickRule = new MatrixGlob(glob); + let reason: string | undefined; if (parts.length > 3) { let reasonIndex = 3; @@ -32,19 +49,30 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln } reason = parts.slice(reasonIndex).join(' ') || ''; } - if (!reason) reason = ""; + if (!reason) reason = ''; - for (const targetRoomId of rooms) { - const joinedUsers = await mjolnir.client.getJoinedRoomMembers(targetRoomId); - if (!joinedUsers.includes(userId)) continue; // skip + for (const protectedRoomId of rooms) { + const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ["join"], ["ban", "leave"]); - await mjolnir.logMessage(LogLevel.INFO, "KickCommand", `Kicking ${userId} in ${targetRoomId} for ${reason}`, targetRoomId); - if (!config.noop) { - await mjolnir.taskQueue.push(async () => { - return mjolnir.client.kickUser(userId, targetRoomId, reason); - }); - } else { - await mjolnir.logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${userId} in ${targetRoomId} but the bot is running in no-op mode.`, targetRoomId); + for (const member of members) { + const victim = member.membershipFor; + + if (kickRule.test(victim)) { + await mjolnir.logMessage(LogLevel.DEBUG, "KickCommand", `Removing ${victim} in ${protectedRoomId}`, protectedRoomId); + + if (!config.noop) { + try { + await mjolnir.taskQueue.push(async () => { + return mjolnir.client.kickUser(victim, protectedRoomId, reason); + }); + await mjolnir.client.kickUser(victim, protectedRoomId, reason); + } catch (e) { + await mjolnir.logMessage(LogLevel.WARN, "KickCommand", `An error happened while trying to kick ${victim}: ${e}`); + } + } else { + await mjolnir.logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${victim} in ${protectedRoomId} but the bot is running in no-op mode.`, protectedRoomId); + } + } } }