3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-14 21:31:50 +00:00

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("*") || [])];
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();
}
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) {

View file

@ -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);
}
}

View file

@ -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<void>;
@ -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