diff --git a/src/QueuedEventEmitter.ts b/src/QueuedEventEmitter.ts index f854c59a..e5f1d80b 100644 --- a/src/QueuedEventEmitter.ts +++ b/src/QueuedEventEmitter.ts @@ -32,7 +32,7 @@ export class QueuedEventEmitter { const listeners = [...(this.listeners.get(eventName) || []), ...(this.listeners.get("*") || [])]; listeners.forEach(listener => { - this.queue.add(listener.bind(null, args)); + this.queue.add(listener.bind(null, ...args)); }); } } diff --git a/src/data/GuildSavedMessages.ts b/src/data/GuildSavedMessages.ts index c84b0985..09b01cc9 100644 --- a/src/data/GuildSavedMessages.ts +++ b/src/data/GuildSavedMessages.ts @@ -73,6 +73,16 @@ export class GuildSavedMessages extends BaseRepository { .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) { try { 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) { diff --git a/src/plugins/Censor.ts b/src/plugins/Censor.ts index d7c2302c..bb8ecfe9 100644 --- a/src/plugins/Censor.ts +++ b/src/plugins/Censor.ts @@ -5,9 +5,15 @@ import { GuildLogs } from "../data/GuildLogs"; import { LogType } from "../data/LogType"; import { getInviteCodesInString, getUrlsInString, stripObjectToScalars } from "../utils"; import { ZalgoRegex } from "../data/Zalgo"; +import { GuildSavedMessages } from "../data/GuildSavedMessages"; +import { SavedMessage } from "../data/entities/SavedMessage"; export class CensorPlugin extends Plugin { protected serverLogs: GuildLogs; + protected savedMessages: GuildSavedMessages; + + private onMessageCreateFn; + private onMessageUpdateFn; getDefaultOptions() { return { @@ -46,58 +52,94 @@ export class CensorPlugin extends Plugin { onLoad() { 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) { - this.serverLogs.ignoreLog(LogType.MESSAGE_DELETE, msg.id); + onUnload() { + 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 { - await msg.delete("Censored"); + await this.bot.deleteMessage(savedMessage.channel_id, savedMessage.id, "Censored"); } catch (e) { return; } + const member = this.guild.members.get(savedMessage.user_id); + const channel = this.guild.channels.get(savedMessage.channel_id); + this.serverLogs.log(LogType.CENSOR, { - member: stripObjectToScalars(msg.member, ["user"]), - channel: stripObjectToScalars(msg.channel), + member: stripObjectToScalars(member, ["user"]), + channel: stripObjectToScalars(channel), reason, - messageText: msg.cleanContent + messageText: savedMessage.data.content }); } - async applyFiltersToMsg(msg: Message) { - if (!msg.author || msg.author.bot) return; - if (msg.type !== 0) return; - if (!msg.content) return; + async applyFiltersToMsg(savedMessage: SavedMessage) { + if (!savedMessage.data.content) return; // Filter zalgo - if (this.configValueForMsg(msg, "filter_zalgo")) { - const result = ZalgoRegex.exec(msg.content); + const filterZalgo = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "filter_zalgo" + ); + if (filterZalgo) { + const result = ZalgoRegex.exec(savedMessage.data.content); if (result) { - this.censorMessage(msg, `zalgo detected`); + this.censorMessage(savedMessage, "zalgo detected"); return; } } // Filter invites - if (this.configValueForMsg(msg, "filter_invites")) { - const inviteGuildWhitelist: string[] = this.configValueForMsg(msg, "invite_guild_whitelist"); - const inviteGuildBlacklist: string[] = this.configValueForMsg(msg, "invite_guild_blacklist"); - const inviteCodeWhitelist: string[] = this.configValueForMsg(msg, "invite_code_whitelist"); - const inviteCodeBlacklist: string[] = this.configValueForMsg(msg, "invite_code_blacklist"); - - const inviteCodes = getInviteCodesInString(msg.content); - - let invites: Invite[] = await Promise.all( - inviteCodes.map(code => this.bot.getInvite(code).catch(() => null)) + const filterInvites = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "filter_invites" + ); + if (filterInvites) { + const inviteGuildWhitelist: string[] = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "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); for (const invite of invites) { if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) { this.censorMessage( - msg, + savedMessage, `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist` ); return; @@ -105,80 +147,94 @@ export class CensorPlugin extends Plugin { if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) { this.censorMessage( - msg, + savedMessage, `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist` ); return; } 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; } 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; } } } // Filter domains - if (this.configValueForMsg(msg, "filter_domains")) { - const domainWhitelist: string[] = this.configValueForMsg(msg, "domain_whitelist"); - const domainBlacklist: string[] = this.configValueForMsg(msg, "domain_blacklist"); + const filterDomains = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + 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) { 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; } 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; } } } // 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) { - if (msg.content.toLowerCase().includes(token.toLowerCase())) { - this.censorMessage(msg, `blocked token (\`${token}\`) found`); + if (savedMessage.data.content.toLowerCase().includes(token.toLowerCase())) { + this.censorMessage(savedMessage, `blocked token (\`${token}\`) found`); return; } } // 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) { const regex = new RegExp(`\\b${escapeStringRegexp(word)}\\b`, "i"); - if (regex.test(msg.content)) { - this.censorMessage(msg, `blocked word (\`${word}\`) found`); + if (regex.test(savedMessage.data.content)) { + this.censorMessage(savedMessage, `blocked word (\`${word}\`) found`); return; } } // 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) { const regex = new RegExp(regexStr); - if (regex.test(msg.content)) { - this.censorMessage(msg, `blocked regex (\`${regexStr}\`) found`); + if (regex.test(savedMessage.data.content)) { + this.censorMessage(savedMessage, `blocked regex (\`${regexStr}\`) found`); return; } } } - @d.event("messageCreate") - async onMessageCreate(msg: Message) { - this.applyFiltersToMsg(msg); + async onMessageCreate(savedMessage: SavedMessage) { + this.applyFiltersToMsg(savedMessage); } - @d.event("messageUpdate") - async onMessageUpdate(msg: Message) { - this.applyFiltersToMsg(msg); + async onMessageUpdate(savedMessage: SavedMessage) { + this.applyFiltersToMsg(savedMessage); } } diff --git a/src/plugins/Spam.ts b/src/plugins/Spam.ts index dcdee4fe..63cb3ab6 100644 --- a/src/plugins/Spam.ts +++ b/src/plugins/Spam.ts @@ -15,6 +15,8 @@ import { ModActionsPlugin } from "./ModActions"; import { CaseTypes } from "../data/CaseTypes"; import { GuildArchives } from "../data/GuildArchives"; import moment from "moment-timezone"; +import { SavedMessage } from "../data/entities/SavedMessage"; +import { GuildSavedMessages } from "../data/GuildSavedMessages"; enum RecentActionType { Message = 1, @@ -30,7 +32,7 @@ interface IRecentAction { type: RecentActionType; userId: string; channelId: string; - msg: Message; + savedMessage: SavedMessage; timestamp: number; count: number; } @@ -43,7 +45,7 @@ const ARCHIVE_HEADER_FORMAT = trimLines(` Channel: #{channel.name} ({channel.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(` Log file generated on {timestamp} Expires at {expires} @@ -52,6 +54,9 @@ const ARCHIVE_FOOTER_FORMAT = trimLines(` export class SpamPlugin extends Plugin { protected logs: GuildLogs; 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 protected spamDetectionQueue: Promise; @@ -97,27 +102,32 @@ export class SpamPlugin extends Plugin { onLoad() { this.logs = new GuildLogs(this.guildId); this.archives = GuildArchives.getInstance(this.guildId); + this.savedMessages = GuildSavedMessages.getInstance(this.guildId); this.recentActions = []; this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60); this.lastHandledMsgIds = new Map(); this.spamDetectionQueue = Promise.resolve(); + + this.onMessageCreateFn = this.onMessageCreate.bind(this); + this.savedMessages.events.on("create", this.onMessageCreateFn); } onUnload() { clearInterval(this.expiryInterval); + this.savedMessages.events.off("create", this.onMessageCreateFn); } addRecentAction( type: RecentActionType, userId: string, channelId: string, - msg: Message, + savedMessage: SavedMessage, timestamp: number, 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) { @@ -152,7 +162,7 @@ export class SpamPlugin extends Plugin { 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 headerStr = formatTemplateString(ARCHIVE_HEADER_FORMAT, { @@ -160,10 +170,11 @@ export class SpamPlugin extends Plugin { channel, user }); - const msgLines = messages.map(msg => { + const msgLines = savedMessages.map(msg => { return formatTemplateString(ARCHIVE_MESSAGE_FORMAT, { - message: msg, - timestamp: moment(msg.timestamp, "x").format("HH:mm:ss"), + id: msg.id, + timestamp: moment(msg.posted_at).format("HH:mm:ss"), + content: msg.data.content, user }); }); @@ -180,7 +191,7 @@ export class SpamPlugin extends Plugin { } async logAndDetectSpam( - msg: Message, + savedMessage: SavedMessage, type: RecentActionType, spamConfig: any, actionCount: number, @@ -189,26 +200,34 @@ export class SpamPlugin extends Plugin { if (actionCount === 0) return; // Make sure we're not handling some messages twice - if (this.lastHandledMsgIds.has(msg.author.id)) { - const channelMap = this.lastHandledMsgIds.get(msg.author.id); - if (channelMap.has(msg.channel.id)) { - const lastHandledMsgId = channelMap.get(msg.channel.id); - if (lastHandledMsgId >= msg.id) return; + if (this.lastHandledMsgIds.has(savedMessage.user_id)) { + const channelMap = this.lastHandledMsgIds.get(savedMessage.user_id); + if (channelMap.has(savedMessage.channel_id)) { + const lastHandledMsgId = channelMap.get(savedMessage.channel_id); + if (lastHandledMsgId >= savedMessage.id) return; } } this.spamDetectionQueue = this.spamDetectionQueue.then( async () => { + const timestamp = moment(savedMessage.posted_at).valueOf(); + const member = this.guild.members.get(savedMessage.user_id); + // 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 - const since = msg.timestamp - 1000 * spamConfig.interval; - const recentActionsCount = this.getRecentActionCount(type, msg.author.id, msg.channel.id, since); + const since = timestamp - 1000 * spamConfig.interval; + const recentActionsCount = this.getRecentActionCount( + type, + savedMessage.user_id, + savedMessage.channel_id, + since + ); // If the user tripped the spam filter... 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; // 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; - this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, msg.member.id); - modActionsPlugin.muteMember(msg.member, muteTime, "Automatic spam detection"); + if (member) { + this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, savedMessage.user_id); + modActionsPlugin.muteMember(member, muteTime, "Automatic spam detection"); + } } // Get the offending message IDs // 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 msgIds = messages.map(m => m.id); + const savedMessages = recentActions.map(a => a.savedMessage); + const msgIds = savedMessages.map(m => m.id); 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)); // Then, if enabled, remove the spam messages if (spamConfig.clean !== false) { 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 - 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)); - 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; }, null); - if (!this.lastHandledMsgIds.has(msg.author.id)) { - this.lastHandledMsgIds.set(msg.author.id, new Map()); + if (!this.lastHandledMsgIds.has(savedMessage.user_id)) { + this.lastHandledMsgIds.set(savedMessage.user_id, new Map()); } - const channelMap = this.lastHandledMsgIds.get(msg.author.id); - channelMap.set(msg.channel.id, lastHandledMsgId); + const channelMap = this.lastHandledMsgIds.get(savedMessage.user_id); + channelMap.set(savedMessage.channel_id, lastHandledMsgId); // 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 - 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 const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note; @@ -267,8 +295,8 @@ export class SpamPlugin extends Plugin { `); this.logs.log(LogType.SPAM_DETECTED, { - member: stripObjectToScalars(msg.member, ["user"]), - channel: stripObjectToScalars(msg.channel), + member: stripObjectToScalars(member, ["user"]), + channel: stripObjectToScalars(channel), description, limit: spamConfig.count, interval: spamConfig.interval, @@ -276,7 +304,7 @@ export class SpamPlugin extends Plugin { }); const caseId = await modActionsPlugin.createCase( - msg.member.id, + savedMessage.user_id, this.bot.user.id, caseType, null, @@ -285,8 +313,8 @@ export class SpamPlugin extends Plugin { ); // For mutes, also set the mute's case id (for !mutes) - if (spamConfig.mute) { - await modActionsPlugin.mutes.setCaseId(msg.member.id, caseId); + if (spamConfig.mute && member) { + await modActionsPlugin.mutes.setCaseId(savedMessage.user_id, caseId); } } }, @@ -298,55 +326,84 @@ export class SpamPlugin extends Plugin { } // For interoperability with the Censor plugin - async logCensor(msg: Message) { - const spamConfig = this.configValueForMsg(msg, "max_censor"); + async logCensor(savedMessage: SavedMessage) { + const spamConfig = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "max_censor" + ); 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(msg: Message) { - if (msg.author.bot) return; + async onMessageCreate(savedMessage: SavedMessage) { + if (savedMessage.is_bot) return; - const maxMessages = this.configValueForMsg(msg, "max_messages"); + const maxMessages = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "max_messages" + ); 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 mentions = msg.content ? [...getUserMentions(msg.content), ...getRoleMentions(msg.content)] : []; + const maxMentions = this.configValueForMemberIdAndChannelId( + 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) { - 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"); - if (maxLinks && msg.content) { - const links = getUrlsInString(msg.content); - this.logAndDetectSpam(msg, RecentActionType.Link, maxLinks, links.length, "too many links"); + const maxLinks = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "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"); - if (maxAttachments && msg.attachments.length) { + const maxAttachments = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "max_attachments" + ); + if (maxAttachments && savedMessage.data.attachments) { this.logAndDetectSpam( - msg, + savedMessage, RecentActionType.Attachment, maxAttachments, - msg.attachments.length, + savedMessage.data.attachments.length, "too many attachments" ); } - const maxEmoji = this.configValueForMsg(msg, "max_emoji"); - if (maxEmoji && msg.content) { - const emojiCount = getEmojiInString(msg.content).length; - this.logAndDetectSpam(msg, RecentActionType.Emoji, maxEmoji, emojiCount, "too many emoji"); + const maxEmoji = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "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"); - if (maxNewlines && msg.content) { - const newlineCount = (msg.content.match(/\n/g) || []).length; - this.logAndDetectSpam(msg, RecentActionType.Newline, maxNewlines, newlineCount, "too many newlines"); + const maxNewlines = this.configValueForMemberIdAndChannelId( + savedMessage.user_id, + savedMessage.channel_id, + "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