Port Censor and Spam plugins to use GuildSavedMessages events

This commit is contained in:
Dragory 2018-11-24 17:12:36 +02:00
parent fbb1ee4719
commit 1a6e680d81
4 changed files with 237 additions and 114 deletions

View file

@ -32,7 +32,7 @@ export class QueuedEventEmitter {
const listeners = [...(this.listeners.get(eventName) || []), ...(this.listeners.get("*") || [])]; const listeners = [...(this.listeners.get(eventName) || []), ...(this.listeners.get("*") || [])];
listeners.forEach(listener => { listeners.forEach(listener => {
this.queue.add(listener.bind(null, args)); this.queue.add(listener.bind(null, ...args));
}); });
} }
} }

View file

@ -73,6 +73,16 @@ export class GuildSavedMessages extends BaseRepository {
.getOne(); .getOne();
} }
getUserMessagesByChannelAfterId(userId, channelId, afterId) {
return this.messages
.createQueryBuilder()
.where("guild_id = :guild_id", { guild_id: this.guildId })
.where("user_id = :user_id", { user_id: userId })
.where("channel_id = :channel_id", { channel_id: channelId })
.where("id > :afterId", { afterId })
.getMany();
}
async create(data) { async create(data) {
try { try {
await this.messages.insert(data); await this.messages.insert(data);
@ -128,7 +138,7 @@ export class GuildSavedMessages extends BaseRepository {
} }
); );
this.events.emit("edit", [newMessage, oldMessage]); this.events.emit("update", [newMessage, oldMessage]);
} }
async saveEditFromMsg(msg: Message) { async saveEditFromMsg(msg: Message) {

View file

@ -5,9 +5,15 @@ import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType"; import { LogType } from "../data/LogType";
import { getInviteCodesInString, getUrlsInString, stripObjectToScalars } from "../utils"; import { getInviteCodesInString, getUrlsInString, stripObjectToScalars } from "../utils";
import { ZalgoRegex } from "../data/Zalgo"; import { ZalgoRegex } from "../data/Zalgo";
import { GuildSavedMessages } from "../data/GuildSavedMessages";
import { SavedMessage } from "../data/entities/SavedMessage";
export class CensorPlugin extends Plugin { export class CensorPlugin extends Plugin {
protected serverLogs: GuildLogs; protected serverLogs: GuildLogs;
protected savedMessages: GuildSavedMessages;
private onMessageCreateFn;
private onMessageUpdateFn;
getDefaultOptions() { getDefaultOptions() {
return { return {
@ -46,58 +52,94 @@ export class CensorPlugin extends Plugin {
onLoad() { onLoad() {
this.serverLogs = new GuildLogs(this.guildId); this.serverLogs = new GuildLogs(this.guildId);
this.savedMessages = GuildSavedMessages.getInstance(this.guildId);
this.onMessageCreateFn = this.onMessageCreate.bind(this);
this.onMessageUpdateFn = this.onMessageUpdate.bind(this);
this.savedMessages.events.on("create", this.onMessageCreateFn);
this.savedMessages.events.on("update", this.onMessageUpdateFn);
} }
async censorMessage(msg: Message, reason: string) { onUnload() {
this.serverLogs.ignoreLog(LogType.MESSAGE_DELETE, msg.id); this.savedMessages.events.off("create", this.onMessageCreateFn);
this.savedMessages.events.off("update", this.onMessageUpdateFn);
}
async censorMessage(savedMessage: SavedMessage, reason: string) {
this.serverLogs.ignoreLog(LogType.MESSAGE_DELETE, savedMessage.id);
try { try {
await msg.delete("Censored"); await this.bot.deleteMessage(savedMessage.channel_id, savedMessage.id, "Censored");
} catch (e) { } catch (e) {
return; return;
} }
const member = this.guild.members.get(savedMessage.user_id);
const channel = this.guild.channels.get(savedMessage.channel_id);
this.serverLogs.log(LogType.CENSOR, { this.serverLogs.log(LogType.CENSOR, {
member: stripObjectToScalars(msg.member, ["user"]), member: stripObjectToScalars(member, ["user"]),
channel: stripObjectToScalars(msg.channel), channel: stripObjectToScalars(channel),
reason, reason,
messageText: msg.cleanContent messageText: savedMessage.data.content
}); });
} }
async applyFiltersToMsg(msg: Message) { async applyFiltersToMsg(savedMessage: SavedMessage) {
if (!msg.author || msg.author.bot) return; if (!savedMessage.data.content) return;
if (msg.type !== 0) return;
if (!msg.content) return;
// Filter zalgo // Filter zalgo
if (this.configValueForMsg(msg, "filter_zalgo")) { const filterZalgo = this.configValueForMemberIdAndChannelId(
const result = ZalgoRegex.exec(msg.content); savedMessage.user_id,
savedMessage.channel_id,
"filter_zalgo"
);
if (filterZalgo) {
const result = ZalgoRegex.exec(savedMessage.data.content);
if (result) { if (result) {
this.censorMessage(msg, `zalgo detected`); this.censorMessage(savedMessage, "zalgo detected");
return; return;
} }
} }
// Filter invites // Filter invites
if (this.configValueForMsg(msg, "filter_invites")) { const filterInvites = this.configValueForMemberIdAndChannelId(
const inviteGuildWhitelist: string[] = this.configValueForMsg(msg, "invite_guild_whitelist"); savedMessage.user_id,
const inviteGuildBlacklist: string[] = this.configValueForMsg(msg, "invite_guild_blacklist"); savedMessage.channel_id,
const inviteCodeWhitelist: string[] = this.configValueForMsg(msg, "invite_code_whitelist"); "filter_invites"
const inviteCodeBlacklist: string[] = this.configValueForMsg(msg, "invite_code_blacklist"); );
if (filterInvites) {
const inviteCodes = getInviteCodesInString(msg.content); const inviteGuildWhitelist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
let invites: Invite[] = await Promise.all( savedMessage.channel_id,
inviteCodes.map(code => this.bot.getInvite(code).catch(() => null)) "invite_guild_whitelist"
); );
const inviteGuildBlacklist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"invite_guild_blacklist"
);
const inviteCodeWhitelist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"invite_code_whitelist"
);
const inviteCodeBlacklist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"invite_code_blacklist"
);
const inviteCodes = getInviteCodesInString(savedMessage.data.content);
let invites: Invite[] = await Promise.all(inviteCodes.map(code => this.bot.getInvite(code).catch(() => null)));
invites = invites.filter(v => !!v); invites = invites.filter(v => !!v);
for (const invite of invites) { for (const invite of invites) {
if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) { if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) {
this.censorMessage( this.censorMessage(
msg, savedMessage,
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist` `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist`
); );
return; return;
@ -105,80 +147,94 @@ export class CensorPlugin extends Plugin {
if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) { if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) {
this.censorMessage( this.censorMessage(
msg, savedMessage,
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist` `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist`
); );
return; return;
} }
if (inviteCodeWhitelist && !inviteCodeWhitelist.includes(invite.code)) { if (inviteCodeWhitelist && !inviteCodeWhitelist.includes(invite.code)) {
this.censorMessage(msg, `invite code (\`${invite.code}\`) not found in whitelist`); this.censorMessage(savedMessage, `invite code (\`${invite.code}\`) not found in whitelist`);
return; return;
} }
if (inviteCodeBlacklist && inviteCodeBlacklist.includes(invite.code)) { if (inviteCodeBlacklist && inviteCodeBlacklist.includes(invite.code)) {
this.censorMessage(msg, `invite code (\`${invite.code}\`) found in blacklist`); this.censorMessage(savedMessage, `invite code (\`${invite.code}\`) found in blacklist`);
return; return;
} }
} }
} }
// Filter domains // Filter domains
if (this.configValueForMsg(msg, "filter_domains")) { const filterDomains = this.configValueForMemberIdAndChannelId(
const domainWhitelist: string[] = this.configValueForMsg(msg, "domain_whitelist"); savedMessage.user_id,
const domainBlacklist: string[] = this.configValueForMsg(msg, "domain_blacklist"); savedMessage.channel_id,
"filter_domains"
);
if (filterDomains) {
const domainWhitelist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"domain_whitelist"
);
const domainBlacklist: string[] = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"domain_blacklist"
);
const urls = getUrlsInString(msg.content); const urls = getUrlsInString(savedMessage.data.content);
for (const thisUrl of urls) { for (const thisUrl of urls) {
if (domainWhitelist && !domainWhitelist.includes(thisUrl.hostname)) { if (domainWhitelist && !domainWhitelist.includes(thisUrl.hostname)) {
this.censorMessage(msg, `domain (\`${thisUrl.hostname}\`) not found in whitelist`); this.censorMessage(savedMessage, `domain (\`${thisUrl.hostname}\`) not found in whitelist`);
return; return;
} }
if (domainBlacklist && domainBlacklist.includes(thisUrl.hostname)) { if (domainBlacklist && domainBlacklist.includes(thisUrl.hostname)) {
this.censorMessage(msg, `domain (\`${thisUrl.hostname}\`) found in blacklist`); this.censorMessage(savedMessage, `domain (\`${thisUrl.hostname}\`) found in blacklist`);
return; return;
} }
} }
} }
// Filter tokens // Filter tokens
const blockedTokens = this.configValueForMsg(msg, "blocked_tokens") || []; const blockedTokens =
this.configValueForMemberIdAndChannelId(savedMessage.user_id, savedMessage.channel_id, "blocked_tokens") || [];
for (const token of blockedTokens) { for (const token of blockedTokens) {
if (msg.content.toLowerCase().includes(token.toLowerCase())) { if (savedMessage.data.content.toLowerCase().includes(token.toLowerCase())) {
this.censorMessage(msg, `blocked token (\`${token}\`) found`); this.censorMessage(savedMessage, `blocked token (\`${token}\`) found`);
return; return;
} }
} }
// Filter words // Filter words
const blockedWords = this.configValueForMsg(msg, "blocked_words") || []; const blockedWords =
this.configValueForMemberIdAndChannelId(savedMessage.user_id, savedMessage.channel_id, "blocked_words") || [];
for (const word of blockedWords) { for (const word of blockedWords) {
const regex = new RegExp(`\\b${escapeStringRegexp(word)}\\b`, "i"); const regex = new RegExp(`\\b${escapeStringRegexp(word)}\\b`, "i");
if (regex.test(msg.content)) { if (regex.test(savedMessage.data.content)) {
this.censorMessage(msg, `blocked word (\`${word}\`) found`); this.censorMessage(savedMessage, `blocked word (\`${word}\`) found`);
return; return;
} }
} }
// Filter regex // Filter regex
const blockedRegex = this.configValueForMsg(msg, "blocked_regex") || []; const blockedRegex =
this.configValueForMemberIdAndChannelId(savedMessage.user_id, savedMessage.channel_id, "blocked_regex") || [];
for (const regexStr of blockedRegex) { for (const regexStr of blockedRegex) {
const regex = new RegExp(regexStr); const regex = new RegExp(regexStr);
if (regex.test(msg.content)) { if (regex.test(savedMessage.data.content)) {
this.censorMessage(msg, `blocked regex (\`${regexStr}\`) found`); this.censorMessage(savedMessage, `blocked regex (\`${regexStr}\`) found`);
return; return;
} }
} }
} }
@d.event("messageCreate") async onMessageCreate(savedMessage: SavedMessage) {
async onMessageCreate(msg: Message) { this.applyFiltersToMsg(savedMessage);
this.applyFiltersToMsg(msg);
} }
@d.event("messageUpdate") async onMessageUpdate(savedMessage: SavedMessage) {
async onMessageUpdate(msg: Message) { this.applyFiltersToMsg(savedMessage);
this.applyFiltersToMsg(msg);
} }
} }

