fix(spam): count duplicate mentions; only clean offending messages
This commit is contained in:
parent
e8c021eea6
commit
847ee11195
2 changed files with 64 additions and 16 deletions
|
@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
30
src/utils.ts
30
src/utils.ts
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue