
Anti-raid levels don't by themselves do anything, but they can be used in overrides to activate specific automod items. Spam detection should now be more reliable and also combine further spam messages after the initial detection into the archive. Messages deleted by automod no longer create the normal deletion log entry. Instead, the AUTOMOD_ACTION log entry contains the deleted message or an archive if there are multiple (i.e. spam).
159 lines
5 KiB
TypeScript
159 lines
5 KiB
TypeScript
import { IPluginOptions, logger } from "knub";
|
|
import * as t from "io-ts";
|
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
|
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
|
import { SavedMessage } from "../data/entities/SavedMessage";
|
|
import { convertDelayStringToMS, MINUTES, sorter, stripObjectToScalars, tDelayString } from "../utils";
|
|
import { GuildLogs } from "../data/GuildLogs";
|
|
import { LogType } from "../data/LogType";
|
|
import moment from "moment-timezone";
|
|
|
|
const AntiRaidLevel = t.type({
|
|
on_join: t.type({
|
|
kick: t.boolean,
|
|
ban: t.boolean,
|
|
}),
|
|
});
|
|
|
|
const ConfigSchema = t.type({
|
|
enabled: t.boolean,
|
|
delay: tDelayString,
|
|
});
|
|
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
|
|
|
interface IDeletionQueueItem {
|
|
deleteAt: number;
|
|
message: SavedMessage;
|
|
}
|
|
|
|
const MAX_DELAY = 5 * MINUTES;
|
|
|
|
export class AntiRaid extends ZeppelinPlugin<TConfigSchema> {
|
|
public static pluginName = "auto_delete";
|
|
public static showInDocs = true;
|
|
|
|
public static configSchema = ConfigSchema;
|
|
|
|
public static pluginInfo = {
|
|
prettyName: "Auto-delete",
|
|
description: "Allows Zeppelin to auto-delete messages from a channel after a delay",
|
|
configurationGuide: "Maximum deletion delay is currently 5 minutes",
|
|
};
|
|
|
|
protected guildSavedMessages: GuildSavedMessages;
|
|
protected guildLogs: GuildLogs;
|
|
|
|
protected onMessageCreateFn;
|
|
protected onMessageDeleteFn;
|
|
protected onMessageDeleteBulkFn;
|
|
|
|
protected deletionQueue: IDeletionQueueItem[];
|
|
protected nextDeletion: number;
|
|
protected nextDeletionTimeout;
|
|
|
|
protected maxDelayWarningSent = false;
|
|
|
|
public static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
|
|
return {
|
|
config: {
|
|
enabled: false,
|
|
delay: "5s",
|
|
},
|
|
};
|
|
}
|
|
|
|
protected onLoad() {
|
|
this.guildSavedMessages = GuildSavedMessages.getGuildInstance(this.guildId);
|
|
this.guildLogs = new GuildLogs(this.guildId);
|
|
|
|
this.deletionQueue = [];
|
|
|
|
this.onMessageCreateFn = this.onMessageCreate.bind(this);
|
|
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
|
|
this.onMessageDeleteBulkFn = this.onMessageDeleteBulk.bind(this);
|
|
|
|
this.guildSavedMessages.events.on("create", this.onMessageCreateFn);
|
|
this.guildSavedMessages.events.on("delete", this.onMessageDeleteFn);
|
|
this.guildSavedMessages.events.on("deleteBulk", this.onMessageDeleteBulkFn);
|
|
}
|
|
|
|
protected onUnload() {
|
|
this.guildSavedMessages.events.off("create", this.onMessageCreateFn);
|
|
this.guildSavedMessages.events.off("delete", this.onMessageDeleteFn);
|
|
this.guildSavedMessages.events.off("deleteBulk", this.onMessageDeleteFn);
|
|
clearTimeout(this.nextDeletionTimeout);
|
|
}
|
|
|
|
protected addMessageToDeletionQueue(msg: SavedMessage, delay: number) {
|
|
const deleteAt = Date.now() + delay;
|
|
this.deletionQueue.push({ deleteAt, message: msg });
|
|
this.deletionQueue.sort(sorter("deleteAt"));
|
|
|
|
this.scheduleNextDeletion();
|
|
}
|
|
|
|
protected scheduleNextDeletion() {
|
|
if (this.deletionQueue.length === 0) {
|
|
clearTimeout(this.nextDeletionTimeout);
|
|
return;
|
|
}
|
|
|
|
const firstDeleteAt = this.deletionQueue[0].deleteAt;
|
|
clearTimeout(this.nextDeletionTimeout);
|
|
this.nextDeletionTimeout = setTimeout(() => this.deleteNextItem(), firstDeleteAt - Date.now());
|
|
}
|
|
|
|
protected async deleteNextItem() {
|
|
const [itemToDelete] = this.deletionQueue.splice(0, 1);
|
|
if (!itemToDelete) return;
|
|
|
|
this.guildLogs.ignoreLog(LogType.MESSAGE_DELETE, itemToDelete.message.id);
|
|
this.bot.deleteMessage(itemToDelete.message.channel_id, itemToDelete.message.id).catch(logger.warn);
|
|
|
|
this.scheduleNextDeletion();
|
|
|
|
const user = await this.resolveUser(itemToDelete.message.user_id);
|
|
const channel = this.guild.channels.get(itemToDelete.message.channel_id);
|
|
const messageDate = moment(itemToDelete.message.data.timestamp, "x").format("YYYY-MM-DD HH:mm:ss");
|
|
|
|
this.guildLogs.log(LogType.MESSAGE_DELETE_AUTO, {
|
|
message: itemToDelete.message,
|
|
user: stripObjectToScalars(user),
|
|
channel: stripObjectToScalars(channel),
|
|
messageDate,
|
|
});
|
|
}
|
|
|
|
protected onMessageCreate(msg: SavedMessage) {
|
|
const config = this.getConfigForMemberIdAndChannelId(msg.user_id, msg.channel_id);
|
|
if (config.enabled) {
|
|
let delay = convertDelayStringToMS(config.delay);
|
|
|
|
if (delay > MAX_DELAY) {
|
|
delay = MAX_DELAY;
|
|
if (!this.maxDelayWarningSent) {
|
|
this.guildLogs.log(LogType.BOT_ALERT, {
|
|
body: `Clamped auto-deletion delay in <#${msg.channel_id}> to 5 minutes`,
|
|
});
|
|
this.maxDelayWarningSent = true;
|
|
}
|
|
}
|
|
|
|
this.addMessageToDeletionQueue(msg, delay);
|
|
}
|
|
}
|
|
|
|
protected onMessageDelete(msg: SavedMessage) {
|
|
const indexToDelete = this.deletionQueue.findIndex(item => item.message.id === msg.id);
|
|
if (indexToDelete > -1) {
|
|
this.deletionQueue.splice(indexToDelete, 1);
|
|
this.scheduleNextDeletion();
|
|
}
|
|
}
|
|
|
|
protected onMessageDeleteBulk(messages: SavedMessage[]) {
|
|
for (const msg of messages) {
|
|
this.onMessageDelete(msg);
|
|
}
|
|
}
|
|
}
|