Glob kick command (#291)

This pull requests adds for glob support in the `!mjolnir kick` command.

## Example
```
!mjolnir kick @*:domain.tld <reason> --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 <reason>
```

## 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 <me@jae.fi>
This commit is contained in:
Jae Lo Presti 2022-06-15 14:20:27 +03:00 committed by GitHub
parent 0eea04bd69
commit a876a05520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 14 deletions

View File

@ -131,7 +131,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st
"!mjolnir unban <list shortcode> <user|room|server> <glob> [apply] - Removes an entity from the ban list. If apply is 'true', the users matching the glob will actually be unbanned\n" +
"!mjolnir redact <user ID> [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 <event permalink> - Redacts a message by permalink\n" +
"!mjolnir kick <user ID> [room alias/ID] [reason] - Kicks a user in a particular room or all protected rooms\n" +
"!mjolnir kick <glob> [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" +

View File

@ -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 <user|filter> [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(' ') || '<no reason supplied>';
}
if (!reason) reason = "<none supplied>";
if (!reason) reason = '<none supplied>';
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);
}
}
}
}