diff --git a/src/data/DefaultLogMessages.json b/src/data/DefaultLogMessages.json index 67f227d0..209cca22 100644 --- a/src/data/DefaultLogMessages.json +++ b/src/data/DefaultLogMessages.json @@ -26,8 +26,8 @@ "MESSAGE_EDIT": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) message edited in **#{channel.name}**:\nBefore:```{before}```After:```{after}```", "MESSAGE_DELETE": "🗑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) message deleted in **#{channel.name}**:\n```{messageText}```", - "MESSAGE_DELETE_BULK": "🗑 **{count}** messages deleted in **#{channel.name}**", - "MESSAGE_DELETE_BARE": "🗑 Message (`{messageId}`) deleted in **#{channel.name}** (no more info available due to bot restart)", + "MESSAGE_DELETE_BULK": "🗑 **{count}** messages deleted in **#{channel.name}** ({archiveUrl})", + "MESSAGE_DELETE_BARE": "🗑 Message (`{messageId}`) deleted in **#{channel.name}** (no more info available)", "VOICE_CHANNEL_JOIN": "🎙 🔵 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined **{channel.name}**", "VOICE_CHANNEL_MOVE": "🎙 ↔ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) moved from **{oldChannel.name}** to **{newChannel.name}**", @@ -35,9 +35,9 @@ "COMMAND": "🤖 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) used command in **#{channel.name}**:\n`{command}`", - "SPAM_DETECTED": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) spam detected in **#{channel.name}**: {description} (more than {limit} in {interval}s)\n{logUrl}", + "SPAM_DETECTED": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) spam detected in **#{channel.name}**: {description} (more than {limit} in {interval}s)\n{archiveUrl}", "CENSOR": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) censored message in **#{channel.name}** (`{channel.id}`) {reason}:\n```{messageText}```", - "CLEAN": "🚿 **{mod.username}#{mod.discriminator}** (`{mod.id}`) cleaned **{count}** message(s) in **#{channel.name}**", + "CLEAN": "🚿 **{mod.username}#{mod.discriminator}** (`{mod.id}`) cleaned **{count}** message(s) in **#{channel.name}**\n{archiveUrl}", "CASE_CREATE": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) manually created new **{caseType}** case (#{caseNum})", diff --git a/src/data/GuildArchives.ts b/src/data/GuildArchives.ts index 64104dcd..1e58ecc0 100644 --- a/src/data/GuildArchives.ts +++ b/src/data/GuildArchives.ts @@ -3,9 +3,23 @@ import moment from "moment-timezone"; import { ArchiveEntry } from "./entities/ArchiveEntry"; import { getRepository, Repository } from "typeorm"; import { BaseRepository } from "./BaseRepository"; +import { formatTemplateString, trimLines } from "../utils"; +import { SavedMessage } from "./entities/SavedMessage"; +import { Channel, Guild, User } from "eris"; const DEFAULT_EXPIRY_DAYS = 30; +const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(` + Server: {guild.name} ({guild.id}) + Channel: #{channel.name} ({channel.id}) + User: {user.username}#{user.discriminator} ({user.id}) +`); +const MESSAGE_ARCHIVE_MESSAGE_FORMAT = "[MSG ID {id}] [{timestamp}] {user.username}: {content}{attachments}"; +const MESSAGE_ARCHIVE_FOOTER_FORMAT = trimLines(` + Log file generated on {timestamp} + Expires at {expires} +`); + export class GuildArchives extends BaseRepository { protected archives: Repository; @@ -50,4 +64,31 @@ export class GuildArchives extends BaseRepository { return result.identifiers[0].id; } + + createFromSavedMessages( + savedMessages: SavedMessage[], + guild: Guild, + channel: Channel = null, + user: User = null, + expiresAt = null + ) { + if (expiresAt == null) expiresAt = moment().add(DEFAULT_EXPIRY_DAYS, "days"); + + const headerStr = formatTemplateString(MESSAGE_ARCHIVE_HEADER_FORMAT, { guild, channel, user }); + const msgLines = savedMessages.map(msg => { + return formatTemplateString(MESSAGE_ARCHIVE_MESSAGE_FORMAT, { + id: msg.id, + timestamp: moment(msg.posted_at).format("HH:mm:ss"), + content: msg.data.content, + user + }); + }); + const messagesStr = msgLines.join("\n"); + const footerStr = formatTemplateString(MESSAGE_ARCHIVE_FOOTER_FORMAT, { + timestamp: moment().format("YYYY-MM-DD [at] HH:mm:ss (Z)"), + expires: expiresAt.format("YYYY-MM-DD [at] HH:mm:ss (Z)") + }); + + return this.create([headerStr, messagesStr, footerStr].join("\n\n"), expiresAt); + } } diff --git a/src/data/GuildSavedMessages.ts b/src/data/GuildSavedMessages.ts index fe378380..03e92dad 100644 --- a/src/data/GuildSavedMessages.ts +++ b/src/data/GuildSavedMessages.ts @@ -77,16 +77,54 @@ export class GuildSavedMessages extends BaseRepository { .getOne(); } - getUserMessagesByChannelAfterId(userId, channelId, afterId) { + getLatestBotMessagesByChannel(channelId, limit) { 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 }) + .andWhere("channel_id = :channel_id", { channel_id: channelId }) + .andWhere("is_bot = 1") + .orderBy("id", "DESC") + .limit(limit) .getMany(); } + getLatestByChannelBeforeId(channelId, beforeId, limit) { + return this.messages + .createQueryBuilder() + .where("guild_id = :guild_id", { guild_id: this.guildId }) + .andWhere("channel_id = :channel_id", { channel_id: channelId }) + .andWhere("id < :beforeId", { beforeId }) + .orderBy("id", "DESC") + .limit(limit) + .getMany(); + } + + getLatestByChannelAndUser(channelId, userId, limit) { + return this.messages + .createQueryBuilder() + .where("guild_id = :guild_id", { guild_id: this.guildId }) + .andWhere("channel_id = :channel_id", { channel_id: channelId }) + .andWhere("user_id = :user_id", { user_id: userId }) + .orderBy("id", "DESC") + .limit(limit) + .getMany(); + } + + getUserMessagesByChannelAfterId(userId, channelId, afterId, limit = null) { + let query = this.messages + .createQueryBuilder() + .where("guild_id = :guild_id", { guild_id: this.guildId }) + .andWhere("user_id = :user_id", { user_id: userId }) + .andWhere("channel_id = :channel_id", { channel_id: channelId }) + .andWhere("id > :afterId", { afterId }); + + if (limit != null) { + query = query.limit(limit); + } + + return query.getMany(); + } + async create(data) { const isPermanent = this.toBePermanent.has(data.id); if (isPermanent) { @@ -137,6 +175,23 @@ export class GuildSavedMessages extends BaseRepository { this.events.emit("delete", [deleted]); } + async markBulkAsDeleted(ids) { + await this.messages + .createQueryBuilder() + .update() + .set({ + deleted_at: () => "NOW(3)" + }) + .where("guild_id = :guild_id", { guild_id: this.guildId }) + .andWhere("id IN (:ids)", { ids }) + .execute(); + + return this.messages + .createQueryBuilder() + .where("id IN (:ids)", { ids }) + .getMany(); + } + async saveEdit(id, newData: ISavedMessageData) { const oldMessage = await this.messages.findOne(id); const newMessage = { ...oldMessage, data: newData }; diff --git a/src/plugins/LogServer.ts b/src/plugins/LogServer.ts index cc9f3f6a..6751cd73 100644 --- a/src/plugins/LogServer.ts +++ b/src/plugins/LogServer.ts @@ -11,9 +11,6 @@ function notFound(res: ServerResponse) { res.end("Not Found"); } -/** - * A global plugin that allows bot owners to control the bot - */ export class LogServerPlugin extends GlobalPlugin { protected archives: GuildArchives; protected server: http.Server; diff --git a/src/plugins/Logs.ts b/src/plugins/Logs.ts index d0e46203..4938e533 100644 --- a/src/plugins/Logs.ts +++ b/src/plugins/Logs.ts @@ -16,6 +16,7 @@ import isEqual from "lodash.isequal"; import diff from "lodash.difference"; import { GuildSavedMessages } from "../data/GuildSavedMessages"; import { SavedMessage } from "../data/entities/SavedMessage"; +import { GuildArchives } from "../data/GuildArchives"; interface ILogChannel { include?: string[]; @@ -33,8 +34,9 @@ const unknownUser = { }; export class LogsPlugin extends Plugin { - protected serverLogs: GuildLogs; + protected guildLogs: GuildLogs; protected savedMessages: GuildSavedMessages; + protected archives: GuildArchives; protected logListener; @@ -55,11 +57,12 @@ export class LogsPlugin extends Plugin { } onLoad() { - this.serverLogs = new GuildLogs(this.guildId); + this.guildLogs = new GuildLogs(this.guildId); this.savedMessages = GuildSavedMessages.getInstance(this.guildId); + this.archives = GuildArchives.getInstance(this.guildId); this.logListener = ({ type, data }) => this.log(type, data); - this.serverLogs.on("log", this.logListener); + this.guildLogs.on("log", this.logListener); this.savedMessages.events.on("delete", this.onMessageDelete.bind(this)); this.savedMessages.events.on("deleteBulk", this.onMessageDeleteBulk.bind(this)); @@ -67,7 +70,7 @@ export class LogsPlugin extends Plugin { } onUnload() { - this.serverLogs.removeListener("log", this.logListener); + this.guildLogs.removeListener("log", this.logListener); this.savedMessages.events.off("delete", this.onMessageDelete.bind(this)); this.savedMessages.events.off("deleteBulk", this.onMessageDeleteBulk.bind(this)); @@ -113,7 +116,7 @@ export class LogsPlugin extends Plugin { round: true }); - this.serverLogs.log(LogType.MEMBER_JOIN, { + this.guildLogs.log(LogType.MEMBER_JOIN, { member: stripObjectToScalars(member, ["user"]), new: member.createdAt >= newThreshold ? " :new:" : "", account_age: accountAge @@ -122,7 +125,7 @@ export class LogsPlugin extends Plugin { @d.event("guildMemberRemove") onMemberLeave(_, member) { - this.serverLogs.log(LogType.MEMBER_LEAVE, { + this.guildLogs.log(LogType.MEMBER_LEAVE, { member: stripObjectToScalars(member, ["user"]) }); } @@ -136,7 +139,7 @@ export class LogsPlugin extends Plugin { ); const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser; - this.serverLogs.log( + this.guildLogs.log( LogType.MEMBER_BAN, { user: stripObjectToScalars(user), @@ -155,7 +158,7 @@ export class LogsPlugin extends Plugin { ); const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser; - this.serverLogs.log( + this.guildLogs.log( LogType.MEMBER_UNBAN, { mod: stripObjectToScalars(mod), @@ -170,7 +173,7 @@ export class LogsPlugin extends Plugin { if (!oldMember) return; if (member.nick !== oldMember.nick) { - this.serverLogs.log(LogType.MEMBER_NICK_CHANGE, { + this.guildLogs.log(LogType.MEMBER_NICK_CHANGE, { member, oldNick: oldMember.nick || "", newNick: member.nick @@ -189,7 +192,7 @@ export class LogsPlugin extends Plugin { const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser; if (addedRoles.length) { - this.serverLogs.log( + this.guildLogs.log( LogType.MEMBER_ROLE_ADD, { member, @@ -199,7 +202,7 @@ export class LogsPlugin extends Plugin { member.id ); } else if (removedRoles.length) { - this.serverLogs.log( + this.guildLogs.log( LogType.MEMBER_ROLE_REMOVE, { member, @@ -218,7 +221,7 @@ export class LogsPlugin extends Plugin { if (user.username !== oldUser.username || user.discriminator !== oldUser.discriminator) { const member = this.guild.members.get(user.id) || { id: user.id, user }; - this.serverLogs.log(LogType.MEMBER_USERNAME_CHANGE, { + this.guildLogs.log(LogType.MEMBER_USERNAME_CHANGE, { member: stripObjectToScalars(member, ["user"]), oldName: `${oldUser.username}#${oldUser.discriminator}`, newName: `${user.username}#${user.discriminator}` @@ -228,28 +231,28 @@ export class LogsPlugin extends Plugin { @d.event("channelCreate") onChannelCreate(channel) { - this.serverLogs.log(LogType.CHANNEL_CREATE, { + this.guildLogs.log(LogType.CHANNEL_CREATE, { channel: stripObjectToScalars(channel) }); } @d.event("channelDelete") onChannelDelete(channel) { - this.serverLogs.log(LogType.CHANNEL_DELETE, { + this.guildLogs.log(LogType.CHANNEL_DELETE, { channel: stripObjectToScalars(channel) }); } @d.event("guildRoleCreate") onRoleCreate(_, role) { - this.serverLogs.log(LogType.ROLE_CREATE, { + this.guildLogs.log(LogType.ROLE_CREATE, { role: stripObjectToScalars(role) }); } @d.event("guildRoleDelete") onRoleDelete(_, role) { - this.serverLogs.log(LogType.ROLE_DELETE, { + this.guildLogs.log(LogType.ROLE_DELETE, { role: stripObjectToScalars(role) }); } @@ -266,7 +269,7 @@ export class LogsPlugin extends Plugin { : "Unknown pre-edit content"; const after = disableCodeBlocks(deactivateMentions(savedMessage.data.content || "")); - this.serverLogs.log(LogType.MESSAGE_EDIT, { + this.guildLogs.log(LogType.MESSAGE_EDIT, { member: stripObjectToScalars(member, ["user"]), channel: stripObjectToScalars(channel), before, @@ -280,7 +283,7 @@ export class LogsPlugin extends Plugin { const channel = this.guild.channels.get(savedMessage.channel_id); if (member) { - this.serverLogs.log( + this.guildLogs.log( LogType.MESSAGE_DELETE, { member: stripObjectToScalars(member, ["user"]), @@ -290,7 +293,7 @@ export class LogsPlugin extends Plugin { savedMessage.id ); } else { - this.serverLogs.log( + this.guildLogs.log( LogType.MESSAGE_DELETE_BARE, { messageId: savedMessage.id, @@ -302,14 +305,18 @@ export class LogsPlugin extends Plugin { } // Uses events from savesMessages - onMessageDeleteBulk(savedMessages: SavedMessage[]) { + async onMessageDeleteBulk(savedMessages: SavedMessage[]) { const channel = this.guild.channels.get(savedMessages[0].channel_id); + const user = this.bot.users.get(savedMessages[0].user_id); + const archiveId = await this.archives.createFromSavedMessages(savedMessages, this.guild, channel, user); + const baseUrl = this.knub.getGlobalConfig().url; - this.serverLogs.log( + this.guildLogs.log( LogType.MESSAGE_DELETE_BULK, { count: savedMessages.length, - channel + channel, + archiveUrl: `${baseUrl}/archives/${archiveId}` }, savedMessages[0].id ); @@ -317,7 +324,7 @@ export class LogsPlugin extends Plugin { @d.event("voiceChannelJoin") onVoiceChannelJoin(member: Member, channel: Channel) { - this.serverLogs.log(LogType.VOICE_CHANNEL_JOIN, { + this.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, { member: stripObjectToScalars(member, ["user"]), channel: stripObjectToScalars(channel) }); @@ -325,7 +332,7 @@ export class LogsPlugin extends Plugin { @d.event("voiceChannelLeave") onVoiceChannelLeave(member: Member, channel: Channel) { - this.serverLogs.log(LogType.VOICE_CHANNEL_LEAVE, { + this.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, { member: stripObjectToScalars(member, ["user"]), channel: stripObjectToScalars(channel) }); @@ -333,7 +340,7 @@ export class LogsPlugin extends Plugin { @d.event("voiceChannelSwitch") onVoiceChannelSwitch(member: Member, newChannel: Channel, oldChannel: Channel) { - this.serverLogs.log(LogType.VOICE_CHANNEL_MOVE, { + this.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, { member: stripObjectToScalars(member, ["user"]), oldChannel: stripObjectToScalars(oldChannel), newChannel: stripObjectToScalars(newChannel) diff --git a/src/plugins/MessageSaver.ts b/src/plugins/MessageSaver.ts index de1404b6..41005ed9 100644 --- a/src/plugins/MessageSaver.ts +++ b/src/plugins/MessageSaver.ts @@ -36,4 +36,10 @@ export class MessageSaverPlugin extends Plugin { await this.savedMessages.saveEditFromMsg(msg); } + + @d.event("messageDeleteBulk", "guild", false) + async onMessageBulkDelete(messages: Message[]) { + const ids = messages.map(m => m.id); + await this.savedMessages.markBulkAsDeleted(ids); + } } diff --git a/src/plugins/Spam.ts b/src/plugins/Spam.ts index 63cb3ab6..1fbb7ef3 100644 --- a/src/plugins/Spam.ts +++ b/src/plugins/Spam.ts @@ -39,17 +39,7 @@ interface IRecentAction { const MAX_INTERVAL = 300; -const ARCHIVE_EXPIRY_DAYS = 90; -const ARCHIVE_HEADER_FORMAT = trimLines(` - Server: {guild.name} ({guild.id}) - Channel: #{channel.name} ({channel.id}) - User: {user.username}#{user.discriminator} ({user.id}) -`); -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} -`); +const SPAM_ARCHIVE_EXPIRY_DAYS = 90; export class SpamPlugin extends Plugin { protected logs: GuildLogs; @@ -163,31 +153,11 @@ export class SpamPlugin extends Plugin { } async saveSpamArchives(savedMessages: SavedMessage[], channel: Channel, user: User) { - const expiresAt = moment().add(ARCHIVE_EXPIRY_DAYS, "days"); - - const headerStr = formatTemplateString(ARCHIVE_HEADER_FORMAT, { - guild: this.guild, - channel, - user - }); - const msgLines = savedMessages.map(msg => { - return formatTemplateString(ARCHIVE_MESSAGE_FORMAT, { - id: msg.id, - timestamp: moment(msg.posted_at).format("HH:mm:ss"), - content: msg.data.content, - user - }); - }); - const messagesStr = msgLines.join("\n"); - const footerStr = formatTemplateString(ARCHIVE_FOOTER_FORMAT, { - timestamp: moment().format("YYYY-MM-DD [at] HH:mm:ss (Z)"), - expires: expiresAt.format("YYYY-MM-DD [at] HH:mm:ss (Z)") - }); - - const logId = await this.archives.create([headerStr, messagesStr, footerStr].join("\n\n"), expiresAt); + const expiresAt = moment().add(SPAM_ARCHIVE_EXPIRY_DAYS, "days"); + const archiveId = await this.archives.createFromSavedMessages(savedMessages, this.guild, channel, user, expiresAt); const url = this.knub.getGlobalConfig().url; - return url ? `${url}/archives/${logId}` : `Archive ID: ${logId}`; + return url ? `${url}/archives/${archiveId}` : `Archive ID: ${archiveId}`; } async logAndDetectSpam( @@ -285,13 +255,13 @@ export class SpamPlugin extends Plugin { // Generate a log from the detected messages 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); + const archiveUrl = await this.saveSpamArchives(uniqueMessages, channel, user); // Create a case and log the actions taken above const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note; const caseText = trimLines(` Automatic spam detection: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s) - ${logUrl} + ${archiveUrl} `); this.logs.log(LogType.SPAM_DETECTED, { @@ -300,7 +270,7 @@ export class SpamPlugin extends Plugin { description, limit: spamConfig.count, interval: spamConfig.interval, - logUrl + archiveUrl }); const caseId = await modActionsPlugin.createCase( diff --git a/src/plugins/Utility.ts b/src/plugins/Utility.ts index aac9ff36..41345201 100644 --- a/src/plugins/Utility.ts +++ b/src/plugins/Utility.ts @@ -1,12 +1,15 @@ import { Plugin, decorators as d, reply } from "knub"; import { Channel, EmbedOptions, Message, TextChannel, User, VoiceChannel } from "eris"; -import { embedPadding, errorMessage, getMessages, stripObjectToScalars, successMessage, trimLines } from "../utils"; +import { embedPadding, errorMessage, stripObjectToScalars, successMessage, trimLines } from "../utils"; import { GuildLogs } from "../data/GuildLogs"; import { LogType } from "../data/LogType"; import moment from "moment-timezone"; import humanizeDuration from "humanize-duration"; import { GuildCases } from "../data/GuildCases"; import { CaseTypes } from "../data/CaseTypes"; +import { SavedMessage } from "../data/entities/SavedMessage"; +import { GuildSavedMessages } from "../data/GuildSavedMessages"; +import { GuildArchives } from "../data/GuildArchives"; const MAX_SEARCH_RESULTS = 15; const MAX_CLEAN_COUNT = 50; @@ -16,6 +19,8 @@ const activeReloads: Map = new Map(); export class UtilityPlugin extends Plugin { protected logs: GuildLogs; protected cases: GuildCases; + protected savedMessages: GuildSavedMessages; + protected archives: GuildArchives; getDefaultOptions() { return { @@ -48,6 +53,8 @@ export class UtilityPlugin extends Plugin { onLoad() { this.logs = new GuildLogs(this.guildId); this.cases = GuildCases.getInstance(this.guildId); + this.savedMessages = GuildSavedMessages.getInstance(this.guildId); + this.archives = GuildArchives.getInstance(this.guildId); if (activeReloads && activeReloads.has(this.guildId)) { activeReloads.get(this.guildId).createMessage(successMessage("Reloaded!")); @@ -125,14 +132,22 @@ export class UtilityPlugin extends Plugin { } } - async cleanMessages(channel: Channel, messageIds: string[], mod: User) { - this.logs.ignoreLog(LogType.MESSAGE_DELETE, messageIds[0]); - this.logs.ignoreLog(LogType.MESSAGE_DELETE_BULK, messageIds[0]); - await this.bot.deleteMessages(channel.id, messageIds); + async cleanMessages(channel: Channel, savedMessages: SavedMessage[], mod: User) { + this.logs.ignoreLog(LogType.MESSAGE_DELETE, savedMessages[0].id); + this.logs.ignoreLog(LogType.MESSAGE_DELETE_BULK, savedMessages[0].id); + + await this.bot.deleteMessages(channel.id, savedMessages.map(m => m.id)); + + savedMessages.reverse(); + const user = this.bot.users.get(savedMessages[0].user_id); + const archiveId = await this.archives.createFromSavedMessages(savedMessages, this.guild, channel, user); + const archiveUrl = `${this.knub.getGlobalConfig().url}/archives/${archiveId}`; + this.logs.log(LogType.CLEAN, { mod: stripObjectToScalars(mod), channel: stripObjectToScalars(channel), - count: messageIds.length + count: savedMessages.length, + archiveUrl }); } @@ -145,9 +160,9 @@ export class UtilityPlugin extends Plugin { return; } - const messagesToClean = await getMessages(msg.channel as TextChannel, m => m.id !== msg.id, args.count); + const messagesToClean = await this.savedMessages.getLatestByChannelBeforeId(msg.channel.id, msg.id, args.count); if (messagesToClean.length > 0) { - await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author); + await this.cleanMessages(msg.channel, messagesToClean, msg.author); } msg.channel.createMessage( @@ -163,13 +178,9 @@ export class UtilityPlugin extends Plugin { return; } - const messagesToClean = await getMessages( - msg.channel as TextChannel, - m => m.id !== msg.id && m.author.id === args.userId, - args.count - ); + const messagesToClean = await this.savedMessages.getLatestByChannelAndUser(msg.channel.id, args.userId, args.count); if (messagesToClean.length > 0) { - await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author); + await this.cleanMessages(msg.channel, messagesToClean, msg.author); } msg.channel.createMessage( @@ -185,13 +196,9 @@ export class UtilityPlugin extends Plugin { return; } - const messagesToClean = await getMessages( - msg.channel as TextChannel, - m => m.id !== msg.id && m.author.bot, - args.count - ); + const messagesToClean = await this.savedMessages.getLatestBotMessagesByChannel(msg.channel.id, args.count); if (messagesToClean.length > 0) { - await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author); + await this.cleanMessages(msg.channel, messagesToClean, msg.author); } msg.channel.createMessage( diff --git a/src/utils.ts b/src/utils.ts index 75feb5ca..04dbae25 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -173,58 +173,6 @@ export function getEmojiInString(str: string): string[] { return str.match(anyEmojiRegex) || []; } -export type MessageFilterFn = (msg: Message) => boolean; -export type StopFn = (msg: Message) => boolean; - -export async function getMessages( - channel: TextChannel, - filter: MessageFilterFn = null, - maxCount: number = 50, - stopFn: StopFn = null -): Promise { - let messages: Message[] = []; - let before; - - if (!filter) { - filter = () => true; - } - - while (true) { - const newMessages = await channel.getMessages(50, before); - if (newMessages.length === 0) break; - - before = newMessages[newMessages.length - 1].id; - - const filtered = newMessages.filter(filter); - messages.push(...filtered); - - if (messages.length >= maxCount) { - messages = messages.slice(0, maxCount); - break; - } - - if (stopFn && newMessages.some(stopFn)) { - break; - } - } - - return messages; -} - -export async function cleanMessagesInChannel( - bot: Client, - channel: TextChannel, - count: number, - userId: string = null, - reason: string = null -) { - const messages = await getMessages(channel, msg => !userId || msg.author.id === userId, count); - const ids = messages.map(m => m.id); - if (ids) { - await bot.deleteMessages(channel.id, ids, reason); - } -} - export function trimLines(str: string) { return str .trim()