Add spam plugin. Add clean commands. Update Knub to 9.6.0.
This commit is contained in:
parent
ad6afdfac1
commit
7ded84b924
11 changed files with 539 additions and 85 deletions
226
src/plugins/Spam.ts
Normal file
226
src/plugins/Spam.ts
Normal file
|
@ -0,0 +1,226 @@
|
|||
import { decorators as d, Plugin } from "knub";
|
||||
import { Message, TextChannel } from "eris";
|
||||
import {
|
||||
cleanMessagesInChannel,
|
||||
getEmojiInString,
|
||||
getUrlsInString,
|
||||
stripObjectToScalars
|
||||
} from "../utils";
|
||||
import { LogType } from "../data/LogType";
|
||||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { ModActionsPlugin } from "./ModActions";
|
||||
import { CaseType } from "../data/CaseType";
|
||||
|
||||
enum RecentActionType {
|
||||
Message = 1,
|
||||
Mention,
|
||||
Link,
|
||||
Attachment,
|
||||
Emoji,
|
||||
Newline
|
||||
}
|
||||
|
||||
interface IRecentAction {
|
||||
type: RecentActionType;
|
||||
userId: string;
|
||||
channelId: string;
|
||||
timestamp: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export class SpamPlugin extends Plugin {
|
||||
protected logs: GuildLogs;
|
||||
|
||||
protected recentActions: IRecentAction[];
|
||||
|
||||
private expiryInterval;
|
||||
|
||||
getDefaultOptions() {
|
||||
return {
|
||||
config: {
|
||||
max_messages: null,
|
||||
max_mentions: null,
|
||||
max_links: null,
|
||||
max_attachments: null,
|
||||
max_emojis: null,
|
||||
max_newlines: null,
|
||||
max_duplicates: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.logs = new GuildLogs(this.guildId);
|
||||
this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60);
|
||||
this.recentActions = [];
|
||||
}
|
||||
|
||||
onUnload() {
|
||||
clearInterval(this.expiryInterval);
|
||||
}
|
||||
|
||||
addRecentAction(
|
||||
type: RecentActionType,
|
||||
userId: string,
|
||||
channelId: string,
|
||||
timestamp: number,
|
||||
count = 1
|
||||
) {
|
||||
this.recentActions.push({
|
||||
type,
|
||||
userId,
|
||||
channelId,
|
||||
timestamp,
|
||||
count
|
||||
});
|
||||
}
|
||||
|
||||
getRecentActionCount(type: RecentActionType, userId: string, channelId: string, since: number) {
|
||||
return this.recentActions.reduce((count, action) => {
|
||||
if (action.timestamp < since) return count;
|
||||
if (action.type !== type) return count;
|
||||
if (action.channelId !== channelId) return count;
|
||||
return count + action.count;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
clearRecentUserActions(type: RecentActionType, userId: string, channelId: string) {
|
||||
this.recentActions = this.recentActions.filter(action => {
|
||||
return action.type !== type || action.userId !== userId || action.channelId !== channelId;
|
||||
});
|
||||
}
|
||||
|
||||
clearOldRecentActions() {
|
||||
// TODO: Figure out expiry time from longest interval in the config?
|
||||
const expiryTimestamp = Date.now() - 1000 * 60 * 5;
|
||||
this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp);
|
||||
}
|
||||
|
||||
async detectSpam(
|
||||
msg: Message,
|
||||
type: RecentActionType,
|
||||
spamConfig: any,
|
||||
actionCount: number,
|
||||
description: string
|
||||
) {
|
||||
if (actionCount === 0) return;
|
||||
|
||||
this.addRecentAction(type, msg.author.id, msg.channel.id, msg.timestamp, actionCount);
|
||||
const recentMessagesCount = this.getRecentActionCount(
|
||||
type,
|
||||
msg.author.id,
|
||||
msg.channel.id,
|
||||
msg.timestamp - 1000 * spamConfig.interval
|
||||
);
|
||||
|
||||
if (recentMessagesCount > spamConfig.count) {
|
||||
if (spamConfig.clean !== false) {
|
||||
const cleanCount =
|
||||
type === RecentActionType.Message ? spamConfig.count : spamConfig.cleanCount || 20;
|
||||
|
||||
await cleanMessagesInChannel(
|
||||
this.bot,
|
||||
msg.channel as TextChannel,
|
||||
cleanCount,
|
||||
msg.author.id,
|
||||
"Spam detected"
|
||||
);
|
||||
this.logs.log(LogType.SPAM_DELETE, {
|
||||
member: stripObjectToScalars(msg.member, ["user"]),
|
||||
channel: stripObjectToScalars(msg.channel),
|
||||
description,
|
||||
limit: spamConfig.count,
|
||||
interval: spamConfig.interval
|
||||
});
|
||||
}
|
||||
|
||||
if (spamConfig.mute) {
|
||||
const guildData = this.knub.getGuildData(this.guildId);
|
||||
const modActionsPlugin = guildData.loadedPlugins.get("mod_actions") as ModActionsPlugin;
|
||||
if (!modActionsPlugin) return;
|
||||
|
||||
this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, msg.member.id);
|
||||
await modActionsPlugin.muteMember(
|
||||
msg.member,
|
||||
spamConfig.muteTime ? spamConfig.muteTime * 1000 : 120 * 1000,
|
||||
"Automatic spam detection"
|
||||
);
|
||||
await modActionsPlugin.createCase(
|
||||
msg.member.id,
|
||||
this.bot.user.id,
|
||||
CaseType.Mute,
|
||||
null,
|
||||
"Automatic spam detection",
|
||||
true
|
||||
);
|
||||
this.logs.log(LogType.MEMBER_MUTE_SPAM, {
|
||||
member: stripObjectToScalars(msg.member, ["user"]),
|
||||
channel: stripObjectToScalars(msg.channel),
|
||||
description,
|
||||
limit: spamConfig.count,
|
||||
interval: spamConfig.interval
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@d.event("messageCreate")
|
||||
onMessageCreate(msg: Message) {
|
||||
if (msg.author.bot) return;
|
||||
|
||||
const maxMessages = this.configValueForMsg(msg, "max_messages");
|
||||
if (maxMessages) {
|
||||
this.detectSpam(msg, RecentActionType.Message, maxMessages, 1, "too many messages");
|
||||
}
|
||||
|
||||
const maxMentions = this.configValueForMsg(msg, "max_mentions");
|
||||
if (maxMentions && (msg.mentions.length || msg.roleMentions.length)) {
|
||||
this.detectSpam(
|
||||
msg,
|
||||
RecentActionType.Mention,
|
||||
maxMentions,
|
||||
msg.mentions.length + msg.roleMentions.length,
|
||||
"too many mentions"
|
||||
);
|
||||
}
|
||||
|
||||
const maxLinks = this.configValueForMsg(msg, "max_links");
|
||||
if (maxLinks && msg.content) {
|
||||
const links = getUrlsInString(msg.content);
|
||||
this.detectSpam(msg, RecentActionType.Link, maxLinks, links.length, "too many links");
|
||||
}
|
||||
|
||||
const maxAttachments = this.configValueForMsg(msg, "max_attachments");
|
||||
if (maxAttachments && msg.attachments.length) {
|
||||
this.detectSpam(
|
||||
msg,
|
||||
RecentActionType.Attachment,
|
||||
maxAttachments,
|
||||
msg.attachments.length,
|
||||
"too many attachments"
|
||||
);
|
||||
}
|
||||
|
||||
const maxEmoji = this.configValueForMsg(msg, "max_emoji");
|
||||
if (maxEmoji && msg.content) {
|
||||
const emojiCount = getEmojiInString(msg.content).length;
|
||||
this.detectSpam(msg, RecentActionType.Emoji, maxEmoji, emojiCount, "too many emoji");
|
||||
}
|
||||
|
||||
const maxNewlines = this.configValueForMsg(msg, "max_newlines");
|
||||
if (maxNewlines && msg.content) {
|
||||
const newlineCount = (msg.content.match(/\n/g) || []).length;
|
||||
this.detectSpam(
|
||||
msg,
|
||||
RecentActionType.Newline,
|
||||
maxNewlines,
|
||||
newlineCount,
|
||||
"too many newlines"
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Max duplicates
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue