fix(spam): count duplicate mentions; only clean offending messages

This commit is contained in:
Dragory 2018-08-01 19:13:32 +03:00
parent e8c021eea6
commit 847ee11195
2 changed files with 64 additions and 16 deletions

View file

@ -3,7 +3,9 @@ import { Message, TextChannel } from "eris";
import { import {
cleanMessagesInChannel, cleanMessagesInChannel,
getEmojiInString, getEmojiInString,
getRoleMentions,
getUrlsInString, getUrlsInString,
getUserMentions,
stripObjectToScalars stripObjectToScalars
} from "../utils"; } from "../utils";
import { LogType } from "../data/LogType"; import { LogType } from "../data/LogType";
@ -24,10 +26,13 @@ interface IRecentAction {
type: RecentActionType; type: RecentActionType;
userId: string; userId: string;
channelId: string; channelId: string;
msg: Message;
timestamp: number; timestamp: number;
count: number; count: number;
} }
const MAX_INTERVAL = 300;
export class SpamPlugin extends Plugin { export class SpamPlugin extends Plugin {
protected logs: GuildLogs; protected logs: GuildLogs;
@ -63,6 +68,7 @@ export class SpamPlugin extends Plugin {
type: RecentActionType, type: RecentActionType,
userId: string, userId: string,
channelId: string, channelId: string,
msg: Message,
timestamp: number, timestamp: number,
count = 1 count = 1
) { ) {
@ -70,11 +76,21 @@ export class SpamPlugin extends Plugin {
type, type,
userId, userId,
channelId, channelId,
msg,
timestamp, timestamp,
count count
}); });
} }
getRecentActions(type: RecentActionType, userId: string, channelId: string, since: number) {
return this.recentActions.filter(action => {
if (action.timestamp < since) return false;
if (action.type !== type) return false;
if (action.channelId !== channelId) return false;
return true;
});
}
getRecentActionCount(type: RecentActionType, userId: string, channelId: string, since: number) { getRecentActionCount(type: RecentActionType, userId: string, channelId: string, since: number) {
return this.recentActions.reduce((count, action) => { return this.recentActions.reduce((count, action) => {
if (action.timestamp < since) return count; if (action.timestamp < since) return count;
@ -92,7 +108,7 @@ export class SpamPlugin extends Plugin {
clearOldRecentActions() { clearOldRecentActions() {
// TODO: Figure out expiry time from longest interval in the config? // TODO: Figure out expiry time from longest interval in the config?
const expiryTimestamp = Date.now() - 1000 * 60 * 5; const expiryTimestamp = Date.now() - 1000 * MAX_INTERVAL;
this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp); this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp);
} }
@ -105,26 +121,23 @@ export class SpamPlugin extends Plugin {
) { ) {
if (actionCount === 0) return; if (actionCount === 0) return;
this.addRecentAction(type, msg.author.id, msg.channel.id, msg.timestamp, actionCount); const since = msg.timestamp - 1000 * spamConfig.interval;
const recentMessagesCount = this.getRecentActionCount(
this.addRecentAction(type, msg.author.id, msg.channel.id, msg, msg.timestamp, actionCount);
const recentActionsCount = this.getRecentActionCount(
type, type,
msg.author.id, msg.author.id,
msg.channel.id, msg.channel.id,
msg.timestamp - 1000 * spamConfig.interval since
); );
if (recentMessagesCount > spamConfig.count) { if (recentActionsCount > spamConfig.count) {
if (spamConfig.clean !== false) { if (spamConfig.clean !== false) {
const cleanCount = const recentActions = this.getRecentActions(type, msg.author.id, msg.channel.id, since);
type === RecentActionType.Message ? spamConfig.count : spamConfig.cleanCount || 20; const msgIds = recentActions.map(a => a.msg.id);
await this.bot.deleteMessages(msg.channel.id, msgIds);
await cleanMessagesInChannel(
this.bot,
msg.channel as TextChannel,
cleanCount,
msg.author.id,
"Spam detected"
);
this.logs.log(LogType.SPAM_DELETE, { this.logs.log(LogType.SPAM_DELETE, {
member: stripObjectToScalars(msg.member, ["user"]), member: stripObjectToScalars(msg.member, ["user"]),
channel: stripObjectToScalars(msg.channel), channel: stripObjectToScalars(msg.channel),
@ -135,6 +148,8 @@ export class SpamPlugin extends Plugin {
} }
if (spamConfig.mute) { if (spamConfig.mute) {
// For muting the user, we use the ModActions plugin
// This means that spam mute functionality requires the ModActions plugin to be loaded
const guildData = this.knub.getGuildData(this.guildId); const guildData = this.knub.getGuildData(this.guildId);
const modActionsPlugin = guildData.loadedPlugins.get("mod_actions") as ModActionsPlugin; const modActionsPlugin = guildData.loadedPlugins.get("mod_actions") as ModActionsPlugin;
if (!modActionsPlugin) return; if (!modActionsPlugin) return;
@ -176,12 +191,15 @@ export class SpamPlugin extends Plugin {
} }
const maxMentions = this.configValueForMsg(msg, "max_mentions"); const maxMentions = this.configValueForMsg(msg, "max_mentions");
if (maxMentions && (msg.mentions.length || msg.roleMentions.length)) { const mentions = msg.content
? [...getUserMentions(msg.content), ...getRoleMentions(msg.content)]
: [];
if (maxMentions && mentions.length) {
this.detectSpam( this.detectSpam(
msg, msg,
RecentActionType.Mention, RecentActionType.Mention,
maxMentions, maxMentions,
msg.mentions.length + msg.roleMentions.length, mentions.length,
"too many mentions" "too many mentions"
); );
} }

View file

@ -238,3 +238,33 @@ export function trimLines(str: string) {
export const emptyEmbedValue = "\u200b"; export const emptyEmbedValue = "\u200b";
export const embedPadding = "\n" + emptyEmbedValue; export const embedPadding = "\n" + emptyEmbedValue;
export const userMentionRegex = /<@!?([0-9]+)>/g;
export const roleMentionRegex = /<&([0-9]+)>/g;
export function getUserMentions(str: string) {
const regex = new RegExp(userMentionRegex.source, "g");
const userIds = [];
let match;
// tslint:disable-next-line
while ((match = regex.exec(str)) !== null) {
console.log("m", match);
userIds.push(match[1]);
}
return userIds;
}
export function getRoleMentions(str: string) {
const regex = new RegExp(roleMentionRegex.source, "g");
const roleIds = [];
let match;
// tslint:disable-next-line
while ((match = regex.exec(str)) !== null) {
roleIds.push(match[1]);
}
return roleIds;
}