View file

@ -15,6 +15,8 @@ import { ModActionsPlugin } from "./ModActions";
import { CaseTypes } from "../data/CaseTypes"; import { CaseTypes } from "../data/CaseTypes";
import { GuildArchives } from "../data/GuildArchives"; import { GuildArchives } from "../data/GuildArchives";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { SavedMessage } from "../data/entities/SavedMessage";
import { GuildSavedMessages } from "../data/GuildSavedMessages";
enum RecentActionType { enum RecentActionType {
Message = 1, Message = 1,
@ -30,7 +32,7 @@ interface IRecentAction {
type: RecentActionType; type: RecentActionType;
userId: string; userId: string;
channelId: string; channelId: string;
msg: Message; savedMessage: SavedMessage;
timestamp: number; timestamp: number;
count: number; count: number;
} }
@ -43,7 +45,7 @@ const ARCHIVE_HEADER_FORMAT = trimLines(`
Channel: #{channel.name} ({channel.id}) Channel: #{channel.name} ({channel.id})
User: {user.username}#{user.discriminator} ({user.id}) User: {user.username}#{user.discriminator} ({user.id})
`); `);
const ARCHIVE_MESSAGE_FORMAT = "[MSG ID {message.id}] [{timestamp}] {user.username}: {message.content}{attachments}"; const ARCHIVE_MESSAGE_FORMAT = "[MSG ID {id}] [{timestamp}] {user.username}: {content}{attachments}";
const ARCHIVE_FOOTER_FORMAT = trimLines(` const ARCHIVE_FOOTER_FORMAT = trimLines(`
Log file generated on {timestamp} Log file generated on {timestamp}
Expires at {expires} Expires at {expires}
@ -52,6 +54,9 @@ const ARCHIVE_FOOTER_FORMAT = trimLines(`
export class SpamPlugin extends Plugin { export class SpamPlugin extends Plugin {
protected logs: GuildLogs; protected logs: GuildLogs;
protected archives: GuildArchives; protected archives: GuildArchives;
protected savedMessages: GuildSavedMessages;
private onMessageCreateFn;
// Handle spam detection with a queue so we don't have overlapping detections on the same user // Handle spam detection with a queue so we don't have overlapping detections on the same user
protected spamDetectionQueue: Promise<void>; protected spamDetectionQueue: Promise<void>;
@ -97,27 +102,32 @@ export class SpamPlugin extends Plugin {
onLoad() { onLoad() {
this.logs = new GuildLogs(this.guildId); this.logs = new GuildLogs(this.guildId);
this.archives = GuildArchives.getInstance(this.guildId); this.archives = GuildArchives.getInstance(this.guildId);
this.savedMessages = GuildSavedMessages.getInstance(this.guildId);
this.recentActions = []; this.recentActions = [];
this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60); this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60);
this.lastHandledMsgIds = new Map(); this.lastHandledMsgIds = new Map();
this.spamDetectionQueue = Promise.resolve(); this.spamDetectionQueue = Promise.resolve();
this.onMessageCreateFn = this.onMessageCreate.bind(this);
this.savedMessages.events.on("create", this.onMessageCreateFn);
} }
onUnload() { onUnload() {
clearInterval(this.expiryInterval); clearInterval(this.expiryInterval);
this.savedMessages.events.off("create", this.onMessageCreateFn);
} }
addRecentAction( addRecentAction(
type: RecentActionType, type: RecentActionType,
userId: string, userId: string,
channelId: string, channelId: string,
msg: Message, savedMessage: SavedMessage,
timestamp: number, timestamp: number,
count = 1 count = 1
) { ) {
this.recentActions.push({ type, userId, channelId, msg, timestamp, count }); this.recentActions.push({ type, userId, channelId, savedMessage, timestamp, count });
} }
getRecentActions(type: RecentActionType, userId: string, channelId: string, since: number) { getRecentActions(type: RecentActionType, userId: string, channelId: string, since: number) {
@ -152,7 +162,7 @@ export class SpamPlugin extends Plugin {
this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp); this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp);
} }
async saveSpamArchives(messages: Message[], channel: Channel, user: User) { async saveSpamArchives(savedMessages: SavedMessage[], channel: Channel, user: User) {
const expiresAt = moment().add(ARCHIVE_EXPIRY_DAYS, "days"); const expiresAt = moment().add(ARCHIVE_EXPIRY_DAYS, "days");
const headerStr = formatTemplateString(ARCHIVE_HEADER_FORMAT, { const headerStr = formatTemplateString(ARCHIVE_HEADER_FORMAT, {
@ -160,10 +170,11 @@ export class SpamPlugin extends Plugin {
channel, channel,
user user
}); });
const msgLines = messages.map(msg => { const msgLines = savedMessages.map(msg => {
return formatTemplateString(ARCHIVE_MESSAGE_FORMAT, { return formatTemplateString(ARCHIVE_MESSAGE_FORMAT, {
message: msg, id: msg.id,
timestamp: moment(msg.timestamp, "x").format("HH:mm:ss"), timestamp: moment(msg.posted_at).format("HH:mm:ss"),
content: msg.data.content,
user user
}); });
}); });
@ -180,7 +191,7 @@ export class SpamPlugin extends Plugin {
} }
async logAndDetectSpam( async logAndDetectSpam(
msg: Message, savedMessage: SavedMessage,
type: RecentActionType, type: RecentActionType,
spamConfig: any, spamConfig: any,
actionCount: number, actionCount: number,
@ -189,26 +200,34 @@ export class SpamPlugin extends Plugin {
if (actionCount === 0) return; if (actionCount === 0) return;
// Make sure we're not handling some messages twice // Make sure we're not handling some messages twice
if (this.lastHandledMsgIds.has(msg.author.id)) { if (this.lastHandledMsgIds.has(savedMessage.user_id)) {
const channelMap = this.lastHandledMsgIds.get(msg.author.id); const channelMap = this.lastHandledMsgIds.get(savedMessage.user_id);
if (channelMap.has(msg.channel.id)) { if (channelMap.has(savedMessage.channel_id)) {
const lastHandledMsgId = channelMap.get(msg.channel.id); const lastHandledMsgId = channelMap.get(savedMessage.channel_id);
if (lastHandledMsgId >= msg.id) return; if (lastHandledMsgId >= savedMessage.id) return;
} }
} }
this.spamDetectionQueue = this.spamDetectionQueue.then( this.spamDetectionQueue = this.spamDetectionQueue.then(
async () => { async () => {
const timestamp = moment(savedMessage.posted_at).valueOf();
const member = this.guild.members.get(savedMessage.user_id);
// Log this action... // Log this action...
this.addRecentAction(type, msg.author.id, msg.channel.id, msg, msg.timestamp, actionCount); this.addRecentAction(type, savedMessage.user_id, savedMessage.channel_id, savedMessage, timestamp, actionCount);
// ...and then check if it trips the spam filters // ...and then check if it trips the spam filters
const since = msg.timestamp - 1000 * spamConfig.interval; const since = timestamp - 1000 * spamConfig.interval;
const recentActionsCount = this.getRecentActionCount(type, msg.author.id, msg.channel.id, since); const recentActionsCount = this.getRecentActionCount(
type,
savedMessage.user_id,
savedMessage.channel_id,
since
);
// If the user tripped the spam filter... // If the user tripped the spam filter...
if (recentActionsCount > spamConfig.count) { if (recentActionsCount > spamConfig.count) {
const recentActions = this.getRecentActions(type, msg.author.id, msg.channel.id, since); const recentActions = this.getRecentActions(type, savedMessage.user_id, savedMessage.channel_id, since);
let modActionsPlugin; let modActionsPlugin;
// Start by muting them, if enabled // Start by muting them, if enabled
@ -221,43 +240,52 @@ export class SpamPlugin extends Plugin {
const muteTime = spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000; const muteTime = spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000;
this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, msg.member.id); if (member) {
modActionsPlugin.muteMember(msg.member, muteTime, "Automatic spam detection"); this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, savedMessage.user_id);
modActionsPlugin.muteMember(member, muteTime, "Automatic spam detection");
}
} }
// Get the offending message IDs // Get the offending message IDs
// We also get the IDs of any messages after the last offending message, to account for lag before detection // We also get the IDs of any messages after the last offending message, to account for lag before detection
const messages = recentActions.map(a => a.msg); const savedMessages = recentActions.map(a => a.savedMessage);
const msgIds = messages.map(m => m.id); const msgIds = savedMessages.map(m => m.id);
const lastDetectedMsgId = msgIds[msgIds.length - 1]; const lastDetectedMsgId = msgIds[msgIds.length - 1];
const additionalMessages = await this.bot.getMessages(msg.channel.id, 100, null, lastDetectedMsgId);
const additionalMessages = await this.savedMessages.getUserMessagesByChannelAfterId(
savedMessage.user_id,
savedMessage.channel_id,
lastDetectedMsgId
);
additionalMessages.forEach(m => msgIds.push(m.id)); additionalMessages.forEach(m => msgIds.push(m.id));
// Then, if enabled, remove the spam messages // Then, if enabled, remove the spam messages
if (spamConfig.clean !== false) { if (spamConfig.clean !== false) {
msgIds.forEach(id => this.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); msgIds.forEach(id => this.logs.ignoreLog(LogType.MESSAGE_DELETE, id));
this.bot.deleteMessages(msg.channel.id, msgIds); this.bot.deleteMessages(savedMessage.channel_id, msgIds);
} }
// Store the ID of the last handled message // Store the ID of the last handled message
const uniqueMessages = Array.from(new Set([...messages, ...additionalMessages])); const uniqueMessages = Array.from(new Set([...savedMessages, ...additionalMessages]));
uniqueMessages.sort((a, b) => (a.id > b.id ? 1 : -1)); uniqueMessages.sort((a, b) => (a.id > b.id ? 1 : -1));
const lastHandledMsgId = uniqueMessages.reduce((last: string, m: Message): string => { const lastHandledMsgId = uniqueMessages.reduce((last: string, m: SavedMessage): string => {
return !last || m.id > last ? m.id : last; return !last || m.id > last ? m.id : last;
}, null); }, null);
if (!this.lastHandledMsgIds.has(msg.author.id)) { if (!this.lastHandledMsgIds.has(savedMessage.user_id)) {
this.lastHandledMsgIds.set(msg.author.id, new Map()); this.lastHandledMsgIds.set(savedMessage.user_id, new Map());
} }
const channelMap = this.lastHandledMsgIds.get(msg.author.id); const channelMap = this.lastHandledMsgIds.get(savedMessage.user_id);
channelMap.set(msg.channel.id, lastHandledMsgId); channelMap.set(savedMessage.channel_id, lastHandledMsgId);
// Clear the handled actions from recentActions // Clear the handled actions from recentActions
this.clearRecentUserActions(type, msg.author.id, msg.channel.id); this.clearRecentUserActions(type, savedMessage.user_id, savedMessage.channel_id);
// Generate a log from the detected messages // Generate a log from the detected messages
const logUrl = await this.saveSpamArchives(uniqueMessages, msg.channel, msg.author); const channel = this.guild.channels.get(savedMessage.channel_id);
const user = this.bot.users.get(savedMessage.user_id);
const logUrl = await this.saveSpamArchives(uniqueMessages, channel, user);
// Create a case and log the actions taken above // Create a case and log the actions taken above
const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note; const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note;
@ -267,8 +295,8 @@ export class SpamPlugin extends Plugin {
`); `);
this.logs.log(LogType.SPAM_DETECTED, { this.logs.log(LogType.SPAM_DETECTED, {
member: stripObjectToScalars(msg.member, ["user"]), member: stripObjectToScalars(member, ["user"]),
channel: stripObjectToScalars(msg.channel), channel: stripObjectToScalars(channel),
description, description,
limit: spamConfig.count, limit: spamConfig.count,
interval: spamConfig.interval, interval: spamConfig.interval,
@ -276,7 +304,7 @@ export class SpamPlugin extends Plugin {
}); });
const caseId = await modActionsPlugin.createCase( const caseId = await modActionsPlugin.createCase(
msg.member.id, savedMessage.user_id,
this.bot.user.id, this.bot.user.id,
caseType, caseType,
null, null,
@ -285,8 +313,8 @@ export class SpamPlugin extends Plugin {
); );
// For mutes, also set the mute's case id (for !mutes) // For mutes, also set the mute's case id (for !mutes)
if (spamConfig.mute) { if (spamConfig.mute && member) {
await modActionsPlugin.mutes.setCaseId(msg.member.id, caseId); await modActionsPlugin.mutes.setCaseId(savedMessage.user_id, caseId);
} }
} }
}, },
@ -298,55 +326,84 @@ export class SpamPlugin extends Plugin {
} }
// For interoperability with the Censor plugin // For interoperability with the Censor plugin
async logCensor(msg: Message) { async logCensor(savedMessage: SavedMessage) {
const spamConfig = this.configValueForMsg(msg, "max_censor"); const spamConfig = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"max_censor"
);
if (spamConfig) { if (spamConfig) {
this.logAndDetectSpam(msg, RecentActionType.Censor, spamConfig, 1, "too many censored messages"); this.logAndDetectSpam(savedMessage, RecentActionType.Censor, spamConfig, 1, "too many censored messages");
} }
} }
@d.event("messageCreate") async onMessageCreate(savedMessage: SavedMessage) {
async onMessageCreate(msg: Message) { if (savedMessage.is_bot) return;
if (msg.author.bot) return;
const maxMessages = this.configValueForMsg(msg, "max_messages"); const maxMessages = this.configValueForMemberIdAndChannelId(
savedMessage.user_id,
savedMessage.channel_id,
"max_messages"
);
if (maxMessages) { if (maxMessages) {
this.logAndDetectSpam(msg, RecentActionType.Message, maxMessages, 1, "too many messages"); this.logAndDetectSpam(savedMessage, RecentActionType.Message, maxMessages, 1, "too many messages");
} }
const maxMentions = this.configValueForMsg(msg, "max_mentions"); const maxMentions = this.configValueForMemberIdAndChannelId(
const mentions = msg.content ? [...getUserMentions(msg.content), ...getRoleMentions(msg.content)] : []; savedMessage.user_id,
savedMessage.channel_id,
"max_mentions"
);
const mentions = savedMessage.data.content
? [...getUserMentions(savedMessage.data.content), ...getRoleMentions(savedMessage.data.content)]
: [];
if (maxMentions && mentions.length) { if (maxMentions && mentions.length) {
this.logAndDetectSpam(msg, RecentActionType.Mention, maxMentions, mentions.length, "too many mentions"); this.logAndDetectSpam(savedMessage, RecentActionType.Mention, maxMentions, mentions.length, "too many mentions");
} }
const maxLinks = this.configValueForMsg(msg, "max_links"); const maxLinks = this.configValueForMemberIdAndChannelId(
if (maxLinks && msg.content) { savedMessage.user_id,
const links = getUrlsInString(msg.content); savedMessage.channel_id,
this.logAndDetectSpam(msg, RecentActionType.Link, maxLinks, links.length, "too many links"); "max_links"
);
if (maxLinks && savedMessage.data.content) {
const links = getUrlsInString(savedMessage.data.content);
this.logAndDetectSpam(savedMessage, RecentActionType.Link, maxLinks, links.length, "too many links");
} }
const maxAttachments = this.configValueForMsg(msg, "max_attachments"); const maxAttachments = this.configValueForMemberIdAndChannelId(
if (maxAttachments && msg.attachments.length) { savedMessage.user_id,
savedMessage.channel_id,
"max_attachments"
);
if (maxAttachments && savedMessage.data.attachments) {
this.logAndDetectSpam( this.logAndDetectSpam(
msg, savedMessage,
RecentActionType.Attachment, RecentActionType.Attachment,
maxAttachments, maxAttachments,
msg.attachments.length, savedMessage.data.attachments.length,
"too many attachments" "too many attachments"
); );
} }
const maxEmoji = this.configValueForMsg(msg, "max_emoji"); const maxEmoji = this.configValueForMemberIdAndChannelId(
if (maxEmoji && msg.content) { savedMessage.user_id,
const emojiCount = getEmojiInString(msg.content).length; savedMessage.channel_id,
this.logAndDetectSpam(msg, RecentActionType.Emoji, maxEmoji, emojiCount, "too many emoji"); "max_emoji"
);
if (maxEmoji && savedMessage.data.content) {
const emojiCount = getEmojiInString(savedMessage.data.content).length;
this.logAndDetectSpam(savedMessage, RecentActionType.Emoji, maxEmoji, emojiCount, "too many emoji");
} }
const maxNewlines = this.configValueForMsg(msg, "max_newlines"); const maxNewlines = this.configValueForMemberIdAndChannelId(
if (maxNewlines && msg.content) { savedMessage.user_id,
const newlineCount = (msg.content.match(/\n/g) || []).length; savedMessage.channel_id,
this.logAndDetectSpam(msg, RecentActionType.Newline, maxNewlines, newlineCount, "too many newlines"); "max_newlines"
);
if (maxNewlines && savedMessage.data.content) {
const newlineCount = (savedMessage.data.content.match(/\n/g) || []).length;
this.logAndDetectSpam(savedMessage, RecentActionType.Newline, maxNewlines, newlineCount, "too many newlines");
} }
// TODO: Max duplicates // TODO: Max duplicates