diff --git a/backend/src/data/GuildArchives.ts b/backend/src/data/GuildArchives.ts index e55ac905..066714a1 100644 --- a/backend/src/data/GuildArchives.ts +++ b/backend/src/data/GuildArchives.ts @@ -1,12 +1,17 @@ -import { Guild, Snowflake } from "discord.js"; +import { Guild, Snowflake, User } from "discord.js"; import moment from "moment-timezone"; import { isDefaultSticker } from "src/utils/isDefaultSticker"; import { getRepository, Repository } from "typeorm"; -import { renderTemplate } from "../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../templateFormatter"; import { trimLines } from "../utils"; import { BaseGuildRepository } from "./BaseGuildRepository"; import { ArchiveEntry } from "./entities/ArchiveEntry"; import { SavedMessage } from "./entities/SavedMessage"; +import { + channelToTemplateSafeChannel, + guildToTemplateSafeGuild, + userToTemplateSafeUser, +} from "../utils/templateSafeObjects"; const DEFAULT_EXPIRY_DAYS = 30; @@ -75,9 +80,9 @@ export class GuildArchives extends BaseGuildRepository { const msgLines: string[] = []; for (const msg of savedMessages) { const channel = guild.channels.cache.get(msg.channel_id as Snowflake); - const user = { ...msg.data.author, id: msg.user_id }; + const partialUser = new TemplateSafeValueContainer({ ...msg.data.author, id: msg.user_id }); - const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, { + const values = new TemplateSafeValueContainer({ id: msg.id, timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), content: msg.data.content, @@ -87,9 +92,10 @@ export class GuildArchives extends BaseGuildRepository { stickers: msg.data.stickers?.map(sti => { return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) }); }), - user, - channel, + user: partialUser, + channel: channel ? channelToTemplateSafeChannel(channel) : null, }); + const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, {}); msgLines.push(line); } return msgLines; @@ -100,7 +106,9 @@ export class GuildArchives extends BaseGuildRepository { expiresAt = moment.utc().add(DEFAULT_EXPIRY_DAYS, "days"); } - const headerStr = await renderTemplate(MESSAGE_ARCHIVE_HEADER_FORMAT, { guild }); + const headerStr = await renderTemplate(MESSAGE_ARCHIVE_HEADER_FORMAT, { + guild: guildToTemplateSafeGuild(guild), + }); const msgLines = await this.renderLinesFromSavedMessages(savedMessages, guild); const messagesStr = msgLines.join("\n"); diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index ff4daf7f..efaa08c0 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -45,9 +45,90 @@ export class GuildSavedMessages extends BaseGuildRepository { timestamp: msg.createdTimestamp, }; - if (msg.attachments.size) data.attachments = [...msg.attachments.values()]; - if (msg.embeds.length) data.embeds = msg.embeds; - if (msg.stickers?.size) data.stickers = [...msg.stickers.values()]; + if (msg.attachments.size) { + data.attachments = Array.from(msg.attachments.values()).map(att => ({ + id: att.id, + contentType: att.contentType, + name: att.name, + proxyURL: att.proxyURL, + size: att.size, + spoiler: att.spoiler, + url: att.url, + width: att.width, + })); + } + + if (msg.embeds.length) { + data.embeds = msg.embeds.map(embed => ({ + title: embed.title, + description: embed.description, + url: embed.url, + timestamp: embed.timestamp, + color: embed.color, + + fields: embed.fields.map(field => ({ + name: field.name, + value: field.value, + inline: field.inline, + })), + + author: embed.author + ? { + name: embed.author.name, + url: embed.author.url, + iconURL: embed.author.iconURL, + proxyIconURL: embed.author.proxyIconURL, + } + : undefined, + + thumbnail: embed.thumbnail + ? { + url: embed.thumbnail.url, + proxyURL: embed.thumbnail.proxyURL, + height: embed.thumbnail.height, + width: embed.thumbnail.width, + } + : undefined, + + image: embed.image + ? { + url: embed.image.url, + proxyURL: embed.image.proxyURL, + height: embed.image.height, + width: embed.image.width, + } + : undefined, + + video: embed.video + ? { + url: embed.video.url, + proxyURL: embed.video.proxyURL, + height: embed.video.height, + width: embed.video.width, + } + : undefined, + + footer: embed.footer + ? { + text: embed.footer.text, + iconURL: embed.footer.iconURL, + proxyIconURL: embed.footer.proxyIconURL, + } + : undefined, + })); + } + + if (msg.stickers?.size) { + data.stickers = Array.from(msg.stickers.values()).map(sticker => ({ + format: sticker.format, + guildId: sticker.guildId, + id: sticker.id, + name: sticker.name, + description: sticker.description, + available: sticker.available, + type: sticker.type, + })); + } return data; } diff --git a/backend/src/data/entities/SavedMessage.ts b/backend/src/data/entities/SavedMessage.ts index 77294366..013dba80 100644 --- a/backend/src/data/entities/SavedMessage.ts +++ b/backend/src/data/entities/SavedMessage.ts @@ -1,16 +1,79 @@ -import { MessageAttachment, Sticker } from "discord.js"; +import { Snowflake } from "discord.js"; import { Column, Entity, PrimaryColumn } from "typeorm"; import { createEncryptedJsonTransformer } from "../encryptedJsonTransformer"; +export interface ISavedMessageAttachmentData { + id: Snowflake; + contentType: string | null; + name: string | null; + proxyURL: string; + size: number; + spoiler: boolean; + url: string; + width: number | null; +} + +export interface ISavedMessageEmbedData { + title: string | null; + description: string | null; + url: string | null; + timestamp: number | null; + color: number | null; + fields: Array<{ + name: string; + value: string; + inline: boolean; + }>; + author?: { + name?: string; + url?: string; + iconURL?: string; + proxyIconURL?: string; + }; + thumbnail?: { + url: string; + proxyURL?: string; + height?: number; + width?: number; + }; + image?: { + url: string; + proxyURL?: string; + height?: number; + width?: number; + }; + video?: { + url?: string; + proxyURL?: string; + height?: number; + width?: number; + }; + footer?: { + text?: string; + iconURL?: string; + proxyIconURL?: string; + }; +} + +export interface ISavedMessageStickerData { + format: string; + guildId: Snowflake | null; + id: Snowflake; + name: string; + description: string | null; + available: boolean | null; + type: string | null; +} + export interface ISavedMessageData { - attachments?: MessageAttachment[]; + attachments?: ISavedMessageAttachmentData[]; author: { username: string; discriminator: string; }; content: string; - embeds?: object[]; - stickers?: Sticker[]; + embeds?: ISavedMessageEmbedData[]; + stickers?: ISavedMessageStickerData[]; timestamp: number; } diff --git a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts index 332994cf..07ffec69 100644 --- a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts +++ b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts @@ -1,7 +1,7 @@ import { Permissions, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { resolveUser, verboseChannelMention } from "../../../utils"; @@ -27,7 +27,7 @@ export async function deleteNextItem(pluginData: GuildPluginData, msg: SavedMessage) { const member = await resolveMember(pluginData.client, pluginData.guild, msg.user_id); @@ -14,7 +15,7 @@ export async function onMessageCreate(pluginData: GuildPluginData MAX_DELAY) { delay = MAX_DELAY; if (!pluginData.state.maxDelayWarningSent) { - pluginData.state.guildLogs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Clamped auto-deletion delay in <#${msg.channel_id}> to 5 minutes`, }); pluginData.state.maxDelayWarningSent = true; diff --git a/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts b/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts index 7f868af1..7649777c 100644 --- a/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts +++ b/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts @@ -26,7 +26,7 @@ export const AddReactionsEvt = autoReactionsEvt({ ); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Cannot apply auto-reactions in <#${message.channel.id}>. ${missingPermissionError(missingPermissions)}`, }); return; @@ -39,11 +39,11 @@ export const AddReactionsEvt = autoReactionsEvt({ if (isDiscordAPIError(e)) { const logs = pluginData.getPlugin(LogsPlugin); if (e.code === 10008) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Could not apply auto-reactions in <#${message.channel.id}> for message \`${message.id}\`. Make sure nothing is deleting the message before the reactions are applied.`, }); } else { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Could not apply auto-reactions in <#${message.channel.id}> for message \`${message.id}\`. Error code ${e.code}.`, }); } diff --git a/backend/src/plugins/Automod/actions/addRoles.ts b/backend/src/plugins/Automod/actions/addRoles.ts index 91a12bca..c39cc5e7 100644 --- a/backend/src/plugins/Automod/actions/addRoles.ts +++ b/backend/src/plugins/Automod/actions/addRoles.ts @@ -23,7 +23,7 @@ export const AddRolesAction = automodAction({ const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Cannot add roles in Automod rule **${ruleName}**. ${missingPermissionError(missingPermissions)}`, }); return; @@ -44,7 +44,7 @@ export const AddRolesAction = automodAction({ roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Unable to assign the following roles in Automod rule **${ruleName}**: **${roleNamesWeCannotAssign.join( "**, **", )}**`, diff --git a/backend/src/plugins/Automod/actions/addToCounter.ts b/backend/src/plugins/Automod/actions/addToCounter.ts index 74e1ddc5..04d73f83 100644 --- a/backend/src/plugins/Automod/actions/addToCounter.ts +++ b/backend/src/plugins/Automod/actions/addToCounter.ts @@ -2,6 +2,7 @@ import * as t from "io-ts"; import { LogType } from "../../../data/LogType"; import { CountersPlugin } from "../../Counters/CountersPlugin"; import { automodAction } from "../helpers"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const AddToCounterAction = automodAction({ configType: t.type({ @@ -14,7 +15,7 @@ export const AddToCounterAction = automodAction({ async apply({ pluginData, contexts, actionConfig, matchResult, ruleName }) { const countersPlugin = pluginData.getPlugin(CountersPlugin); if (!countersPlugin.counterExists(actionConfig.counter)) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown counter \`${actionConfig.counter}\` in \`add_to_counter\` action of Automod rule \`${ruleName}\``, }); return; diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index 206c215c..d26a9777 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -2,17 +2,24 @@ import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions"; import { LogType } from "../../../data/LogType"; -import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; +import { + createTypedTemplateSafeValueContainer, + renderTemplate, + TemplateParseError, + TemplateSafeValueContainer, +} from "../../../templateFormatter"; import { createChunkedMessage, messageLink, stripObjectToScalars, tAllowedMentions, tNormalizedNullOptional, + isTruthy, verboseChannelMention, } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { automodAction } from "../helpers"; +import { TemplateSafeUser, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; export const AlertAction = automodAction({ configType: t.type({ @@ -32,33 +39,39 @@ export const AlertAction = automodAction({ const theMessageLink = contexts[0].message && messageLink(pluginData.guild.id, contexts[0].message.channel_id, contexts[0].message.id); - const safeUsers = contexts.map(c => c.user && stripObjectToScalars(c.user)).filter(Boolean); + const safeUsers = contexts.map(c => (c.user ? userToTemplateSafeUser(c.user) : null)).filter(isTruthy); const safeUser = safeUsers[0]; const actionsTaken = Object.keys(pluginData.config.get().rules[ruleName].actions).join(", "); - const logMessage = await logs.getLogMessage(LogType.AUTOMOD_ACTION, { - rule: ruleName, - user: safeUser, - users: safeUsers, - actionsTaken, - matchSummary: matchResult.summary, - }); - - let rendered; - try { - rendered = await renderTemplate(actionConfig.text, { + const logMessage = await logs.getLogMessage( + LogType.AUTOMOD_ACTION, + createTypedTemplateSafeValueContainer({ rule: ruleName, user: safeUser, users: safeUsers, - text, actionsTaken, - matchSummary: matchResult.summary, - messageLink: theMessageLink, - logMessage, - }); + matchSummary: matchResult.summary ?? "", + }), + ); + + let rendered; + try { + rendered = await renderTemplate( + actionConfig.text, + new TemplateSafeValueContainer({ + rule: ruleName, + user: safeUser, + users: safeUsers, + text, + actionsTaken, + matchSummary: matchResult.summary, + messageLink: theMessageLink, + logMessage: logMessage?.content, + }), + ); } catch (err) { if (err instanceof TemplateParseError) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Error in alert format of automod rule ${ruleName}: ${err.message}`, }); return; @@ -75,13 +88,13 @@ export const AlertAction = automodAction({ ); } catch (err) { if (err.code === 50001) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Missing access to send alert to channel ${verboseChannelMention( channel, )} in automod rule **${ruleName}**`, }); } else { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Error ${err.code || "UNKNOWN"} when sending alert to channel ${verboseChannelMention( channel, )} in automod rule **${ruleName}**`, @@ -89,7 +102,7 @@ export const AlertAction = automodAction({ } } } else { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Invalid channel id \`${actionConfig.channel}\` for alert action in automod rule **${ruleName}**`, }); } diff --git a/backend/src/plugins/Automod/actions/changeNickname.ts b/backend/src/plugins/Automod/actions/changeNickname.ts index d6dee527..ed2db27c 100644 --- a/backend/src/plugins/Automod/actions/changeNickname.ts +++ b/backend/src/plugins/Automod/actions/changeNickname.ts @@ -22,7 +22,7 @@ export const ChangeNicknameAction = automodAction({ const newName = typeof actionConfig === "string" ? actionConfig : actionConfig.name; member.edit({ nick: newName }).catch(err => { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to change the nickname of \`${member.id}\``, }); }); diff --git a/backend/src/plugins/Automod/actions/log.ts b/backend/src/plugins/Automod/actions/log.ts index 7218ce5f..b7470427 100644 --- a/backend/src/plugins/Automod/actions/log.ts +++ b/backend/src/plugins/Automod/actions/log.ts @@ -1,24 +1,23 @@ import * as t from "io-ts"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars, unique } from "../../../utils"; +import { isTruthy, stripObjectToScalars, unique } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { automodAction } from "../helpers"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; export const LogAction = automodAction({ configType: t.boolean, defaultConfig: true, async apply({ pluginData, contexts, ruleName, matchResult }) { - const safeUsers = unique(contexts.map(c => c.user)) - .filter(Boolean) - .map(user => stripObjectToScalars(user)); - const safeUser = safeUsers[0]; + const users = unique(contexts.map(c => c.user)).filter(isTruthy); + const user = users[0]; const actionsTaken = Object.keys(pluginData.config.get().rules[ruleName].actions).join(", "); - pluginData.getPlugin(LogsPlugin).log(LogType.AUTOMOD_ACTION, { + pluginData.getPlugin(LogsPlugin).logAutomodAction({ rule: ruleName, - user: safeUser, - users: safeUsers, + user, + users, actionsTaken, matchSummary: matchResult.summary, }); diff --git a/backend/src/plugins/Automod/actions/mute.ts b/backend/src/plugins/Automod/actions/mute.ts index 564f65a8..8d4aee6a 100644 --- a/backend/src/plugins/Automod/actions/mute.ts +++ b/backend/src/plugins/Automod/actions/mute.ts @@ -55,7 +55,7 @@ export const MuteAction = automodAction({ ); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to mute <@!${userId}> in Automod rule \`${ruleName}\` because a mute role has not been specified in server config`, }); } else { diff --git a/backend/src/plugins/Automod/actions/removeRoles.ts b/backend/src/plugins/Automod/actions/removeRoles.ts index 0e125fb7..690af257 100644 --- a/backend/src/plugins/Automod/actions/removeRoles.ts +++ b/backend/src/plugins/Automod/actions/removeRoles.ts @@ -24,7 +24,7 @@ export const RemoveRolesAction = automodAction({ const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Cannot add roles in Automod rule **${ruleName}**. ${missingPermissionError(missingPermissions)}`, }); return; @@ -45,7 +45,7 @@ export const RemoveRolesAction = automodAction({ roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Unable to remove the following roles in Automod rule **${ruleName}**: **${roleNamesWeCannotRemove.join( "**, **", )}**`, diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index ad682ee9..69fcf937 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -1,6 +1,6 @@ import { MessageOptions, Permissions, Snowflake, TextChannel, User } from "discord.js"; import * as t from "io-ts"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { renderTemplate } from "../../../templateFormatter"; import { @@ -16,6 +16,7 @@ import { import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; import { automodAction } from "../helpers"; import { AutomodContext } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const ReplyAction = automodAction({ configType: t.union([ @@ -48,7 +49,7 @@ export const ReplyAction = automodAction({ const renderReplyText = async str => renderTemplate(str, { - user: userToConfigAccessibleUser(user), + user: userToTemplateSafeUser(user), }); const formatted = typeof actionConfig === "string" @@ -65,7 +66,7 @@ export const ReplyAction = automodAction({ Permissions.FLAGS.SEND_MESSAGES | Permissions.FLAGS.VIEW_CHANNEL, ) ) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing permissions to reply in ${verboseChannelMention(channel)} in Automod rule \`${ruleName}\``, }); continue; @@ -76,7 +77,7 @@ export const ReplyAction = automodAction({ typeof formatted !== "string" && !hasDiscordPermissions(channel.permissionsFor(pluginData.client.user!.id), Permissions.FLAGS.EMBED_LINKS) ) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing permissions to reply **with an embed** in ${verboseChannelMention( channel, )} in Automod rule \`${ruleName}\``, diff --git a/backend/src/plugins/Automod/actions/setCounter.ts b/backend/src/plugins/Automod/actions/setCounter.ts index 150d77f7..5be82cf6 100644 --- a/backend/src/plugins/Automod/actions/setCounter.ts +++ b/backend/src/plugins/Automod/actions/setCounter.ts @@ -2,6 +2,7 @@ import * as t from "io-ts"; import { LogType } from "../../../data/LogType"; import { CountersPlugin } from "../../Counters/CountersPlugin"; import { automodAction } from "../helpers"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const SetCounterAction = automodAction({ configType: t.type({ @@ -14,7 +15,7 @@ export const SetCounterAction = automodAction({ async apply({ pluginData, contexts, actionConfig, matchResult, ruleName }) { const countersPlugin = pluginData.getPlugin(CountersPlugin); if (!countersPlugin.counterExists(actionConfig.counter)) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown counter \`${actionConfig.counter}\` in \`add_to_counter\` action of Automod rule \`${ruleName}\``, }); return; diff --git a/backend/src/plugins/Automod/actions/setSlowmode.ts b/backend/src/plugins/Automod/actions/setSlowmode.ts index d0d286a8..ad333e6d 100644 --- a/backend/src/plugins/Automod/actions/setSlowmode.ts +++ b/backend/src/plugins/Automod/actions/setSlowmode.ts @@ -4,6 +4,7 @@ import { ChannelTypeStrings } from "src/types"; import { LogType } from "../../../data/LogType"; import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils"; import { automodAction } from "../helpers"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const SetSlowmodeAction = automodAction({ configType: t.type({ @@ -53,7 +54,7 @@ export const SetSlowmodeAction = automodAction({ ? `Duration is greater than maximum native slowmode duration` : e.message; - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unable to set slowmode for channel ${channel.id} to ${slowmodeSeconds} seconds: ${errorMessage}`, }); } diff --git a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts index c4da61cd..c77cb414 100644 --- a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts +++ b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts @@ -1,6 +1,6 @@ import { User } from "discord.js"; import { GuildPluginData } from "knub"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { runAutomodOnAntiraidLevel } from "../events/runAutomodOnAntiraidLevel"; @@ -19,12 +19,12 @@ export async function setAntiraidLevel( const logs = pluginData.getPlugin(LogsPlugin); if (user) { - logs.log(LogType.SET_ANTIRAID_USER, { + logs.logSetAntiraidUser({ level: newLevel ?? "off", - user: userToConfigAccessibleUser(user), + user, }); } else { - logs.log(LogType.SET_ANTIRAID_AUTO, { + logs.logSetAntiraidAuto({ level: newLevel ?? "off", }); } diff --git a/backend/src/plugins/Cases/CasesPlugin.ts b/backend/src/plugins/Cases/CasesPlugin.ts index 6f870fc6..159e45fc 100644 --- a/backend/src/plugins/Cases/CasesPlugin.ts +++ b/backend/src/plugins/Cases/CasesPlugin.ts @@ -16,6 +16,7 @@ import { getRecentCasesByMod } from "./functions/getRecentCasesByMod"; import { getTotalCasesByMod } from "./functions/getTotalCasesByMod"; import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel"; import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types"; +import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions = { config: { @@ -38,7 +39,7 @@ export const CasesPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: [TimeAndDatePlugin], + dependencies: [TimeAndDatePlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts b/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts index 7f3f04d5..cba29ecb 100644 --- a/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts +++ b/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts @@ -6,6 +6,7 @@ import { isDiscordAPIError } from "../../../utils"; import { CasesPluginType } from "../types"; import { getCaseEmbed } from "./getCaseEmbed"; import { resolveCaseId } from "./resolveCaseId"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function postToCaseLogChannel( pluginData: GuildPluginData, @@ -26,7 +27,7 @@ export async function postToCaseLogChannel( result = await caseLogChannel.send({ ...content }); } catch (e) { if (isDiscordAPIError(e) && (e.code === 50013 || e.code === 50001)) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing permissions to post mod cases in <#${caseLogChannel.id}>`, }); return null; @@ -67,7 +68,7 @@ export async function postCaseToCaseLogChannel( } return postedMessage; } catch { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to post case #${theCase.case_number} to the case log channel`, }); return null; diff --git a/backend/src/plugins/Censor/util/censorMessage.ts b/backend/src/plugins/Censor/util/censorMessage.ts index 509680e2..a9549974 100644 --- a/backend/src/plugins/Censor/util/censorMessage.ts +++ b/backend/src/plugins/Censor/util/censorMessage.ts @@ -1,11 +1,11 @@ -import { Snowflake, TextChannel } from "discord.js"; +import { BaseGuildTextChannel, Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { deactivateMentions, disableCodeBlocks } from "knub/dist/helpers"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; import { resolveUser } from "../../../utils"; import { CensorPluginType } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function censorMessage( pluginData: GuildPluginData, @@ -22,13 +22,14 @@ export async function censorMessage( } const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)! as + | BaseGuildTextChannel + | ThreadChannel; - pluginData.state.serverLogs.log(LogType.CENSOR, { - user: userToConfigAccessibleUser(user), - channel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logCensor({ + user, + channel, reason, message: savedMessage, - messageText: disableCodeBlocks(deactivateMentions(savedMessage.data.content)), }); } diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts index ea9c58ef..6ee14cf6 100644 --- a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -70,7 +70,7 @@ export async function handleCompanionPermissions( } catch (e) { if (isDiscordAPIError(e) && e.code === 50001) { const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Missing permissions to handle companion channels. Pausing companion channels for 5 minutes or until the bot is reloaded on this server.`, }); pluginData.state.errorCooldownManager.setCooldown(ERROR_COOLDOWN_KEY, ERROR_COOLDOWN); diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index c63ffc1a..1831f7d8 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -41,7 +41,7 @@ export async function cleanAction( interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`clean\`:_ ${e}`, }); } else { diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 089dcd26..e69c84db 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -56,7 +56,7 @@ export async function muteAction( interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to mute <@!${userId}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`, }); } else { diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts index b38d885c..77311ba7 100644 --- a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts +++ b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts @@ -27,7 +27,7 @@ export async function loadAllCommands(pluginData: GuildPluginData { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, `Unable to overwrite context menus: ${e}`); + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unable to overwrite context menus: ${e}` }); return undefined; }); if (!setCommands) return; diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index d4285de8..d324cb11 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -30,13 +30,85 @@ import { import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts"; import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts"; import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts"; -import { ConfigSchema, FORMAT_NO_TIMESTAMP, LogsPluginType } from "./types"; +import { ConfigSchema, FORMAT_NO_TIMESTAMP, ILogTypeData, LogsPluginType, TLogChannel } from "./types"; import { getLogMessage } from "./util/getLogMessage"; import { log } from "./util/log"; import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk"; import { onMessageUpdate } from "./util/onMessageUpdate"; import { Util } from "discord.js"; +import { TemplateSafeValueContainer, TypedTemplateSafeValueContainer } from "../../templateFormatter"; +import { mapToPublicFn } from "../../pluginUtils"; + +import { logAutomodAction } from "./logFunctions/logAutomodAction"; +import { logBotAlert } from "./logFunctions/logBotAlert"; +import { logCaseCreate } from "./logFunctions/logCaseCreate"; +import { logCaseDelete } from "./logFunctions/logCaseDelete"; +import { logCaseUpdate } from "./logFunctions/logCaseUpdate"; +import { logCensor } from "./logFunctions/logCensor"; +import { logChannelCreate } from "./logFunctions/logChannelCreate"; +import { logChannelDelete } from "./logFunctions/logChannelDelete"; +import { logChannelUpdate } from "./logFunctions/logChannelUpdate"; +import { logClean } from "./logFunctions/logClean"; +import { logEmojiCreate } from "./logFunctions/logEmojiCreate"; +import { logEmojiDelete } from "./logFunctions/logEmojiDelete"; +import { logEmojiUpdate } from "./logFunctions/logEmojiUpdate"; +import { logMassBan } from "./logFunctions/logMassBan"; +import { logMassMute } from "./logFunctions/logMassMute"; +import { logMassUnban } from "./logFunctions/logMassUnban"; +import { logMemberBan } from "./logFunctions/logMemberBan"; +import { logMemberForceban } from "./logFunctions/logMemberForceban"; +import { logMemberJoin } from "./logFunctions/logMemberJoin"; +import { logMemberJoinWithPriorRecords } from "./logFunctions/logMemberJoinWithPriorRecords"; +import { logMemberKick } from "./logFunctions/logMemberKick"; +import { logMemberLeave } from "./logFunctions/logMemberLeave"; +import { logMemberMute } from "./logFunctions/logMemberMute"; +import { logMemberMuteExpired } from "./logFunctions/logMemberMuteExpired"; +import { logMemberMuteRejoin } from "./logFunctions/logMemberMuteRejoin"; +import { logMemberNickChange } from "./logFunctions/logMemberNickChange"; +import { logMemberNote } from "./logFunctions/logMemberNote"; +import { logMemberRestore } from "./logFunctions/logMemberRestore"; +import { logMemberRoleAdd } from "./logFunctions/logMemberRoleAdd"; +import { logMemberRoleChanges } from "./logFunctions/logMemberRoleChanges"; +import { logMemberRoleRemove } from "./logFunctions/logMemberRoleRemove"; +import { logMemberTimedBan } from "./logFunctions/logMemberTimedBan"; +import { logMemberTimedMute } from "./logFunctions/logMemberTimedMute"; +import { logMemberTimedUnmute } from "./logFunctions/logMemberTimedUnmute"; +import { logMemberUnban } from "./logFunctions/logMemberUnban"; +import { logMemberUnmute } from "./logFunctions/logMemberUnmute"; +import { logMemberWarn } from "./logFunctions/logMemberWarn"; +import { logMessageDelete } from "./logFunctions/logMessageDelete"; +import { logMessageDeleteAuto } from "./logFunctions/logMessageDeleteAuto"; +import { logMessageDeleteBare } from "./logFunctions/logMessageDeleteBare"; +import { logMessageDeleteBulk } from "./logFunctions/logMessageDeleteBulk"; +import { logMessageEdit } from "./logFunctions/logMessageEdit"; +import { logMessageSpamDetected } from "./logFunctions/logMessageSpamDetected"; +import { logOtherSpamDetected } from "./logFunctions/logOtherSpamDetected"; +import { logPostedScheduledMessage } from "./logFunctions/logPostedScheduledMessage"; +import { logRepeatedMessage } from "./logFunctions/logRepeatedMessage"; +import { logRoleCreate } from "./logFunctions/logRoleCreate"; +import { logRoleDelete } from "./logFunctions/logRoleDelete"; +import { logRoleUpdate } from "./logFunctions/logRoleUpdate"; +import { logScheduledMessage } from "./logFunctions/logScheduledMessage"; +import { logScheduledRepeatedMessage } from "./logFunctions/logScheduledRepeatedMessage"; +import { logSetAntiraidAuto } from "./logFunctions/logSetAntiraidAuto"; +import { logSetAntiraidUser } from "./logFunctions/logSetAntiraidUser"; +import { logStageInstanceCreate } from "./logFunctions/logStageInstanceCreate"; +import { logStageInstanceDelete } from "./logFunctions/logStageInstanceDelete"; +import { logStageInstanceUpdate } from "./logFunctions/logStageInstanceUpdate"; +import { logStickerCreate } from "./logFunctions/logStickerCreate"; +import { logStickerDelete } from "./logFunctions/logStickerDelete"; +import { logStickerUpdate } from "./logFunctions/logStickerUpdate"; +import { logThreadCreate } from "./logFunctions/logThreadCreate"; +import { logThreadDelete } from "./logFunctions/logThreadDelete"; +import { logThreadUpdate } from "./logFunctions/logThreadUpdate"; +import { logVoiceChannelForceDisconnect } from "./logFunctions/logVoiceChannelForceDisconnect"; +import { logVoiceChannelForceMove } from "./logFunctions/logVoiceChannelForceMove"; +import { logVoiceChannelJoin } from "./logFunctions/logVoiceChannelJoin"; +import { logVoiceChannelLeave } from "./logFunctions/logVoiceChannelLeave"; +import { logVoiceChannelMove } from "./logFunctions/logVoiceChannelMove"; +import { logMemberTimedUnban } from "./logFunctions/logMemberTimedUnban"; +import { logDmFailed } from "./logFunctions/logDmFailed"; const defaultOptions: PluginOptions = { config: { @@ -98,17 +170,85 @@ export const LogsPlugin = zeppelinGuildPlugin()({ ], public: { - log(pluginData) { - return (type: LogType, data: any) => { - return log(pluginData, type, data); + getLogMessage: pluginData => { + return ( + type: TLogType, + data: TypedTemplateSafeValueContainer, + opts?: Pick, + ) => { + return getLogMessage(pluginData, type, data, opts); }; }, - getLogMessage(pluginData) { - return (type: LogType, data: any) => { - return getLogMessage(pluginData, type, data); - }; - }, + logAutomodAction: mapToPublicFn(logAutomodAction), + logBotAlert: mapToPublicFn(logBotAlert), + logCaseCreate: mapToPublicFn(logCaseCreate), + logCaseDelete: mapToPublicFn(logCaseDelete), + logCaseUpdate: mapToPublicFn(logCaseUpdate), + logCensor: mapToPublicFn(logCensor), + logChannelCreate: mapToPublicFn(logChannelCreate), + logChannelDelete: mapToPublicFn(logChannelDelete), + logChannelUpdate: mapToPublicFn(logChannelUpdate), + logClean: mapToPublicFn(logClean), + logEmojiCreate: mapToPublicFn(logEmojiCreate), + logEmojiDelete: mapToPublicFn(logEmojiDelete), + logEmojiUpdate: mapToPublicFn(logEmojiUpdate), + logMassBan: mapToPublicFn(logMassBan), + logMassMute: mapToPublicFn(logMassMute), + logMassUnban: mapToPublicFn(logMassUnban), + logMemberBan: mapToPublicFn(logMemberBan), + logMemberForceban: mapToPublicFn(logMemberForceban), + logMemberJoin: mapToPublicFn(logMemberJoin), + logMemberJoinWithPriorRecords: mapToPublicFn(logMemberJoinWithPriorRecords), + logMemberKick: mapToPublicFn(logMemberKick), + logMemberLeave: mapToPublicFn(logMemberLeave), + logMemberMute: mapToPublicFn(logMemberMute), + logMemberMuteExpired: mapToPublicFn(logMemberMuteExpired), + logMemberMuteRejoin: mapToPublicFn(logMemberMuteRejoin), + logMemberNickChange: mapToPublicFn(logMemberNickChange), + logMemberNote: mapToPublicFn(logMemberNote), + logMemberRestore: mapToPublicFn(logMemberRestore), + logMemberRoleAdd: mapToPublicFn(logMemberRoleAdd), + logMemberRoleChanges: mapToPublicFn(logMemberRoleChanges), + logMemberRoleRemove: mapToPublicFn(logMemberRoleRemove), + logMemberTimedBan: mapToPublicFn(logMemberTimedBan), + logMemberTimedMute: mapToPublicFn(logMemberTimedMute), + logMemberTimedUnban: mapToPublicFn(logMemberTimedUnban), + logMemberTimedUnmute: mapToPublicFn(logMemberTimedUnmute), + logMemberUnban: mapToPublicFn(logMemberUnban), + logMemberUnmute: mapToPublicFn(logMemberUnmute), + logMemberWarn: mapToPublicFn(logMemberWarn), + logMessageDelete: mapToPublicFn(logMessageDelete), + logMessageDeleteAuto: mapToPublicFn(logMessageDeleteAuto), + logMessageDeleteBare: mapToPublicFn(logMessageDeleteBare), + logMessageDeleteBulk: mapToPublicFn(logMessageDeleteBulk), + logMessageEdit: mapToPublicFn(logMessageEdit), + logMessageSpamDetected: mapToPublicFn(logMessageSpamDetected), + logOtherSpamDetected: mapToPublicFn(logOtherSpamDetected), + logPostedScheduledMessage: mapToPublicFn(logPostedScheduledMessage), + logRepeatedMessage: mapToPublicFn(logRepeatedMessage), + logRoleCreate: mapToPublicFn(logRoleCreate), + logRoleDelete: mapToPublicFn(logRoleDelete), + logRoleUpdate: mapToPublicFn(logRoleUpdate), + logScheduledMessage: mapToPublicFn(logScheduledMessage), + logScheduledRepeatedMessage: mapToPublicFn(logScheduledRepeatedMessage), + logSetAntiraidAuto: mapToPublicFn(logSetAntiraidAuto), + logSetAntiraidUser: mapToPublicFn(logSetAntiraidUser), + logStageInstanceCreate: mapToPublicFn(logStageInstanceCreate), + logStageInstanceDelete: mapToPublicFn(logStageInstanceDelete), + logStageInstanceUpdate: mapToPublicFn(logStageInstanceUpdate), + logStickerCreate: mapToPublicFn(logStickerCreate), + logStickerDelete: mapToPublicFn(logStickerDelete), + logStickerUpdate: mapToPublicFn(logStickerUpdate), + logThreadCreate: mapToPublicFn(logThreadCreate), + logThreadDelete: mapToPublicFn(logThreadDelete), + logThreadUpdate: mapToPublicFn(logThreadUpdate), + logVoiceChannelForceDisconnect: mapToPublicFn(logVoiceChannelForceDisconnect), + logVoiceChannelForceMove: mapToPublicFn(logVoiceChannelForceMove), + logVoiceChannelJoin: mapToPublicFn(logVoiceChannelJoin), + logVoiceChannelLeave: mapToPublicFn(logVoiceChannelLeave), + logVoiceChannelMove: mapToPublicFn(logVoiceChannelMove), + logDmFailed: mapToPublicFn(logDmFailed), }, beforeLoad(pluginData) { diff --git a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts index a6aa8c64..dc3a5a8a 100644 --- a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts @@ -1,14 +1,17 @@ import { LogType } from "../../../data/LogType"; import { differenceToString, getScalarDifference } from "../../../utils"; -import { channelToConfigAccessibleChannel } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; +import { logChannelCreate } from "../logFunctions/logChannelCreate"; +import { logChannelDelete } from "../logFunctions/logChannelDelete"; +import { logChannelUpdate } from "../logFunctions/logChannelUpdate"; export const LogsChannelCreateEvt = logsEvt({ event: "channelCreate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.CHANNEL_CREATE, { - channel: channelToConfigAccessibleChannel(meta.args.channel), + logChannelCreate(meta.pluginData, { + channel: meta.args.channel, }); }, }); @@ -17,8 +20,8 @@ export const LogsChannelDeleteEvt = logsEvt({ event: "channelDelete", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.CHANNEL_DELETE, { - channel: channelToConfigAccessibleChannel(meta.args.channel), + logChannelDelete(meta.pluginData, { + channel: meta.args.channel, }); }, }); @@ -30,14 +33,10 @@ export const LogsChannelUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldChannel, meta.args.newChannel); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log( - LogType.CHANNEL_UPDATE, - { - oldChannel: channelToConfigAccessibleChannel(meta.args.oldChannel), - newChannel: channelToConfigAccessibleChannel(meta.args.newChannel), - differenceString, - }, - meta.args.newChannel.id, - ); + logChannelUpdate(meta.pluginData, { + oldChannel: meta.args.oldChannel, + newChannel: meta.args.newChannel, + differenceString, + }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts index 4f8ce433..25a157a2 100644 --- a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts @@ -1,18 +1,18 @@ -import { LogType } from "../../../data/LogType"; import { differenceToString, getScalarDifference } from "../../../utils"; -import { - channelToConfigAccessibleChannel, - emojiToConfigAccessibleEmoji, - stickerToConfigAccessibleSticker, -} from "../../../utils/configAccessibleObjects"; import { logsEvt } from "../types"; +import { logEmojiCreate } from "../logFunctions/logEmojiCreate"; +import { logEmojiDelete } from "../logFunctions/logEmojiDelete"; +import { logEmojiUpdate } from "../logFunctions/logEmojiUpdate"; +import { logStickerCreate } from "../logFunctions/logStickerCreate"; +import { logStickerDelete } from "../logFunctions/logStickerDelete"; +import { logStickerUpdate } from "../logFunctions/logStickerUpdate"; export const LogsEmojiCreateEvt = logsEvt({ event: "emojiCreate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.EMOJI_CREATE, { - emoji: emojiToConfigAccessibleEmoji(meta.args.emoji), + logEmojiCreate(meta.pluginData, { + emoji: meta.args.emoji, }); }, }); @@ -21,8 +21,8 @@ export const LogsEmojiDeleteEvt = logsEvt({ event: "emojiDelete", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.EMOJI_DELETE, { - emoji: emojiToConfigAccessibleEmoji(meta.args.emoji), + logEmojiDelete(meta.pluginData, { + emoji: meta.args.emoji, }); }, }); @@ -34,9 +34,9 @@ export const LogsEmojiUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldEmoji, meta.args.newEmoji); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log(LogType.EMOJI_UPDATE, { - oldEmoji: emojiToConfigAccessibleEmoji(meta.args.oldEmoji), - newEmoji: emojiToConfigAccessibleEmoji(meta.args.newEmoji), + logEmojiUpdate(meta.pluginData, { + oldEmoji: meta.args.oldEmoji, + newEmoji: meta.args.newEmoji, differenceString, }); }, @@ -46,8 +46,8 @@ export const LogsStickerCreateEvt = logsEvt({ event: "stickerCreate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.STICKER_CREATE, { - sticker: stickerToConfigAccessibleSticker(meta.args.sticker), + logStickerCreate(meta.pluginData, { + sticker: meta.args.sticker, }); }, }); @@ -56,8 +56,8 @@ export const LogsStickerDeleteEvt = logsEvt({ event: "stickerDelete", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.STICKER_DELETE, { - sticker: stickerToConfigAccessibleSticker(meta.args.sticker), + logStickerDelete(meta.pluginData, { + sticker: meta.args.sticker, }); }, }); @@ -69,14 +69,10 @@ export const LogsStickerUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldSticker, meta.args.newSticker); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log( - LogType.STICKER_UPDATE, - { - oldSticker: stickerToConfigAccessibleSticker(meta.args.oldSticker), - newSticker: stickerToConfigAccessibleSticker(meta.args.newSticker), - differenceString, - }, - meta.args.newSticker.id, - ); + logStickerUpdate(meta.pluginData, { + oldSticker: meta.args.oldSticker, + newSticker: meta.args.newSticker, + differenceString, + }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts b/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts index 580ce85e..f9baa897 100644 --- a/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts +++ b/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts @@ -1,8 +1,11 @@ import { GuildAuditLogs } from "discord.js"; import { LogType } from "../../../data/LogType"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; import { logsEvt } from "../types"; +import { logMemberBan } from "../logFunctions/logMemberBan"; +import { isLogIgnored } from "../util/isLogIgnored"; +import { logMemberUnban } from "../logFunctions/logMemberUnban"; export const LogsGuildBanAddEvt = logsEvt({ event: "guildBanAdd", @@ -11,21 +14,22 @@ export const LogsGuildBanAddEvt = logsEvt({ const pluginData = meta.pluginData; const user = meta.args.ban.user; + if (isLogIgnored(pluginData, LogType.MEMBER_BAN, user.id)) { + return; + } + const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, GuildAuditLogs.Actions.MEMBER_BAN_ADD as number, user.id, ); const mod = relevantAuditLogEntry?.executor ?? null; - - pluginData.state.guildLogs.log( - LogType.MEMBER_BAN, - { - mod: mod ? userToConfigAccessibleUser(mod) : {}, - user: userToConfigAccessibleUser(user), - }, - user.id, - ); + logMemberBan(meta.pluginData, { + mod, + user, + caseNumber: 0, + reason: "", + }); }, }); @@ -36,6 +40,10 @@ export const LogsGuildBanRemoveEvt = logsEvt({ const pluginData = meta.pluginData; const user = meta.args.ban.user; + if (isLogIgnored(pluginData, LogType.MEMBER_UNBAN, user.id)) { + return; + } + const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, GuildAuditLogs.Actions.MEMBER_BAN_REMOVE as number, @@ -43,13 +51,11 @@ export const LogsGuildBanRemoveEvt = logsEvt({ ); const mod = relevantAuditLogEntry?.executor ?? null; - pluginData.state.guildLogs.log( - LogType.MEMBER_UNBAN, - { - mod: mod ? userToConfigAccessibleUser(mod) : {}, - userId: user.id, - }, - user.id, - ); + logMemberUnban(pluginData, { + mod, + userId: user.id, + caseNumber: 0, + reason: "", + }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts index 099c3cb3..60d7036a 100644 --- a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts +++ b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts @@ -1,9 +1,11 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; import { LogType } from "../../../data/LogType"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { logsEvt } from "../types"; +import { logMemberJoin } from "../logFunctions/logMemberJoin"; +import { logMemberJoinWithPriorRecords } from "../logFunctions/logMemberJoinWithPriorRecords"; export const LogsGuildMemberAddEvt = logsEvt({ event: "guildMemberAdd", @@ -12,16 +14,8 @@ export const LogsGuildMemberAddEvt = logsEvt({ const pluginData = meta.pluginData; const member = meta.args.member; - const newThreshold = moment.utc().valueOf() - 1000 * 60 * 60; - const accountAge = humanizeDuration(moment.utc().valueOf() - member.user.createdTimestamp, { - largest: 2, - round: true, - }); - - pluginData.state.guildLogs.log(LogType.MEMBER_JOIN, { - member: memberToConfigAccessibleMember(member), - new: member.user.createdTimestamp >= newThreshold ? " :new:" : "", - account_age: accountAge, + logMemberJoin(pluginData, { + member, }); const cases = (await pluginData.state.cases.with("notes").getByUserId(member.id)).filter(c => !c.is_hidden); @@ -45,8 +39,8 @@ export const LogsGuildMemberAddEvt = logsEvt({ } } - pluginData.state.guildLogs.log(LogType.MEMBER_JOIN_WITH_PRIOR_RECORDS, { - member: memberToConfigAccessibleMember(member), + logMemberJoinWithPriorRecords(pluginData, { + member, recentCaseSummary, }); } diff --git a/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts b/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts index 8b18516d..289372e1 100644 --- a/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts +++ b/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts @@ -1,13 +1,14 @@ import { LogType } from "../../../data/LogType"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; +import { logMemberLeave } from "../logFunctions/logMemberLeave"; export const LogsGuildMemberRemoveEvt = logsEvt({ event: "guildMemberRemove", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.MEMBER_LEAVE, { - member: memberToConfigAccessibleMember(meta.args.member), + logMemberLeave(meta.pluginData, { + member: meta.args.member, }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts index e9ad1b07..0fea7c3f 100644 --- a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts @@ -1,14 +1,17 @@ import { LogType } from "../../../data/LogType"; import { differenceToString, getScalarDifference } from "../../../utils"; -import { roleToConfigAccessibleRole } from "../../../utils/configAccessibleObjects"; +import { roleToTemplateSafeRole } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; +import { logRoleCreate } from "../logFunctions/logRoleCreate"; +import { logRoleDelete } from "../logFunctions/logRoleDelete"; +import { logRoleUpdate } from "../logFunctions/logRoleUpdate"; export const LogsRoleCreateEvt = logsEvt({ event: "roleCreate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.ROLE_CREATE, { - role: roleToConfigAccessibleRole(meta.args.role), + logRoleCreate(meta.pluginData, { + role: meta.args.role, }); }, }); @@ -17,8 +20,8 @@ export const LogsRoleDeleteEvt = logsEvt({ event: "roleDelete", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.ROLE_DELETE, { - role: roleToConfigAccessibleRole(meta.args.role), + logRoleDelete(meta.pluginData, { + role: meta.args.role, }); }, }); @@ -30,9 +33,9 @@ export const LogsRoleUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldRole, meta.args.newRole); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log(LogType.ROLE_UPDATE, { - newRole: roleToConfigAccessibleRole(meta.args.newRole), - oldRole: roleToConfigAccessibleRole(meta.args.oldRole), + logRoleUpdate(meta.pluginData, { + newRole: meta.args.newRole, + oldRole: meta.args.oldRole, differenceString, }); }, diff --git a/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts index b2cb0989..d964c672 100644 --- a/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts @@ -1,7 +1,11 @@ import { LogType } from "../../../data/LogType"; import { differenceToString, getScalarDifference } from "../../../utils"; -import { channelToConfigAccessibleChannel, stageToConfigAccessibleStage } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; +import { logStageInstanceCreate } from "../logFunctions/logStageInstanceCreate"; +import { StageChannel } from "discord.js"; +import { logStageInstanceDelete } from "../logFunctions/logStageInstanceDelete"; +import { logStageInstanceUpdate } from "../logFunctions/logStageInstanceUpdate"; export const LogsStageInstanceCreateEvt = logsEvt({ event: "stageInstanceCreate", @@ -9,11 +13,11 @@ export const LogsStageInstanceCreateEvt = logsEvt({ async listener(meta) { const stageChannel = meta.args.stageInstance.channel ?? - (await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId))!; + ((await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId)) as StageChannel); - meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_CREATE, { - stageInstance: stageToConfigAccessibleStage(meta.args.stageInstance), - stageChannel: channelToConfigAccessibleChannel(stageChannel), + logStageInstanceCreate(meta.pluginData, { + stageInstance: meta.args.stageInstance, + stageChannel, }); }, }); @@ -24,11 +28,11 @@ export const LogsStageInstanceDeleteEvt = logsEvt({ async listener(meta) { const stageChannel = meta.args.stageInstance.channel ?? - (await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId))!; + ((await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId)) as StageChannel); - meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_DELETE, { - stageInstance: stageToConfigAccessibleStage(meta.args.stageInstance), - stageChannel: channelToConfigAccessibleChannel(stageChannel), + logStageInstanceDelete(meta.pluginData, { + stageInstance: meta.args.stageInstance, + stageChannel, }); }, }); @@ -39,15 +43,15 @@ export const LogsStageInstanceUpdateEvt = logsEvt({ async listener(meta) { const stageChannel = meta.args.newStageInstance.channel ?? - (await meta.pluginData.guild.channels.fetch(meta.args.newStageInstance.channelId))!; + ((await meta.pluginData.guild.channels.fetch(meta.args.newStageInstance.channelId)) as StageChannel); const diff = getScalarDifference(meta.args.oldStageInstance, meta.args.newStageInstance); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_UPDATE, { - oldStageInstance: stageToConfigAccessibleStage(meta.args.oldStageInstance), - newStageInstance: stageToConfigAccessibleStage(meta.args.newStageInstance), - stageChannel: channelToConfigAccessibleChannel(stageChannel), + logStageInstanceUpdate(meta.pluginData, { + oldStageInstance: meta.args.oldStageInstance, + newStageInstance: meta.args.newStageInstance, + stageChannel, differenceString, }); }, diff --git a/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts index f20c15e4..7fb3dcf6 100644 --- a/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts @@ -1,14 +1,17 @@ import { LogType } from "../../../data/LogType"; import { differenceToString, getScalarDifference } from "../../../utils"; -import { channelToConfigAccessibleChannel } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; +import { logThreadCreate } from "../logFunctions/logThreadCreate"; +import { logThreadDelete } from "../logFunctions/logThreadDelete"; +import { logThreadUpdate } from "../logFunctions/logThreadUpdate"; export const LogsThreadCreateEvt = logsEvt({ event: "threadCreate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.THREAD_CREATE, { - thread: channelToConfigAccessibleChannel(meta.args.thread), + logThreadCreate(meta.pluginData, { + thread: meta.args.thread, }); }, }); @@ -17,8 +20,8 @@ export const LogsThreadDeleteEvt = logsEvt({ event: "threadDelete", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.THREAD_DELETE, { - thread: channelToConfigAccessibleChannel(meta.args.thread), + logThreadDelete(meta.pluginData, { + thread: meta.args.thread, }); }, }); @@ -30,14 +33,10 @@ export const LogsThreadUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldThread, meta.args.newThread, ["messageCount", "archiveTimestamp"]); const differenceString = differenceToString(diff); - meta.pluginData.state.guildLogs.log( - LogType.THREAD_UPDATE, - { - oldThread: channelToConfigAccessibleChannel(meta.args.oldThread), - newThread: channelToConfigAccessibleChannel(meta.args.newThread), - differenceString, - }, - meta.args.newThread.id, - ); + logThreadUpdate(meta.pluginData, { + oldThread: meta.args.oldThread, + newThread: meta.args.newThread, + differenceString, + }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index a5f7e9c7..640a62ca 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -1,10 +1,14 @@ import { GuildAuditLogs } from "discord.js"; import diff from "lodash.difference"; import isEqual from "lodash.isequal"; -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; import { logsEvt } from "../types"; +import { logMemberNickChange } from "../logFunctions/logMemberNickChange"; +import { logMemberRoleChanges } from "../logFunctions/logMemberRoleChanges"; +import { logMemberRoleAdd } from "../logFunctions/logMemberRoleAdd"; +import { logMemberRoleRemove } from "../logFunctions/logMemberRoleRemove"; export const LogsGuildMemberUpdateEvt = logsEvt({ event: "guildMemberUpdate", @@ -16,11 +20,9 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ if (!oldMember) return; - const logMember = memberToConfigAccessibleMember(member); - if (member.nickname !== oldMember.nickname) { - pluginData.state.guildLogs.log(LogType.MEMBER_NICK_CHANGE, { - member: logMember, + logMemberNickChange(pluginData, { + member, oldNick: oldMember.nickname != null ? oldMember.nickname : "", newNick: member.nickname != null ? member.nickname : "", }); @@ -56,50 +58,38 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ if (addedRoles.length && removedRoles.length) { // Roles added *and* removed - pluginData.state.guildLogs.log( - LogType.MEMBER_ROLE_CHANGES, - { - member: logMember, - addedRoles: addedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), - removedRoles: removedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), - mod: mod ? userToConfigAccessibleUser(mod) : {}, - }, - member.id, - ); + logMemberRoleChanges(pluginData, { + member, + addedRoles: addedRoles + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) + .map(r => r.name) + .join(", "), + removedRoles: removedRoles + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) + .map(r => r.name) + .join(", "), + mod, + }); } else if (addedRoles.length) { // Roles added - pluginData.state.guildLogs.log( - LogType.MEMBER_ROLE_ADD, - { - member: logMember, - roles: addedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), - mod: mod ? userToConfigAccessibleUser(mod) : {}, - }, - member.id, - ); + logMemberRoleAdd(pluginData, { + member, + roles: addedRoles + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) + .map(r => r.name) + .join(", "), + mod, + }); } else if (removedRoles.length && !addedRoles.length) { // Roles removed - pluginData.state.guildLogs.log( - LogType.MEMBER_ROLE_REMOVE, - { - member: logMember, - roles: removedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), - mod: mod ? userToConfigAccessibleUser(mod) : {}, - }, - member.id, - ); + logMemberRoleRemove(pluginData, { + member, + roles: removedRoles + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) + .map(r => r.name) + .join(", "), + mod, + }); } } } diff --git a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts index 935c7847..bdb4b1a4 100644 --- a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts +++ b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts @@ -1,9 +1,9 @@ -import { - channelToConfigAccessibleChannel, - memberToConfigAccessibleMember, -} from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logsEvt } from "../types"; +import { logVoiceChannelLeave } from "../logFunctions/logVoiceChannelLeave"; +import { logVoiceChannelJoin } from "../logFunctions/logVoiceChannelJoin"; +import { logVoiceChannelMove } from "../logFunctions/logVoiceChannelMove"; export const LogsVoiceStateUpdateEvt = logsEvt({ event: "voiceStateUpdate", @@ -15,21 +15,21 @@ export const LogsVoiceStateUpdateEvt = logsEvt({ if (!newChannel && oldChannel) { // Leave evt - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, { - member: memberToConfigAccessibleMember(member), - channel: channelToConfigAccessibleChannel(oldChannel!), + logVoiceChannelLeave(meta.pluginData, { + member, + channel: oldChannel, }); } else if (!oldChannel && newChannel) { // Join Evt - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, { - member: memberToConfigAccessibleMember(member), - channel: channelToConfigAccessibleChannel(newChannel), + logVoiceChannelJoin(meta.pluginData, { + member, + channel: newChannel, }); - } else { - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, { - member: memberToConfigAccessibleMember(member), - oldChannel: channelToConfigAccessibleChannel(oldChannel!), - newChannel: channelToConfigAccessibleChannel(newChannel!), + } else if (oldChannel && newChannel) { + logVoiceChannelMove(meta.pluginData, { + member, + oldChannel, + newChannel, }); } }, diff --git a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts new file mode 100644 index 00000000..1874f24a --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts @@ -0,0 +1,33 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogAutomodActionData { + rule: string; + user: User; + users: User[]; + actionsTaken: string; + matchSummary: string; +} + +export function logAutomodAction(pluginData: GuildPluginData, data: LogAutomodActionData) { + return log( + pluginData, + LogType.AUTOMOD_ACTION, + createTypedTemplateSafeValueContainer({ + rule: data.rule, + user: userToTemplateSafeUser(data.user), + users: data.users.map(user => userToTemplateSafeUser(user)), + actionsTaken: data.actionsTaken, + matchSummary: data.matchSummary, + }), + { + userId: data.user.id, + bot: data.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logBotAlert.ts b/backend/src/plugins/Logs/logFunctions/logBotAlert.ts new file mode 100644 index 00000000..9f47dea0 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logBotAlert.ts @@ -0,0 +1,20 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; + +interface LogBotAlertData { + body: string; +} + +export function logBotAlert(pluginData: GuildPluginData, data: LogBotAlertData) { + return log( + pluginData, + LogType.BOT_ALERT, + createTypedTemplateSafeValueContainer({ + body: data.body, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logCaseCreate.ts b/backend/src/plugins/Logs/logFunctions/logCaseCreate.ts new file mode 100644 index 00000000..412dc8fa --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logCaseCreate.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogCaseCreateData { + mod: User; + userId: string; + caseNum: number; + caseType: string; + reason: string; +} + +export function logCaseCreate(pluginData: GuildPluginData, data: LogCaseCreateData) { + return log( + pluginData, + LogType.CASE_CREATE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + userId: data.userId, + caseNum: data.caseNum, + caseType: data.caseType, + reason: data.reason, + }), + { + userId: data.userId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logCaseDelete.ts b/backend/src/plugins/Logs/logFunctions/logCaseDelete.ts new file mode 100644 index 00000000..85c2e0d8 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logCaseDelete.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { caseToTemplateSafeCase, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import { Case } from "../../../data/entities/Case"; + +interface LogCaseDeleteData { + mod: GuildMember; + case: Case; +} + +export function logCaseDelete(pluginData: GuildPluginData, data: LogCaseDeleteData) { + return log( + pluginData, + LogType.CASE_DELETE, + createTypedTemplateSafeValueContainer({ + mod: memberToTemplateSafeMember(data.mod), + case: caseToTemplateSafeCase(data.case), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logCaseUpdate.ts b/backend/src/plugins/Logs/logFunctions/logCaseUpdate.ts new file mode 100644 index 00000000..d8db4499 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logCaseUpdate.ts @@ -0,0 +1,28 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogCaseUpdateData { + mod: User; + caseNumber: number; + caseType: string; + note: string; +} + +export function logCaseUpdate(pluginData: GuildPluginData, data: LogCaseUpdateData) { + return log( + pluginData, + LogType.CASE_UPDATE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + caseNumber: data.caseNumber, + caseType: data.caseType, + note: data.note, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logCensor.ts b/backend/src/plugins/Logs/logFunctions/logCensor.ts new file mode 100644 index 00000000..2385273f --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logCensor.ts @@ -0,0 +1,41 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + savedMessageToTemplateSafeSavedMessage, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { UnknownUser } from "../../../utils"; +import { deactivateMentions, disableCodeBlocks } from "knub/dist/helpers"; + +interface LogCensorData { + user: User | UnknownUser; + channel: BaseGuildTextChannel | ThreadChannel; + reason: string; + message: SavedMessage; +} + +export function logCensor(pluginData: GuildPluginData, data: LogCensorData) { + return log( + pluginData, + LogType.CENSOR, + createTypedTemplateSafeValueContainer({ + user: userToTemplateSafeUser(data.user), + channel: channelToTemplateSafeChannel(data.channel), + reason: data.reason, + message: savedMessageToTemplateSafeSavedMessage(data.message), + messageText: disableCodeBlocks(deactivateMentions(data.message.data.content)), + }), + { + userId: data.user.id, + channel: data.channel.id, + category: data.channel.parentId, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts b/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts new file mode 100644 index 00000000..02ccba3d --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildChannel, NewsChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogChannelCreateData { + channel: GuildChannel | NewsChannel; +} + +export function logChannelCreate(pluginData: GuildPluginData, data: LogChannelCreateData) { + return log( + pluginData, + LogType.CHANNEL_CREATE, + createTypedTemplateSafeValueContainer({ + channel: channelToTemplateSafeChannel(data.channel), + }), + { + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts b/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts new file mode 100644 index 00000000..547ed963 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildChannel, NewsChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogChannelDeleteData { + channel: GuildChannel | NewsChannel; +} + +export function logChannelDelete(pluginData: GuildPluginData, data: LogChannelDeleteData) { + return log( + pluginData, + LogType.CHANNEL_DELETE, + createTypedTemplateSafeValueContainer({ + channel: channelToTemplateSafeChannel(data.channel), + }), + { + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts b/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts new file mode 100644 index 00000000..ebe2ceb5 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts @@ -0,0 +1,29 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildChannel, NewsChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogChannelUpdateData { + oldChannel: GuildChannel | NewsChannel; + newChannel: GuildChannel | NewsChannel; + differenceString: string; +} + +export function logChannelUpdate(pluginData: GuildPluginData, data: LogChannelUpdateData) { + return log( + pluginData, + LogType.CHANNEL_UPDATE, + createTypedTemplateSafeValueContainer({ + oldChannel: channelToTemplateSafeChannel(data.oldChannel), + newChannel: channelToTemplateSafeChannel(data.newChannel), + differenceString: data.differenceString, + }), + { + channel: data.newChannel.id, + category: data.newChannel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logClean.ts b/backend/src/plugins/Logs/logFunctions/logClean.ts new file mode 100644 index 00000000..4ef2077e --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logClean.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogCleanData { + mod: User; + channel: BaseGuildTextChannel; + count: number; + archiveUrl: string; +} + +export function logClean(pluginData: GuildPluginData, data: LogCleanData) { + return log( + pluginData, + LogType.CLEAN, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + channel: channelToTemplateSafeChannel(data.channel), + count: data.count, + archiveUrl: data.archiveUrl, + }), + { + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logDmFailed.ts b/backend/src/plugins/Logs/logFunctions/logDmFailed.ts new file mode 100644 index 00000000..32ba3bde --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logDmFailed.ts @@ -0,0 +1,28 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { UnknownUser } from "../../../utils"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogDmFailedData { + source: string; + user: User | UnknownUser; +} + +export function logDmFailed(pluginData: GuildPluginData, data: LogDmFailedData) { + return log( + pluginData, + LogType.DM_FAILED, + createTypedTemplateSafeValueContainer({ + source: data.source, + user: userToTemplateSafeUser(data.user), + }), + { + userId: data.user.id, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logEmojiCreate.ts b/backend/src/plugins/Logs/logFunctions/logEmojiCreate.ts new file mode 100644 index 00000000..d49d940b --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logEmojiCreate.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Emoji } from "discord.js"; +import { emojiToTemplateSafeEmoji } from "../../../utils/templateSafeObjects"; + +interface LogEmojiCreateData { + emoji: Emoji; +} + +export function logEmojiCreate(pluginData: GuildPluginData, data: LogEmojiCreateData) { + return log( + pluginData, + LogType.EMOJI_CREATE, + createTypedTemplateSafeValueContainer({ + emoji: emojiToTemplateSafeEmoji(data.emoji), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logEmojiDelete.ts b/backend/src/plugins/Logs/logFunctions/logEmojiDelete.ts new file mode 100644 index 00000000..6f5b717c --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logEmojiDelete.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Emoji } from "discord.js"; +import { emojiToTemplateSafeEmoji } from "../../../utils/templateSafeObjects"; + +interface LogEmojiDeleteData { + emoji: Emoji; +} + +export function logEmojiDelete(pluginData: GuildPluginData, data: LogEmojiDeleteData) { + return log( + pluginData, + LogType.EMOJI_DELETE, + createTypedTemplateSafeValueContainer({ + emoji: emojiToTemplateSafeEmoji(data.emoji), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logEmojiUpdate.ts b/backend/src/plugins/Logs/logFunctions/logEmojiUpdate.ts new file mode 100644 index 00000000..9934a045 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logEmojiUpdate.ts @@ -0,0 +1,26 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Emoji } from "discord.js"; +import { emojiToTemplateSafeEmoji } from "../../../utils/templateSafeObjects"; + +interface LogEmojiUpdateData { + oldEmoji: Emoji; + newEmoji: Emoji; + differenceString: string; +} + +export function logEmojiUpdate(pluginData: GuildPluginData, data: LogEmojiUpdateData) { + return log( + pluginData, + LogType.EMOJI_UPDATE, + createTypedTemplateSafeValueContainer({ + oldEmoji: emojiToTemplateSafeEmoji(data.oldEmoji), + newEmoji: emojiToTemplateSafeEmoji(data.newEmoji), + differenceString: data.differenceString, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMassBan.ts b/backend/src/plugins/Logs/logFunctions/logMassBan.ts new file mode 100644 index 00000000..e920fe03 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMassBan.ts @@ -0,0 +1,26 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMassBanData { + mod: User; + count: number; + reason: string; +} + +export function logMassBan(pluginData: GuildPluginData, data: LogMassBanData) { + return log( + pluginData, + LogType.MASSBAN, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + count: data.count, + reason: data.reason, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMassMute.ts b/backend/src/plugins/Logs/logFunctions/logMassMute.ts new file mode 100644 index 00000000..f7a510a9 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMassMute.ts @@ -0,0 +1,24 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMassMuteData { + mod: User; + count: number; +} + +export function logMassMute(pluginData: GuildPluginData, data: LogMassMuteData) { + return log( + pluginData, + LogType.MASSMUTE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + count: data.count, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMassUnban.ts b/backend/src/plugins/Logs/logFunctions/logMassUnban.ts new file mode 100644 index 00000000..138c5be9 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMassUnban.ts @@ -0,0 +1,26 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMassUnbanData { + mod: User; + count: number; + reason: string; +} + +export function logMassUnban(pluginData: GuildPluginData, data: LogMassUnbanData) { + return log( + pluginData, + LogType.MASSUNBAN, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + count: data.count, + reason: data.reason, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberBan.ts b/backend/src/plugins/Logs/logFunctions/logMemberBan.ts new file mode 100644 index 00000000..21777c2b --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberBan.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberBanData { + mod: User | UnknownUser | null; + user: User | UnknownUser; + caseNumber: number; + reason: string; +} + +export function logMemberBan(pluginData: GuildPluginData, data: LogMemberBanData) { + return log( + pluginData, + LogType.MEMBER_BAN, + createTypedTemplateSafeValueContainer({ + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, + user: userToTemplateSafeUser(data.user), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberForceban.ts b/backend/src/plugins/Logs/logFunctions/logMemberForceban.ts new file mode 100644 index 00000000..2f9acaa3 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberForceban.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, Snowflake } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberForcebanData { + mod: GuildMember; + userId: Snowflake; + caseNumber: number; + reason: string; +} + +export function logMemberForceban(pluginData: GuildPluginData, data: LogMemberForcebanData) { + return log( + pluginData, + LogType.MEMBER_FORCEBAN, + createTypedTemplateSafeValueContainer({ + mod: memberToTemplateSafeMember(data.mod), + userId: data.userId, + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.userId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberJoin.ts b/backend/src/plugins/Logs/logFunctions/logMemberJoin.ts new file mode 100644 index 00000000..0daf7a33 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberJoin.ts @@ -0,0 +1,35 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import moment from "moment-timezone"; +import humanizeDuration from "humanize-duration"; + +interface LogMemberJoinData { + member: GuildMember; +} + +export function logMemberJoin(pluginData: GuildPluginData, data: LogMemberJoinData) { + const newThreshold = moment.utc().valueOf() - 1000 * 60 * 60; + const accountAge = humanizeDuration(moment.utc().valueOf() - data.member.user.createdTimestamp, { + largest: 2, + round: true, + }); + + return log( + pluginData, + LogType.MEMBER_JOIN, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + new: data.member.user.createdTimestamp >= newThreshold ? " :new:" : "", + account_age: accountAge, + }), + { + userId: data.member.id, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberJoinWithPriorRecords.ts b/backend/src/plugins/Logs/logFunctions/logMemberJoinWithPriorRecords.ts new file mode 100644 index 00000000..92ca6cb7 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberJoinWithPriorRecords.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberJoinWithPriorRecordsData { + member: GuildMember; + recentCaseSummary: string; +} + +export function logMemberJoinWithPriorRecords( + pluginData: GuildPluginData, + data: LogMemberJoinWithPriorRecordsData, +) { + return log( + pluginData, + LogType.MEMBER_JOIN_WITH_PRIOR_RECORDS, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + recentCaseSummary: data.recentCaseSummary, + }), + { + userId: data.member.id, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberKick.ts b/backend/src/plugins/Logs/logFunctions/logMemberKick.ts new file mode 100644 index 00000000..704b8769 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberKick.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberKickData { + mod: User | UnknownUser | null; + user: User; + caseNumber: number; + reason: string; +} + +export function logMemberKick(pluginData: GuildPluginData, data: LogMemberKickData) { + return log( + pluginData, + LogType.MEMBER_KICK, + createTypedTemplateSafeValueContainer({ + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, + user: userToTemplateSafeUser(data.user), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberLeave.ts b/backend/src/plugins/Logs/logFunctions/logMemberLeave.ts new file mode 100644 index 00000000..05460a20 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberLeave.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, PartialGuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberLeaveData { + member: GuildMember | PartialGuildMember; +} + +export function logMemberLeave(pluginData: GuildPluginData, data: LogMemberLeaveData) { + return log( + pluginData, + LogType.MEMBER_LEAVE, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + }), + { + userId: data.member.id, + bot: data.member.user?.bot ?? false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberMute.ts b/backend/src/plugins/Logs/logFunctions/logMemberMute.ts new file mode 100644 index 00000000..c99fd300 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberMute.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberMuteData { + mod: User | UnknownUser; + user: User | UnknownUser; + caseNumber: number; + reason: string; +} + +export function logMemberMute(pluginData: GuildPluginData, data: LogMemberMuteData) { + return log( + pluginData, + LogType.MEMBER_MUTE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + user: userToTemplateSafeUser(data.user), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts b/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts new file mode 100644 index 00000000..be203de7 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts @@ -0,0 +1,40 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { + memberToTemplateSafeMember, + TemplateSafeUnknownMember, + TemplateSafeUnknownUser, +} from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberMuteExpiredData { + member: GuildMember | UnknownUser; +} + +export function logMemberMuteExpired(pluginData: GuildPluginData, data: LogMemberMuteExpiredData) { + const member = + data.member instanceof GuildMember + ? memberToTemplateSafeMember(data.member) + : new TemplateSafeUnknownMember({ ...data.member, user: new TemplateSafeUnknownUser({ ...data.member }) }); + + const roles = data.member instanceof GuildMember ? Array.from(data.member.roles.cache.keys()) : []; + + const bot = data.member instanceof GuildMember ? data.member.user.bot : false; + + return log( + pluginData, + LogType.MEMBER_MUTE_EXPIRED, + createTypedTemplateSafeValueContainer({ + member, + }), + { + userId: data.member.id, + roles, + bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberMuteRejoin.ts b/backend/src/plugins/Logs/logFunctions/logMemberMuteRejoin.ts new file mode 100644 index 00000000..fcd9a641 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberMuteRejoin.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberMuteRejoinData { + member: GuildMember; +} + +export function logMemberMuteRejoin(pluginData: GuildPluginData, data: LogMemberMuteRejoinData) { + return log( + pluginData, + LogType.MEMBER_MUTE_REJOIN, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + }), + { + userId: data.member.id, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberNickChange.ts b/backend/src/plugins/Logs/logFunctions/logMemberNickChange.ts new file mode 100644 index 00000000..ddab9b08 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberNickChange.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberNickChangeData { + member: GuildMember; + oldNick: string; + newNick: string; +} + +export function logMemberNickChange(pluginData: GuildPluginData, data: LogMemberNickChangeData) { + return log( + pluginData, + LogType.MEMBER_NICK_CHANGE, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + oldNick: data.oldNick, + newNick: data.newNick, + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberNote.ts b/backend/src/plugins/Logs/logFunctions/logMemberNote.ts new file mode 100644 index 00000000..4f05c9a9 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberNote.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberNoteData { + mod: User; + user: User | UnknownUser; + caseNumber: number; + reason: string; +} + +export function logMemberNote(pluginData: GuildPluginData, data: LogMemberNoteData) { + return log( + pluginData, + LogType.MEMBER_NOTE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + user: userToTemplateSafeUser(data.user), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRestore.ts b/backend/src/plugins/Logs/logFunctions/logMemberRestore.ts new file mode 100644 index 00000000..d6eab53f --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberRestore.ts @@ -0,0 +1,28 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberRestoreData { + member: GuildMember; + restoredData: string; +} + +export function logMemberRestore(pluginData: GuildPluginData, data: LogMemberRestoreData) { + return log( + pluginData, + LogType.MEMBER_RESTORE, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + restoredData: data.restoredData, + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts new file mode 100644 index 00000000..032e5781 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, Role, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMemberRoleAddData { + mod: User | null; + member: GuildMember; + roles: Role[]; +} + +export function logMemberRoleAdd(pluginData: GuildPluginData, data: LogMemberRoleAddData) { + return log( + pluginData, + LogType.MEMBER_ROLE_ADD, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + member: memberToTemplateSafeMember(data.member), + roles: data.roles.map(r => r.name).join(", "), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts new file mode 100644 index 00000000..bfc72ad0 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberRoleChangesData { + mod: User | UnknownUser | null; + member: GuildMember; + addedRoles: string; + removedRoles: string; +} + +export function logMemberRoleChanges(pluginData: GuildPluginData, data: LogMemberRoleChangesData) { + return log( + pluginData, + LogType.MEMBER_ROLE_CHANGES, + createTypedTemplateSafeValueContainer({ + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, + member: memberToTemplateSafeMember(data.member), + addedRoles: data.addedRoles, + removedRoles: data.removedRoles, + }), + { + userId: data.member.id, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts new file mode 100644 index 00000000..98a7acdb --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, Role, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMemberRoleRemoveData { + mod: User | null; + member: GuildMember; + roles: Role[]; +} + +export function logMemberRoleRemove(pluginData: GuildPluginData, data: LogMemberRoleRemoveData) { + return log( + pluginData, + LogType.MEMBER_ROLE_REMOVE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + member: memberToTemplateSafeMember(data.member), + roles: data.roles.map(r => r.name).join(", "), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberTimedBan.ts b/backend/src/plugins/Logs/logFunctions/logMemberTimedBan.ts new file mode 100644 index 00000000..aa4c7846 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberTimedBan.ts @@ -0,0 +1,34 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberTimedBanData { + mod: User | UnknownUser; + user: User | UnknownUser; + banTime: string; + caseNumber: number; + reason: string; +} + +export function logMemberTimedBan(pluginData: GuildPluginData, data: LogMemberTimedBanData) { + return log( + pluginData, + LogType.MEMBER_TIMED_BAN, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + user: userToTemplateSafeUser(data.user), + banTime: data.banTime, + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberTimedMute.ts b/backend/src/plugins/Logs/logFunctions/logMemberTimedMute.ts new file mode 100644 index 00000000..70fd663c --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberTimedMute.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberTimedMuteData { + mod: User | UnknownUser; + user: User | UnknownUser; + time: string; + caseNumber: number; + reason: string; +} + +export function logMemberTimedMute(pluginData: GuildPluginData, data: LogMemberTimedMuteData) { + return log( + pluginData, + LogType.MEMBER_TIMED_MUTE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + user: userToTemplateSafeUser(data.user), + time: data.time, + caseNumber: data.caseNumber, + reason: data.reason, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberTimedUnban.ts b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnban.ts new file mode 100644 index 00000000..8716157b --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnban.ts @@ -0,0 +1,33 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberTimedUnbanData { + mod: User | UnknownUser; + userId: string; + banTime: string; + caseNumber: number; + reason: string; +} + +export function logMemberTimedUnban(pluginData: GuildPluginData, data: LogMemberTimedUnbanData) { + return log( + pluginData, + LogType.MEMBER_TIMED_UNBAN, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + userId: data.userId, + banTime: data.banTime, + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.userId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts new file mode 100644 index 00000000..be850eb8 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMemberTimedUnmuteData { + mod: User; + user: User; + time: string; + caseNumber: number; + reason: string; +} + +export function logMemberTimedUnmute(pluginData: GuildPluginData, data: LogMemberTimedUnmuteData) { + return log( + pluginData, + LogType.MEMBER_TIMED_UNMUTE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + user: userToTemplateSafeUser(data.user), + time: data.time, + caseNumber: data.caseNumber, + reason: data.reason, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberUnban.ts b/backend/src/plugins/Logs/logFunctions/logMemberUnban.ts new file mode 100644 index 00000000..d9702531 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberUnban.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, Snowflake, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; + +interface LogMemberUnbanData { + mod: User | UnknownUser | null; + userId: Snowflake; + caseNumber: number; + reason: string; +} + +export function logMemberUnban(pluginData: GuildPluginData, data: LogMemberUnbanData) { + return log( + pluginData, + LogType.MEMBER_UNBAN, + createTypedTemplateSafeValueContainer({ + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, + userId: data.userId, + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.userId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts b/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts new file mode 100644 index 00000000..a3b9b942 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember, User } from "discord.js"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogMemberUnmuteData { + mod: GuildMember; + user: User; + caseNumber: number; + reason: string; +} + +export function logMemberUnmute(pluginData: GuildPluginData, data: LogMemberUnmuteData) { + return log( + pluginData, + LogType.MEMBER_UNMUTE, + createTypedTemplateSafeValueContainer({ + mod: memberToTemplateSafeMember(data.mod), + user: userToTemplateSafeUser(data.user), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.user.id, + bot: data.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMemberWarn.ts b/backend/src/plugins/Logs/logFunctions/logMemberWarn.ts new file mode 100644 index 00000000..180582b1 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMemberWarn.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMemberWarnData { + mod: GuildMember; + member: GuildMember; + caseNumber: number; + reason: string; +} + +export function logMemberWarn(pluginData: GuildPluginData, data: LogMemberWarnData) { + return log( + pluginData, + LogType.MEMBER_WARN, + createTypedTemplateSafeValueContainer({ + mod: memberToTemplateSafeMember(data.mod), + member: memberToTemplateSafeMember(data.member), + caseNumber: data.caseNumber, + reason: data.reason, + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts new file mode 100644 index 00000000..1b752866 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts @@ -0,0 +1,56 @@ +import { GuildPluginData } from "knub"; +import { FORMAT_NO_TIMESTAMP, LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + savedMessageToTemplateSafeSavedMessage, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; +import moment from "moment-timezone"; +import { ISavedMessageAttachmentData, SavedMessage } from "../../../data/entities/SavedMessage"; +import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UnknownUser, useMediaUrls } from "../../../utils"; + +interface LogMessageDeleteData { + user: User | UnknownUser; + channel: BaseGuildTextChannel | ThreadChannel; + message: SavedMessage; +} + +export function logMessageDelete(pluginData: GuildPluginData, data: LogMessageDeleteData) { + // Replace attachment URLs with media URLs + if (data.message.data.attachments) { + for (const attachment of data.message.data.attachments as ISavedMessageAttachmentData[]) { + attachment.url = useMediaUrls(attachment.url); + } + } + + // See comment on FORMAT_NO_TIMESTAMP in types.ts + const config = pluginData.config.get(); + const timestampFormat = + (config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ?? config.timestamp_format; + + return log( + pluginData, + LogType.MESSAGE_DELETE, + createTypedTemplateSafeValueContainer({ + user: userToTemplateSafeUser(data.user), + channel: channelToTemplateSafeChannel(data.channel), + message: savedMessageToTemplateSafeSavedMessage(data.message), + messageDate: pluginData + .getPlugin(TimeAndDatePlugin) + .inGuildTz(moment.utc(data.message.data.timestamp, "x")) + .format(timestampFormat), + }), + { + userId: data.user.id, + channel: data.channel.id, + category: data.channel.parentId, + messageTextContent: data.message.data.content, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts new file mode 100644 index 00000000..ad8b9b72 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts @@ -0,0 +1,39 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + savedMessageToTemplateSafeSavedMessage, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { UnknownUser } from "../../../utils"; + +interface LogMessageDeleteAutoData { + message: SavedMessage; + user: User | UnknownUser; + channel: BaseGuildTextChannel; + messageDate: string; +} + +export function logMessageDeleteAuto(pluginData: GuildPluginData, data: LogMessageDeleteAutoData) { + return log( + pluginData, + LogType.MESSAGE_DELETE_AUTO, + createTypedTemplateSafeValueContainer({ + message: savedMessageToTemplateSafeSavedMessage(data.message), + user: userToTemplateSafeUser(data.user), + channel: channelToTemplateSafeChannel(data.channel), + messageDate: data.messageDate, + }), + { + userId: data.user.id, + bot: data.user.bot, + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts new file mode 100644 index 00000000..6b980951 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts @@ -0,0 +1,27 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogMessageDeleteBareData { + messageId: string; + channel: BaseGuildTextChannel | ThreadChannel; +} + +export function logMessageDeleteBare(pluginData: GuildPluginData, data: LogMessageDeleteBareData) { + return log( + pluginData, + LogType.MESSAGE_DELETE_BARE, + createTypedTemplateSafeValueContainer({ + messageId: data.messageId, + channel: channelToTemplateSafeChannel(data.channel), + }), + { + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts new file mode 100644 index 00000000..01f38279 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogMessageDeleteBulkData { + count: number; + authorIds: string[]; + channel: BaseGuildTextChannel | ThreadChannel; + archiveUrl: string; +} + +export function logMessageDeleteBulk(pluginData: GuildPluginData, data: LogMessageDeleteBulkData) { + return log( + pluginData, + LogType.MESSAGE_DELETE_BULK, + createTypedTemplateSafeValueContainer({ + count: data.count, + authorIds: data.authorIds, + channel: channelToTemplateSafeChannel(data.channel), + archiveUrl: data.archiveUrl, + }), + { + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts b/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts new file mode 100644 index 00000000..a77ce103 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts @@ -0,0 +1,39 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + savedMessageToTemplateSafeSavedMessage, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { UnknownUser } from "../../../utils"; + +interface LogMessageEditData { + user: User | UnknownUser; + channel: BaseGuildTextChannel | ThreadChannel; + before: SavedMessage; + after: SavedMessage; +} + +export function logMessageEdit(pluginData: GuildPluginData, data: LogMessageEditData) { + return log( + pluginData, + LogType.MESSAGE_EDIT, + createTypedTemplateSafeValueContainer({ + user: userToTemplateSafeUser(data.user), + channel: channelToTemplateSafeChannel(data.channel), + before: savedMessageToTemplateSafeSavedMessage(data.before), + after: savedMessageToTemplateSafeSavedMessage(data.after), + }), + { + userId: data.user.id, + channel: data.channel.id, + messageTextContent: data.after.data.content, + bot: data.user instanceof User ? data.user.bot : false, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts b/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts new file mode 100644 index 00000000..94e41eda --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts @@ -0,0 +1,38 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, GuildChannel, GuildMember, ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogMessageSpamDetectedData { + member: GuildMember; + channel: GuildChannel | ThreadChannel; + description: string; + limit: number; + interval: number; + archiveUrl: string; +} + +export function logMessageSpamDetected(pluginData: GuildPluginData, data: LogMessageSpamDetectedData) { + return log( + pluginData, + LogType.MESSAGE_SPAM_DETECTED, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + channel: channelToTemplateSafeChannel(data.channel), + description: data.description, + limit: data.limit, + interval: data.interval, + archiveUrl: data.archiveUrl, + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.channel.id, + category: data.channel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logOtherSpamDetected.ts b/backend/src/plugins/Logs/logFunctions/logOtherSpamDetected.ts new file mode 100644 index 00000000..d4c4d0c5 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logOtherSpamDetected.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { GuildMember } from "discord.js"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogOtherSpamDetectedData { + member: GuildMember; + description: string; + limit: number; + interval: number; +} + +export function logOtherSpamDetected(pluginData: GuildPluginData, data: LogOtherSpamDetectedData) { + return log( + pluginData, + LogType.OTHER_SPAM_DETECTED, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + description: data.description, + limit: data.limit, + interval: data.interval, + }), + { + userId: data.member.id, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logPostedScheduledMessage.ts b/backend/src/plugins/Logs/logFunctions/logPostedScheduledMessage.ts new file mode 100644 index 00000000..ee4ffd1e --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logPostedScheduledMessage.ts @@ -0,0 +1,34 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogPostedScheduledMessageData { + author: User; + channel: BaseGuildTextChannel; + messageId: string; +} + +export function logPostedScheduledMessage( + pluginData: GuildPluginData, + data: LogPostedScheduledMessageData, +) { + return log( + pluginData, + LogType.POSTED_SCHEDULED_MESSAGE, + createTypedTemplateSafeValueContainer({ + author: userToTemplateSafeUser(data.author), + channel: channelToTemplateSafeChannel(data.channel), + messageId: data.messageId, + }), + { + userId: data.author.id, + bot: data.author.bot, + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts b/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts new file mode 100644 index 00000000..70fdaf00 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts @@ -0,0 +1,39 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogRepeatedMessageData { + author: User; + channel: BaseGuildTextChannel; + datetime: string; + date: string; + time: string; + repeatInterval: string; + repeatDetails: string; +} + +export function logRepeatedMessage(pluginData: GuildPluginData, data: LogRepeatedMessageData) { + return log( + pluginData, + LogType.REPEATED_MESSAGE, + createTypedTemplateSafeValueContainer({ + author: userToTemplateSafeUser(data.author), + channel: channelToTemplateSafeChannel(data.channel), + datetime: data.datetime, + date: data.date, + time: data.time, + repeatInterval: data.repeatInterval, + repeatDetails: data.repeatDetails, + }), + { + userId: data.author.id, + bot: data.author.bot, + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logRoleCreate.ts b/backend/src/plugins/Logs/logFunctions/logRoleCreate.ts new file mode 100644 index 00000000..b4137bf2 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logRoleCreate.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Role } from "discord.js"; +import { roleToTemplateSafeRole } from "../../../utils/templateSafeObjects"; + +interface LogRoleCreateData { + role: Role; +} + +export function logRoleCreate(pluginData: GuildPluginData, data: LogRoleCreateData) { + return log( + pluginData, + LogType.ROLE_CREATE, + createTypedTemplateSafeValueContainer({ + role: roleToTemplateSafeRole(data.role), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logRoleDelete.ts b/backend/src/plugins/Logs/logFunctions/logRoleDelete.ts new file mode 100644 index 00000000..bce63794 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logRoleDelete.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Role } from "discord.js"; +import { roleToTemplateSafeRole } from "../../../utils/templateSafeObjects"; + +interface LogRoleDeleteData { + role: Role; +} + +export function logRoleDelete(pluginData: GuildPluginData, data: LogRoleDeleteData) { + return log( + pluginData, + LogType.ROLE_DELETE, + createTypedTemplateSafeValueContainer({ + role: roleToTemplateSafeRole(data.role), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logRoleUpdate.ts b/backend/src/plugins/Logs/logFunctions/logRoleUpdate.ts new file mode 100644 index 00000000..be36dfda --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logRoleUpdate.ts @@ -0,0 +1,26 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Role } from "discord.js"; +import { roleToTemplateSafeRole } from "../../../utils/templateSafeObjects"; + +interface LogRoleUpdateData { + oldRole: Role; + newRole: Role; + differenceString: string; +} + +export function logRoleUpdate(pluginData: GuildPluginData, data: LogRoleUpdateData) { + return log( + pluginData, + LogType.ROLE_UPDATE, + createTypedTemplateSafeValueContainer({ + oldRole: roleToTemplateSafeRole(data.oldRole), + newRole: roleToTemplateSafeRole(data.newRole), + differenceString: data.differenceString, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts new file mode 100644 index 00000000..64cce036 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts @@ -0,0 +1,35 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogScheduledMessageData { + author: User; + channel: BaseGuildTextChannel; + datetime: string; + date: string; + time: string; +} + +export function logScheduledMessage(pluginData: GuildPluginData, data: LogScheduledMessageData) { + return log( + pluginData, + LogType.SCHEDULED_MESSAGE, + createTypedTemplateSafeValueContainer({ + author: userToTemplateSafeUser(data.author), + channel: channelToTemplateSafeChannel(data.channel), + datetime: data.datetime, + date: data.date, + time: data.time, + }), + { + userId: data.author.id, + bot: data.author.bot, + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts new file mode 100644 index 00000000..d020188f --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts @@ -0,0 +1,42 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildTextChannel, User } from "discord.js"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogScheduledRepeatedMessageData { + author: User; + channel: BaseGuildTextChannel; + datetime: string; + date: string; + time: string; + repeatInterval: string; + repeatDetails: string; +} + +export function logScheduledRepeatedMessage( + pluginData: GuildPluginData, + data: LogScheduledRepeatedMessageData, +) { + return log( + pluginData, + LogType.SCHEDULED_REPEATED_MESSAGE, + createTypedTemplateSafeValueContainer({ + author: userToTemplateSafeUser(data.author), + channel: channelToTemplateSafeChannel(data.channel), + datetime: data.datetime, + date: data.date, + time: data.time, + repeatInterval: data.repeatInterval, + repeatDetails: data.repeatDetails, + }), + { + userId: data.author.id, + bot: data.author.bot, + channel: data.channel.id, + category: data.channel.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logSetAntiraidAuto.ts b/backend/src/plugins/Logs/logFunctions/logSetAntiraidAuto.ts new file mode 100644 index 00000000..5e3261a7 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logSetAntiraidAuto.ts @@ -0,0 +1,20 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; + +interface LogSetAntiraidAutoData { + level: string; +} + +export function logSetAntiraidAuto(pluginData: GuildPluginData, data: LogSetAntiraidAutoData) { + return log( + pluginData, + LogType.SET_ANTIRAID_AUTO, + createTypedTemplateSafeValueContainer({ + level: data.level, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logSetAntiraidUser.ts b/backend/src/plugins/Logs/logFunctions/logSetAntiraidUser.ts new file mode 100644 index 00000000..bd65a67d --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logSetAntiraidUser.ts @@ -0,0 +1,27 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { User } from "discord.js"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; + +interface LogSetAntiraidUserData { + level: string; + user: User; +} + +export function logSetAntiraidUser(pluginData: GuildPluginData, data: LogSetAntiraidUserData) { + return log( + pluginData, + LogType.SET_ANTIRAID_USER, + createTypedTemplateSafeValueContainer({ + level: data.level, + user: userToTemplateSafeUser(data.user), + }), + { + userId: data.user.id, + bot: data.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts b/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts new file mode 100644 index 00000000..f1602c3b --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts @@ -0,0 +1,27 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { StageChannel, StageInstance } from "discord.js"; +import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; + +interface LogStageInstanceCreateData { + stageInstance: StageInstance; + stageChannel: StageChannel; +} + +export function logStageInstanceCreate(pluginData: GuildPluginData, data: LogStageInstanceCreateData) { + return log( + pluginData, + LogType.STAGE_INSTANCE_CREATE, + createTypedTemplateSafeValueContainer({ + stageInstance: stageToTemplateSafeStage(data.stageInstance), + stageChannel: channelToTemplateSafeChannel(data.stageChannel), + }), + { + channel: data.stageInstance.channel!.id, + category: data.stageInstance.channel!.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStageInstanceDelete.ts b/backend/src/plugins/Logs/logFunctions/logStageInstanceDelete.ts new file mode 100644 index 00000000..bc2841b0 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStageInstanceDelete.ts @@ -0,0 +1,27 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { StageChannel, StageInstance } from "discord.js"; +import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; + +interface LogStageInstanceDeleteData { + stageInstance: StageInstance; + stageChannel: StageChannel; +} + +export function logStageInstanceDelete(pluginData: GuildPluginData, data: LogStageInstanceDeleteData) { + return log( + pluginData, + LogType.STAGE_INSTANCE_DELETE, + createTypedTemplateSafeValueContainer({ + stageInstance: stageToTemplateSafeStage(data.stageInstance), + stageChannel: channelToTemplateSafeChannel(data.stageChannel), + }), + { + channel: data.stageInstance.channel!.id, + category: data.stageInstance.channel!.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStageInstanceUpdate.ts b/backend/src/plugins/Logs/logFunctions/logStageInstanceUpdate.ts new file mode 100644 index 00000000..fe02db88 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStageInstanceUpdate.ts @@ -0,0 +1,31 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { StageChannel, StageInstance } from "discord.js"; +import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; + +interface LogStageInstanceUpdateData { + oldStageInstance: StageInstance; + newStageInstance: StageInstance; + stageChannel: StageChannel; + differenceString: string; +} + +export function logStageInstanceUpdate(pluginData: GuildPluginData, data: LogStageInstanceUpdateData) { + return log( + pluginData, + LogType.STAGE_INSTANCE_UPDATE, + createTypedTemplateSafeValueContainer({ + oldStageInstance: stageToTemplateSafeStage(data.oldStageInstance), + newStageInstance: stageToTemplateSafeStage(data.newStageInstance), + stageChannel: channelToTemplateSafeChannel(data.stageChannel), + differenceString: data.differenceString, + }), + { + channel: data.newStageInstance.channel!.id, + category: data.newStageInstance.channel!.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStickerCreate.ts b/backend/src/plugins/Logs/logFunctions/logStickerCreate.ts new file mode 100644 index 00000000..4e8a46be --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStickerCreate.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Sticker } from "discord.js"; +import { stickerToTemplateSafeSticker } from "../../../utils/templateSafeObjects"; + +interface LogStickerCreateData { + sticker: Sticker; +} + +export function logStickerCreate(pluginData: GuildPluginData, data: LogStickerCreateData) { + return log( + pluginData, + LogType.STICKER_CREATE, + createTypedTemplateSafeValueContainer({ + sticker: stickerToTemplateSafeSticker(data.sticker), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStickerDelete.ts b/backend/src/plugins/Logs/logFunctions/logStickerDelete.ts new file mode 100644 index 00000000..59f7128a --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStickerDelete.ts @@ -0,0 +1,22 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Sticker } from "discord.js"; +import { stickerToTemplateSafeSticker } from "../../../utils/templateSafeObjects"; + +interface LogStickerDeleteData { + sticker: Sticker; +} + +export function logStickerDelete(pluginData: GuildPluginData, data: LogStickerDeleteData) { + return log( + pluginData, + LogType.STICKER_DELETE, + createTypedTemplateSafeValueContainer({ + sticker: stickerToTemplateSafeSticker(data.sticker), + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logStickerUpdate.ts b/backend/src/plugins/Logs/logFunctions/logStickerUpdate.ts new file mode 100644 index 00000000..b1f7ea04 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logStickerUpdate.ts @@ -0,0 +1,26 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { Sticker } from "discord.js"; +import { stickerToTemplateSafeSticker } from "../../../utils/templateSafeObjects"; + +interface LogStickerUpdateData { + oldSticker: Sticker; + newSticker: Sticker; + differenceString: string; +} + +export function logStickerUpdate(pluginData: GuildPluginData, data: LogStickerUpdateData) { + return log( + pluginData, + LogType.STICKER_UPDATE, + createTypedTemplateSafeValueContainer({ + oldSticker: stickerToTemplateSafeSticker(data.oldSticker), + newSticker: stickerToTemplateSafeSticker(data.newSticker), + differenceString: data.differenceString, + }), + {}, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logThreadCreate.ts b/backend/src/plugins/Logs/logFunctions/logThreadCreate.ts new file mode 100644 index 00000000..f36a2f76 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logThreadCreate.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogThreadCreateData { + thread: ThreadChannel; +} + +export function logThreadCreate(pluginData: GuildPluginData, data: LogThreadCreateData) { + return log( + pluginData, + LogType.THREAD_CREATE, + createTypedTemplateSafeValueContainer({ + thread: channelToTemplateSafeChannel(data.thread), + }), + { + channel: data.thread.parentId, + category: data.thread.parent?.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts b/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts new file mode 100644 index 00000000..2467682f --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts @@ -0,0 +1,25 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogThreadDeleteData { + thread: ThreadChannel; +} + +export function logThreadDelete(pluginData: GuildPluginData, data: LogThreadDeleteData) { + return log( + pluginData, + LogType.THREAD_DELETE, + createTypedTemplateSafeValueContainer({ + thread: channelToTemplateSafeChannel(data.thread), + }), + { + channel: data.thread.parentId, + category: data.thread.parent?.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts b/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts new file mode 100644 index 00000000..80e20acc --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts @@ -0,0 +1,29 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { ThreadChannel } from "discord.js"; +import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; + +interface LogThreadUpdateData { + oldThread: ThreadChannel; + newThread: ThreadChannel; + differenceString: string; +} + +export function logThreadUpdate(pluginData: GuildPluginData, data: LogThreadUpdateData) { + return log( + pluginData, + LogType.THREAD_UPDATE, + createTypedTemplateSafeValueContainer({ + oldThread: channelToTemplateSafeChannel(data.oldThread), + newThread: channelToTemplateSafeChannel(data.newThread), + differenceString: data.differenceString, + }), + { + channel: data.newThread.parentId, + category: data.newThread.parent?.parentId, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts new file mode 100644 index 00000000..4bdca809 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts @@ -0,0 +1,39 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildVoiceChannel, GuildMember, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + memberToTemplateSafeMember, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; + +interface LogVoiceChannelForceDisconnectData { + mod: User; + member: GuildMember; + oldChannel: BaseGuildVoiceChannel; +} + +export function logVoiceChannelForceDisconnect( + pluginData: GuildPluginData, + data: LogVoiceChannelForceDisconnectData, +) { + return log( + pluginData, + LogType.VOICE_CHANNEL_FORCE_DISCONNECT, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + member: memberToTemplateSafeMember(data.member), + oldChannel: channelToTemplateSafeChannel(data.oldChannel), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.oldChannel.id, + category: data.oldChannel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts new file mode 100644 index 00000000..5f5ec1e7 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts @@ -0,0 +1,41 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildVoiceChannel, GuildMember, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + memberToTemplateSafeMember, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; + +interface LogVoiceChannelForceMoveData { + mod: User; + member: GuildMember; + oldChannel: BaseGuildVoiceChannel; + newChannel: BaseGuildVoiceChannel; +} + +export function logVoiceChannelForceMove( + pluginData: GuildPluginData, + data: LogVoiceChannelForceMoveData, +) { + return log( + pluginData, + LogType.VOICE_CHANNEL_FORCE_MOVE, + createTypedTemplateSafeValueContainer({ + mod: userToTemplateSafeUser(data.mod), + member: memberToTemplateSafeMember(data.member), + oldChannel: channelToTemplateSafeChannel(data.oldChannel), + newChannel: channelToTemplateSafeChannel(data.newChannel), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.newChannel.id, + category: data.newChannel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts new file mode 100644 index 00000000..07c44309 --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogVoiceChannelJoinData { + member: GuildMember; + channel: BaseGuildVoiceChannel; +} + +export function logVoiceChannelJoin(pluginData: GuildPluginData, data: LogVoiceChannelJoinData) { + return log( + pluginData, + LogType.VOICE_CHANNEL_JOIN, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + channel: channelToTemplateSafeChannel(data.channel), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.channel.id, + category: data.channel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts new file mode 100644 index 00000000..3fc67abb --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts @@ -0,0 +1,30 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogVoiceChannelLeaveData { + member: GuildMember; + channel: BaseGuildVoiceChannel; +} + +export function logVoiceChannelLeave(pluginData: GuildPluginData, data: LogVoiceChannelLeaveData) { + return log( + pluginData, + LogType.VOICE_CHANNEL_LEAVE, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + channel: channelToTemplateSafeChannel(data.channel), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.channel.id, + category: data.channel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts new file mode 100644 index 00000000..14e47ccd --- /dev/null +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts @@ -0,0 +1,32 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; +import { log } from "../util/log"; +import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; + +interface LogVoiceChannelMoveData { + member: GuildMember; + oldChannel: BaseGuildVoiceChannel; + newChannel: BaseGuildVoiceChannel; +} + +export function logVoiceChannelMove(pluginData: GuildPluginData, data: LogVoiceChannelMoveData) { + return log( + pluginData, + LogType.VOICE_CHANNEL_MOVE, + createTypedTemplateSafeValueContainer({ + member: memberToTemplateSafeMember(data.member), + oldChannel: channelToTemplateSafeChannel(data.oldChannel), + newChannel: channelToTemplateSafeChannel(data.newChannel), + }), + { + userId: data.member.id, + roles: Array.from(data.member.roles.cache.keys()), + channel: data.newChannel.id, + category: data.newChannel.parentId, + bot: data.member.user.bot, + }, + ); +} diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index 56178d6b..8dd547f2 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -1,4 +1,5 @@ import * as t from "io-ts"; +import { z } from "zod"; import { BasePluginType, typedGuildEventListener } from "knub"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; @@ -7,6 +8,26 @@ import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { RegExpRunner } from "../../RegExpRunner"; import { tMessageContent, tNullable } from "../../utils"; import { TRegex } from "../../validatorUtils"; +import { LogType } from "../../data/LogType"; +import { GuildMember } from "discord.js"; +import { + TemplateSafeCase, + TemplateSafeChannel, + TemplateSafeEmoji, + TemplateSafeMember, + TemplateSafeRole, + TemplateSafeSavedMessage, + TemplateSafeStage, + TemplateSafeSticker, + TemplateSafeUnknownMember, + TemplateSafeUnknownUser, + TemplateSafeUser, +} from "../../utils/templateSafeObjects"; +import { + TemplateSafeValue, + TemplateSafeValueContainer, + TypedTemplateSafeValueContainer, +} from "../../templateFormatter"; export const tLogFormats = t.record(t.string, t.union([t.string, tMessageContent])); export type TLogFormats = t.TypeOf; @@ -73,3 +94,428 @@ export interface LogsPluginType extends BasePluginType { } export const logsEvt = typedGuildEventListener(); + +export const LogTypeData = z.object({ + [LogType.MEMBER_WARN]: z.object({ + mod: z.instanceof(TemplateSafeMember), + member: z.instanceof(TemplateSafeMember), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_MUTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_UNMUTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_MUTE_EXPIRED]: z.object({ + member: z.instanceof(TemplateSafeMember).or(z.instanceof(TemplateSafeUnknownMember)), + }), + + [LogType.MEMBER_KICK]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.null()), + user: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_BAN]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.null()), + user: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_UNBAN]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.null()), + userId: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_FORCEBAN]: z.object({ + mod: z.instanceof(TemplateSafeUser), + userId: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_JOIN]: z.object({ + member: z.instanceof(TemplateSafeMember), + new: z.string(), + account_age: z.string(), + }), + + [LogType.MEMBER_LEAVE]: z.object({ + member: z.instanceof(TemplateSafeMember), + }), + + [LogType.MEMBER_ROLE_ADD]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.null()), + member: z.instanceof(TemplateSafeMember), + roles: z.string(), + }), + + [LogType.MEMBER_ROLE_REMOVE]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.null()), + member: z.instanceof(TemplateSafeMember), + roles: z.string(), + }), + + [LogType.MEMBER_NICK_CHANGE]: z.object({ + member: z.instanceof(TemplateSafeMember), + oldNick: z.string(), + newNick: z.string(), + }), + + [LogType.MEMBER_RESTORE]: z.object({ + member: z.instanceof(TemplateSafeMember), + restoredData: z.string(), + }), + + [LogType.CHANNEL_CREATE]: z.object({ + channel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.CHANNEL_DELETE]: z.object({ + channel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.CHANNEL_UPDATE]: z.object({ + oldChannel: z.instanceof(TemplateSafeChannel), + newChannel: z.instanceof(TemplateSafeChannel), + differenceString: z.string(), + }), + + [LogType.THREAD_CREATE]: z.object({ + thread: z.instanceof(TemplateSafeChannel), + }), + + [LogType.THREAD_DELETE]: z.object({ + thread: z.instanceof(TemplateSafeChannel), + }), + + [LogType.THREAD_UPDATE]: z.object({ + oldThread: z.instanceof(TemplateSafeChannel), + newThread: z.instanceof(TemplateSafeChannel), + differenceString: z.string(), + }), + + [LogType.ROLE_CREATE]: z.object({ + role: z.instanceof(TemplateSafeRole), + }), + + [LogType.ROLE_DELETE]: z.object({ + role: z.instanceof(TemplateSafeRole), + }), + + [LogType.ROLE_UPDATE]: z.object({ + oldRole: z.instanceof(TemplateSafeRole), + newRole: z.instanceof(TemplateSafeRole), + differenceString: z.string(), + }), + + [LogType.MESSAGE_EDIT]: z.object({ + user: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + before: z.instanceof(TemplateSafeSavedMessage), + after: z.instanceof(TemplateSafeSavedMessage), + }), + + [LogType.MESSAGE_DELETE]: z.object({ + user: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + messageDate: z.string(), + message: z.instanceof(TemplateSafeSavedMessage), + }), + + [LogType.MESSAGE_DELETE_BULK]: z.object({ + count: z.number(), + authorIds: z.array(z.string()), + channel: z.instanceof(TemplateSafeChannel), + archiveUrl: z.string(), + }), + + [LogType.MESSAGE_DELETE_BARE]: z.object({ + messageId: z.string(), + channel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.VOICE_CHANNEL_JOIN]: z.object({ + member: z.instanceof(TemplateSafeMember), + channel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.VOICE_CHANNEL_LEAVE]: z.object({ + member: z.instanceof(TemplateSafeMember), + channel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.VOICE_CHANNEL_MOVE]: z.object({ + member: z.instanceof(TemplateSafeMember), + oldChannel: z.instanceof(TemplateSafeChannel), + newChannel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.STAGE_INSTANCE_CREATE]: z.object({ + stageInstance: z.instanceof(TemplateSafeStage), + stageChannel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.STAGE_INSTANCE_DELETE]: z.object({ + stageInstance: z.instanceof(TemplateSafeStage), + stageChannel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.STAGE_INSTANCE_UPDATE]: z.object({ + oldStageInstance: z.instanceof(TemplateSafeStage), + newStageInstance: z.instanceof(TemplateSafeStage), + stageChannel: z.instanceof(TemplateSafeChannel), + differenceString: z.string(), + }), + + [LogType.EMOJI_CREATE]: z.object({ + emoji: z.instanceof(TemplateSafeEmoji), + }), + + [LogType.EMOJI_DELETE]: z.object({ + emoji: z.instanceof(TemplateSafeEmoji), + }), + + [LogType.EMOJI_UPDATE]: z.object({ + oldEmoji: z.instanceof(TemplateSafeEmoji), + newEmoji: z.instanceof(TemplateSafeEmoji), + differenceString: z.string(), + }), + + [LogType.STICKER_CREATE]: z.object({ + sticker: z.instanceof(TemplateSafeSticker), + }), + + [LogType.STICKER_DELETE]: z.object({ + sticker: z.instanceof(TemplateSafeSticker), + }), + + [LogType.STICKER_UPDATE]: z.object({ + oldSticker: z.instanceof(TemplateSafeSticker), + newSticker: z.instanceof(TemplateSafeSticker), + differenceString: z.string(), + }), + + [LogType.MESSAGE_SPAM_DETECTED]: z.object({ + member: z.instanceof(TemplateSafeMember), + channel: z.instanceof(TemplateSafeChannel), + description: z.string(), + limit: z.number(), + interval: z.number(), + archiveUrl: z.string(), + }), + + [LogType.CENSOR]: z.object({ + user: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + reason: z.string(), + message: z.instanceof(TemplateSafeSavedMessage), + messageText: z.string(), + }), + + [LogType.CLEAN]: z.object({ + mod: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + count: z.number(), + archiveUrl: z.string(), + }), + + [LogType.CASE_CREATE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + userId: z.string(), + caseNum: z.number(), + caseType: z.string(), + reason: z.string(), + }), + + [LogType.MASSUNBAN]: z.object({ + mod: z.instanceof(TemplateSafeUser), + count: z.number(), + reason: z.string(), + }), + + [LogType.MASSBAN]: z.object({ + mod: z.instanceof(TemplateSafeUser), + count: z.number(), + reason: z.string(), + }), + + [LogType.MASSMUTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + count: z.number(), + }), + + [LogType.MEMBER_TIMED_MUTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + time: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_TIMED_UNMUTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + time: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_TIMED_BAN]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + banTime: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_TIMED_UNBAN]: z.object({ + mod: z.instanceof(TemplateSafeUser).or(z.instanceof(TemplateSafeUnknownUser)), + userId: z.string(), + banTime: z.string(), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.MEMBER_JOIN_WITH_PRIOR_RECORDS]: z.object({ + member: z.instanceof(TemplateSafeMember), + recentCaseSummary: z.string(), + }), + + [LogType.OTHER_SPAM_DETECTED]: z.object({ + member: z.instanceof(TemplateSafeMember), + description: z.string(), + limit: z.number(), + interval: z.number(), + }), + + [LogType.MEMBER_ROLE_CHANGES]: z.object({ + mod: z + .instanceof(TemplateSafeUser) + .or(z.instanceof(TemplateSafeUnknownUser)) + .or(z.null()), + member: z.instanceof(TemplateSafeMember), + addedRoles: z.string(), + removedRoles: z.string(), + }), + + [LogType.VOICE_CHANNEL_FORCE_MOVE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + member: z.instanceof(TemplateSafeMember), + oldChannel: z.instanceof(TemplateSafeChannel), + newChannel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.VOICE_CHANNEL_FORCE_DISCONNECT]: z.object({ + mod: z.instanceof(TemplateSafeUser), + member: z.instanceof(TemplateSafeMember), + oldChannel: z.instanceof(TemplateSafeChannel), + }), + + [LogType.CASE_UPDATE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + caseType: z.string(), + note: z.string(), + }), + + [LogType.MEMBER_MUTE_REJOIN]: z.object({ + member: z.instanceof(TemplateSafeMember), + }), + + [LogType.SCHEDULED_MESSAGE]: z.object({ + author: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + datetime: z.string(), + date: z.string(), + time: z.string(), + }), + + [LogType.POSTED_SCHEDULED_MESSAGE]: z.object({ + author: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + messageId: z.string(), + }), + + [LogType.BOT_ALERT]: z.object({ + body: z.string(), + }), + + [LogType.AUTOMOD_ACTION]: z.object({ + rule: z.string(), + user: z.instanceof(TemplateSafeUser), + users: z.array(z.instanceof(TemplateSafeUser)), + actionsTaken: z.string(), + matchSummary: z.string(), + }), + + [LogType.SCHEDULED_REPEATED_MESSAGE]: z.object({ + author: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + datetime: z.string(), + date: z.string(), + time: z.string(), + repeatInterval: z.string(), + repeatDetails: z.string(), + }), + + [LogType.REPEATED_MESSAGE]: z.object({ + author: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + datetime: z.string(), + date: z.string(), + time: z.string(), + repeatInterval: z.string(), + repeatDetails: z.string(), + }), + + [LogType.MESSAGE_DELETE_AUTO]: z.object({ + message: z.instanceof(TemplateSafeSavedMessage), + user: z.instanceof(TemplateSafeUser), + channel: z.instanceof(TemplateSafeChannel), + messageDate: z.string(), + }), + + [LogType.SET_ANTIRAID_USER]: z.object({ + level: z.string(), + user: z.instanceof(TemplateSafeUser), + }), + + [LogType.SET_ANTIRAID_AUTO]: z.object({ + level: z.string(), + }), + + [LogType.MEMBER_NOTE]: z.object({ + mod: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser), + caseNumber: z.number(), + reason: z.string(), + }), + + [LogType.CASE_DELETE]: z.object({ + mod: z.instanceof(TemplateSafeMember), + case: z.instanceof(TemplateSafeCase), + }), + + [LogType.DM_FAILED]: z.object({ + source: z.string(), + user: z.instanceof(TemplateSafeUser).or(z.instanceof(TemplateSafeUnknownUser)), + }), +}); + +export type ILogTypeData = z.infer; diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index 8b77b736..f39b8ab4 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -3,7 +3,12 @@ import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; +import { + renderTemplate, + TemplateParseError, + TemplateSafeValueContainer, + TypedTemplateSafeValueContainer, +} from "../../../templateFormatter"; import { messageSummary, renderRecursively, @@ -13,17 +18,18 @@ import { verboseUserName, } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { FORMAT_NO_TIMESTAMP, LogsPluginType, TLogChannel } from "../types"; +import { FORMAT_NO_TIMESTAMP, ILogTypeData, LogsPluginType, TLogChannel } from "../types"; import { - getConfigAccessibleMemberLevel, - IConfigAccessibleMember, - memberToConfigAccessibleMember, -} from "../../../utils/configAccessibleObjects"; + getTemplateSafeMemberLevel, + TemplateSafeMember, + memberToTemplateSafeMember, + TemplateSafeUser, +} from "../../../utils/templateSafeObjects"; -export async function getLogMessage( +export async function getLogMessage( pluginData: GuildPluginData, - type: LogType, - data: any, + type: TLogType, + data: TypedTemplateSafeValueContainer, opts?: Pick, ): Promise { const config = pluginData.config.get(); @@ -42,10 +48,12 @@ export async function getLogMessage( const isoTimestamp = time.toISOString(); const timestamp = timestampFormat ? time.format(timestampFormat) : ""; - const values = { + const values = new TemplateSafeValueContainer({ ...data, timestamp, - userMention: async inputUserOrMember => { + userMention: async ( + inputUserOrMember: TemplateSafeUser | TemplateSafeMember | TemplateSafeUser[] | TemplateSafeMember[], + ) => { if (!inputUserOrMember) return ""; const usersOrMembers = Array.isArray(inputUserOrMember) ? inputUserOrMember : [inputUserOrMember]; @@ -53,20 +61,20 @@ export async function getLogMessage( const mentions: string[] = []; for (const userOrMember of usersOrMembers) { let user; - let member: IConfigAccessibleMember | null = null; + let member: TemplateSafeMember | null = null; if (userOrMember.user) { - member = userOrMember as IConfigAccessibleMember; + member = userOrMember as TemplateSafeMember; user = member.user; } else { user = userOrMember; const apiMember = await resolveMember(pluginData.client, pluginData.guild, user.id); if (apiMember) { - member = memberToConfigAccessibleMember(apiMember); + member = memberToTemplateSafeMember(apiMember); } } - const level = member ? getConfigAccessibleMemberLevel(pluginData, member) : 0; + const level = member ? getTemplateSafeMemberLevel(pluginData, member) : 0; const memberConfig = (await pluginData.config.getMatchingConfig({ level, @@ -92,7 +100,7 @@ export async function getLogMessage( if (!msg) return ""; return messageSummary(msg); }, - }; + }); if (type === LogType.BOT_ALERT) { const valuesWithoutTmplEval = { ...values }; diff --git a/backend/src/plugins/Logs/util/isLogIgnored.ts b/backend/src/plugins/Logs/util/isLogIgnored.ts new file mode 100644 index 00000000..093fb9a6 --- /dev/null +++ b/backend/src/plugins/Logs/util/isLogIgnored.ts @@ -0,0 +1,7 @@ +import { GuildPluginData } from "knub"; +import { LogsPluginType } from "../types"; +import { LogType } from "../../../data/LogType"; + +export function isLogIgnored(pluginData: GuildPluginData, type: LogType, ignoreId: string) { + return pluginData.state.guildLogs.isLogIgnored(type, ignoreId); +} diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index b8e6eed0..45205882 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -1,11 +1,12 @@ import { MessageMentionTypes, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { LogType } from "../../../data/LogType"; import { allowTimeout } from "../../../RegExpRunner"; import { createChunkedMessage, get, noop } from "../../../utils"; -import { LogsPluginType, TLogChannelMap } from "../types"; +import { ILogTypeData, LogsPluginType, LogTypeData, TLogChannelMap } from "../types"; import { getLogMessage } from "./getLogMessage"; +import { TemplateSafeValueContainer, TypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { LogType } from "../../../data/LogType"; const excludedUserProps = ["user", "member", "mod"]; const excludedRoleProps = ["message.member.roles", "member.roles"]; @@ -14,7 +15,21 @@ function isRoleArray(value: any): value is string[] { return Array.isArray(value); } -export async function log(pluginData: GuildPluginData, type: LogType, data: any) { +interface ExclusionData { + userId?: Snowflake | null; + bot?: boolean | null; + roles?: Snowflake[] | null; + channel?: Snowflake | null; + category?: Snowflake | null; + messageTextContent?: string | null; +} + +export async function log( + pluginData: GuildPluginData, + type: TLogType, + data: TypedTemplateSafeValueContainer, + exclusionData: ExclusionData = {}, +) { const logChannels: TLogChannelMap = pluginData.config.get().channels; const typeStr = LogType[type]; @@ -25,94 +40,40 @@ export async function log(pluginData: GuildPluginData, type: Log if ((opts.include && opts.include.includes(typeStr)) || (opts.exclude && !opts.exclude.includes(typeStr))) { // If this log entry is about an excluded user, skip it // TODO: Quick and dirty solution, look into changing at some point - if (opts.excluded_users) { - for (const prop of excludedUserProps) { - if (data && data[prop] && opts.excluded_users.includes(data[prop].id)) { - continue logChannelLoop; - } - } + if (opts.excluded_users && exclusionData.userId && opts.excluded_users.includes(exclusionData.userId)) { + continue; } // If we're excluding bots and the logged user is a bot, skip it - if (opts.exclude_bots) { - for (const prop of excludedUserProps) { - if (data && data[prop] && data[prop].bot) { + if (opts.exclude_bots && exclusionData.bot) { + continue; + } + + if (opts.excluded_roles && exclusionData.roles) { + for (const role of exclusionData.roles) { + if (opts.excluded_roles.includes(role)) { continue logChannelLoop; } } } - if (opts.excluded_roles) { - for (const value of Object.values(data || {})) { - if (value instanceof SavedMessage) { - const member = pluginData.guild.members.cache.get(value.user_id as Snowflake); - for (const role of member?.roles.cache || []) { - if (opts.excluded_roles.includes(role[0])) { - continue logChannelLoop; - } - } - } - } - - for (const prop of excludedRoleProps) { - const roles = get(data, prop); - if (!isRoleArray(roles)) { - continue; - } - - for (const role of roles) { - if (opts.excluded_roles.includes(role)) { - continue logChannelLoop; - } - } - } + if (opts.excluded_channels && exclusionData.channel && opts.excluded_channels.includes(exclusionData.channel)) { + continue; } - // If this entry is from an excluded channel, skip it - if (opts.excluded_channels) { - if ( - type === LogType.MESSAGE_DELETE || - type === LogType.MESSAGE_DELETE_BARE || - type === LogType.MESSAGE_EDIT || - type === LogType.MESSAGE_SPAM_DETECTED || - type === LogType.CENSOR || - type === LogType.CLEAN - ) { - if (opts.excluded_channels.includes(data.channel.id)) { - continue logChannelLoop; - } - } + if ( + opts.excluded_categories && + exclusionData.category && + opts.excluded_categories.includes(exclusionData.category) + ) { + continue; } - // If this entry is from an excluded category, skip it - if (opts.excluded_categories) { - if ( - type === LogType.MESSAGE_DELETE || - type === LogType.MESSAGE_DELETE_BARE || - type === LogType.MESSAGE_EDIT || - type === LogType.MESSAGE_SPAM_DETECTED || - type === LogType.CENSOR || - type === LogType.CLEAN - ) { - if (data.channel.parentId && opts.excluded_categories.includes(data.channel.parentId)) { - continue logChannelLoop; - } - } - } - - // If this entry contains a message with an excluded regex, skip it - if (type === LogType.MESSAGE_DELETE && opts.excluded_message_regexes && data.message.data.content) { + if (opts.excluded_message_regexes && exclusionData.messageTextContent) { for (const regex of opts.excluded_message_regexes) { - const matches = await pluginData.state.regexRunner.exec(regex, data.message.data.content).catch(allowTimeout); - if (matches) { - continue logChannelLoop; - } - } - } - - if (type === LogType.MESSAGE_EDIT && opts.excluded_message_regexes && data.before.data.content) { - for (const regex of opts.excluded_message_regexes) { - const matches = await pluginData.state.regexRunner.exec(regex, data.before.data.content).catch(allowTimeout); + const matches = await pluginData.state.regexRunner + .exec(regex, exclusionData.messageTextContent) + .catch(allowTimeout); if (matches) { continue logChannelLoop; } diff --git a/backend/src/plugins/Logs/util/onMessageDelete.ts b/backend/src/plugins/Logs/util/onMessageDelete.ts index f1f64893..55d5d341 100644 --- a/backend/src/plugins/Logs/util/onMessageDelete.ts +++ b/backend/src/plugins/Logs/util/onMessageDelete.ts @@ -1,51 +1,33 @@ -import { MessageAttachment, Snowflake } from "discord.js"; +import { BaseGuildTextChannel, Snowflake, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import moment from "moment-timezone"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; -import { resolveUser, useMediaUrls } from "../../../utils"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { FORMAT_NO_TIMESTAMP, LogsPluginType } from "../types"; +import { resolveUser } from "../../../utils"; +import { LogsPluginType } from "../types"; +import { logMessageDelete } from "../logFunctions/logMessageDelete"; +import { isLogIgnored } from "./isLogIgnored"; +import { logMessageDeleteBare } from "../logFunctions/logMessageDeleteBare"; export async function onMessageDelete(pluginData: GuildPluginData, savedMessage: SavedMessage) { const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)! as + | BaseGuildTextChannel + | ThreadChannel; + + if (isLogIgnored(pluginData, LogType.MESSAGE_DELETE, savedMessage.id)) { + return; + } if (user) { - // Replace attachment URLs with media URLs - if (savedMessage.data.attachments) { - for (const attachment of savedMessage.data.attachments as MessageAttachment[]) { - attachment.url = useMediaUrls(attachment.url); - } - } - - // See comment on FORMAT_NO_TIMESTAMP in types.ts - const config = pluginData.config.get(); - const timestampFormat = - (config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ?? config.timestamp_format; - - pluginData.state.guildLogs.log( - LogType.MESSAGE_DELETE, - { - user: userToConfigAccessibleUser(user), - channel: channelToConfigAccessibleChannel(channel), - messageDate: pluginData - .getPlugin(TimeAndDatePlugin) - .inGuildTz(moment.utc(savedMessage.data.timestamp, "x")) - .format(timestampFormat), - message: savedMessage, - }, - savedMessage.id, - ); + logMessageDelete(pluginData, { + user, + channel, + message: savedMessage, + }); } else { - pluginData.state.guildLogs.log( - LogType.MESSAGE_DELETE_BARE, - { - messageId: savedMessage.id, - channel: channelToConfigAccessibleChannel(channel), - }, - savedMessage.id, - ); + logMessageDeleteBare(pluginData, { + messageId: savedMessage.id, + channel, + }); } } diff --git a/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts b/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts index 500b5cb7..c62e260b 100644 --- a/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts +++ b/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts @@ -1,24 +1,28 @@ -import { Snowflake } from "discord.js"; +import { BaseGuildTextChannel, Snowflake, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; import { getBaseUrl } from "../../../pluginUtils"; import { LogsPluginType } from "../types"; +import { logMessageDeleteBulk } from "../logFunctions/logMessageDeleteBulk"; +import { isLogIgnored } from "./isLogIgnored"; export async function onMessageDeleteBulk(pluginData: GuildPluginData, savedMessages: SavedMessage[]) { - const channel = pluginData.guild.channels.cache.get(savedMessages[0].channel_id as Snowflake); + if (isLogIgnored(pluginData, LogType.MESSAGE_DELETE, savedMessages[0].id)) { + return; + } + + const channel = pluginData.guild.channels.cache.get(savedMessages[0].channel_id as Snowflake) as + | BaseGuildTextChannel + | ThreadChannel; const archiveId = await pluginData.state.archives.createFromSavedMessages(savedMessages, pluginData.guild); const archiveUrl = pluginData.state.archives.getUrl(getBaseUrl(pluginData), archiveId); - const authorIds = Array.from(new Set(savedMessages.map(item => `\`${item.user_id}\``))).join(", "); + const authorIds = Array.from(new Set(savedMessages.map(item => `\`${item.user_id}\``))); - pluginData.state.guildLogs.log( - LogType.MESSAGE_DELETE_BULK, - { - count: savedMessages.length, - authorIds, - channel, - archiveUrl, - }, - savedMessages[0].id, - ); + logMessageDeleteBulk(pluginData, { + count: savedMessages.length, + authorIds, + channel, + archiveUrl, + }); } diff --git a/backend/src/plugins/Logs/util/onMessageUpdate.ts b/backend/src/plugins/Logs/util/onMessageUpdate.ts index 1d0efd1a..b3269b45 100644 --- a/backend/src/plugins/Logs/util/onMessageUpdate.ts +++ b/backend/src/plugins/Logs/util/onMessageUpdate.ts @@ -1,11 +1,12 @@ -import { MessageEmbed, Snowflake } from "discord.js"; +import { BaseGuildTextChannel, MessageEmbed, Snowflake, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import cloneDeep from "lodash.clonedeep"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; import { resolveUser } from "../../../utils"; import { LogsPluginType } from "../types"; +import { logMessageEdit } from "../logFunctions/logMessageEdit"; export async function onMessageUpdate( pluginData: GuildPluginData, @@ -48,11 +49,13 @@ export async function onMessageUpdate( } const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)! as + | BaseGuildTextChannel + | ThreadChannel; - pluginData.state.guildLogs.log(LogType.MESSAGE_EDIT, { - user: userToConfigAccessibleUser(user), - channel: channelToConfigAccessibleChannel(channel), + logMessageEdit(pluginData, { + user, + channel, before: oldSavedMessage, after: savedMessage, }); diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index c38d0e9a..0a1ac232 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -46,6 +46,7 @@ import { outdatedTempbansLoop } from "./functions/outdatedTempbansLoop"; import { updateCase } from "./functions/updateCase"; import { warnMember } from "./functions/warnMember"; import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types"; +import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions = { config: { @@ -121,7 +122,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: [TimeAndDatePlugin, CasesPlugin, MutesPlugin], + dependencies: [TimeAndDatePlugin, CasesPlugin, MutesPlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts index dd968c76..b28b1eeb 100644 --- a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts @@ -1,4 +1,4 @@ -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; @@ -8,6 +8,7 @@ import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from ". import { resolveMember, resolveUser } from "../../../utils"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const opts = { mod: ct.member({ option: true }), @@ -79,8 +80,8 @@ export const AddCaseCmd = modActionsCmd({ } // Log the action - pluginData.state.serverLogs.log(LogType.CASE_CREATE, { - mod: userToConfigAccessibleUser(mod.user), + pluginData.getPlugin(LogsPlugin).logCaseCreate({ + mod: mod.user, userId: user.id, caseNum: theCase.case_number, caseType: type.toUpperCase(), diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 6437a5e9..34512a14 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -1,6 +1,6 @@ import humanizeDuration from "humanize-duration"; import { getMemberLevel } from "knub/dist/helpers"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -14,6 +14,7 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { isBanned } from "../functions/isBanned"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; import { modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const opts = { mod: ct.member({ option: true }), @@ -108,14 +109,22 @@ export const BanCmd = modActionsCmd({ reason, noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], }); - const logtype = time ? LogType.MEMBER_TIMED_BAN : LogType.MEMBER_BAN; - pluginData.state.serverLogs.log(logtype, { - mod: userToConfigAccessibleUser(mod.user), - user: userToConfigAccessibleUser(user), - caseNumber: createdCase.case_number, - reason, - banTime: time ? humanizeDuration(time) : null, - }); + if (time) { + pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason, + banTime: humanizeDuration(time), + }); + } else { + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod, + user, + caseNumber: createdCase.case_number, + reason, + }); + } sendSuccessMessage( pluginData, diff --git a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts index 925d97c6..352b6d00 100644 --- a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts @@ -1,6 +1,6 @@ import { TextChannel } from "discord.js"; import { helpers } from "knub"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; @@ -81,9 +81,9 @@ export const DeleteCaseCmd = modActionsCmd({ ); const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.CASE_DELETE, { - mod: memberToConfigAccessibleMember(message.member), - case: stripObjectToScalars(theCase), + logs.logCaseDelete({ + mod: message.member, + case: theCase, }); } diff --git a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts index 8a1df43e..1df592b6 100644 --- a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts @@ -1,5 +1,5 @@ import { Snowflake } from "discord.js"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -10,6 +10,7 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { ignoreEvent } from "../functions/ignoreEvent"; import { isBanned } from "../functions/isBanned"; import { IgnoredEventType, modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const opts = { mod: ct.member({ option: true }), @@ -91,8 +92,8 @@ export const ForcebanCmd = modActionsCmd({ sendSuccessMessage(pluginData, msg.channel, `Member forcebanned (Case #${createdCase.case_number})`); // Log the action - pluginData.state.serverLogs.log(LogType.MEMBER_FORCEBAN, { - mod: userToConfigAccessibleUser(mod.user), + pluginData.getPlugin(LogsPlugin).logMemberForceban({ + mod, userId: user.id, caseNumber: createdCase.case_number, reason, diff --git a/backend/src/plugins/ModActions/commands/MassBanCmd.ts b/backend/src/plugins/ModActions/commands/MassBanCmd.ts index e4e4fd44..b11f730b 100644 --- a/backend/src/plugins/ModActions/commands/MassBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassBanCmd.ts @@ -1,7 +1,7 @@ import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; import { performance } from "perf_hooks"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -12,6 +12,7 @@ import { MINUTES, noop } from "../../../utils"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; import { IgnoredEventType, modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const MassbanCmd = modActionsCmd({ trigger: "massban", @@ -129,8 +130,8 @@ export const MassbanCmd = modActionsCmd({ sendErrorMessage(pluginData, msg.channel, "All bans failed. Make sure the IDs are valid."); } else { // Some or all bans were successful. Create a log entry for the mass ban and notify the user. - pluginData.state.serverLogs.log(LogType.MASSBAN, { - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMassBan({ + mod: msg.author, count: successfulBanCount, reason: banReason, }); diff --git a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts index ea5e5770..4fee866f 100644 --- a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts @@ -1,6 +1,6 @@ import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -10,6 +10,7 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { ignoreEvent } from "../functions/ignoreEvent"; import { isBanned } from "../functions/isBanned"; import { IgnoredEventType, modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const MassunbanCmd = modActionsCmd({ trigger: "massunban", @@ -83,8 +84,8 @@ export const MassunbanCmd = modActionsCmd({ sendErrorMessage(pluginData, msg.channel, "All unbans failed. Make sure the IDs are valid and banned."); } else { // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. - pluginData.state.serverLogs.log(LogType.MASSUNBAN, { - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMassUnban({ + mod: msg.author, count: successfulUnbanCount, reason: unbanReason, }); diff --git a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts index 5c82dd5b..7b5d50b1 100644 --- a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts @@ -1,6 +1,6 @@ import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; @@ -8,6 +8,7 @@ import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const MassmuteCmd = modActionsCmd({ trigger: "massmute", @@ -86,8 +87,8 @@ export const MassmuteCmd = modActionsCmd({ sendErrorMessage(pluginData, msg.channel, "All mutes failed. Make sure the IDs are valid."); } else { // Success on all or some mutes - pluginData.state.serverLogs.log(LogType.MASSMUTE, { - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMassMute({ + mod: msg.author, count: successfulMuteCount, }); diff --git a/backend/src/plugins/ModActions/commands/NoteCmd.ts b/backend/src/plugins/ModActions/commands/NoteCmd.ts index 92e1b0fa..569831a8 100644 --- a/backend/src/plugins/ModActions/commands/NoteCmd.ts +++ b/backend/src/plugins/ModActions/commands/NoteCmd.ts @@ -1,4 +1,4 @@ -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -7,6 +7,7 @@ import { resolveUser } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const NoteCmd = modActionsCmd({ trigger: "note", @@ -41,9 +42,9 @@ export const NoteCmd = modActionsCmd({ reason, }); - pluginData.state.serverLogs.log(LogType.MEMBER_NOTE, { - mod: userToConfigAccessibleUser(msg.author), - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberNote({ + mod: msg.author, + user, caseNumber: createdCase.case_number, reason, }); diff --git a/backend/src/plugins/ModActions/commands/UnbanCmd.ts b/backend/src/plugins/ModActions/commands/UnbanCmd.ts index 0e199b25..eb235917 100644 --- a/backend/src/plugins/ModActions/commands/UnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnbanCmd.ts @@ -1,5 +1,5 @@ import { Snowflake } from "discord.js"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; @@ -9,6 +9,7 @@ import { resolveUser } from "../../../utils"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; import { IgnoredEventType, modActionsCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const opts = { mod: ct.member({ option: true }), @@ -73,11 +74,11 @@ export const UnbanCmd = modActionsCmd({ sendSuccessMessage(pluginData, msg.channel, `Member unbanned (Case #${createdCase.case_number})`); // Log the action - pluginData.state.serverLogs.log(LogType.MEMBER_UNBAN, { - mod: userToConfigAccessibleUser(mod.user), + pluginData.getPlugin(LogsPlugin).logMemberUnban({ + mod: mod.user, userId: user.id, caseNumber: createdCase.case_number, - reason, + reason: reason ?? "", }); pluginData.state.events.emit("unban", user.id); diff --git a/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts b/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts index 875f8cd7..1afa0a01 100644 --- a/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts @@ -1,5 +1,5 @@ import { GuildAuditLogs, User } from "discord.js"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; @@ -9,6 +9,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; import { isEventIgnored } from "../functions/isEventIgnored"; import { IgnoredEventType, modActionsEvt } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Create a BAN case automatically when a user is banned manually. @@ -65,9 +66,9 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ } } - pluginData.state.serverLogs.log(LogType.MEMBER_BAN, { - mod: mod ? userToConfigAccessibleUser(mod) : null, - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod: mod ? userToTemplateSafeUser(mod) : null, + user: userToTemplateSafeUser(user), caseNumber: createdCase?.case_number ?? 0, reason, }); diff --git a/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts b/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts index d69ede2a..421c8681 100644 --- a/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts @@ -1,5 +1,5 @@ import { GuildAuditLogs, User } from "discord.js"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; @@ -10,6 +10,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; import { isEventIgnored } from "../functions/isEventIgnored"; import { IgnoredEventType, modActionsEvt } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Create a KICK case automatically when a user is kicked manually. @@ -58,9 +59,9 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt({ } } - pluginData.state.serverLogs.log(LogType.MEMBER_KICK, { - user: userToConfigAccessibleUser(member.user!), - mod: mod ? userToConfigAccessibleUser(mod) : null, + pluginData.getPlugin(LogsPlugin).logMemberKick({ + user: member.user!, + mod, caseNumber: createdCase?.case_number ?? 0, reason: kickAuditLogEntry.reason || "", }); diff --git a/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts b/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts index 83143606..de86e6e4 100644 --- a/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts @@ -1,5 +1,5 @@ import { GuildAuditLogs, User } from "discord.js"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; @@ -9,6 +9,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; import { isEventIgnored } from "../functions/isEventIgnored"; import { IgnoredEventType, modActionsEvt } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Create an UNBAN case automatically when a user is unbanned manually. @@ -63,10 +64,11 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({ } } - pluginData.state.serverLogs.log(LogType.MEMBER_UNBAN, { - mod: mod ? userToConfigAccessibleUser(mod) : null, + pluginData.getPlugin(LogsPlugin).logMemberUnban({ + mod, userId: user.id, caseNumber: createdCase?.case_number ?? 0, + reason: "", }); pluginData.state.events.emit("unban", user.id); diff --git a/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts b/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts index 136e41cc..39a11c25 100644 --- a/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts +++ b/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts @@ -24,14 +24,14 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt({ if (actions.length) { const alertChannel = pluginData.guild.channels.cache.get(alertChannelId as Snowflake); if (!alertChannel) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Unknown \`alert_channel\` configured for \`mod_actions\`: \`${alertChannelId}\``, }); return; } if (!(alertChannel instanceof TextChannel)) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Non-text channel configured as \`alert_channel\` in \`mod_actions\`: \`${alertChannelId}\``, }); return; @@ -40,7 +40,7 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt({ const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); const botPerms = alertChannel.permissionsFor(botMember ?? pluginData.client.user!.id); if (!hasDiscordPermissions(botPerms, Permissions.FLAGS.SEND_MESSAGES)) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Missing "Send Messages" permissions for the \`alert_channel\` configured in \`mod_actions\`: \`${alertChannelId}\``, }); return; diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts index 3462842d..e9275fc2 100644 --- a/backend/src/plugins/ModActions/functions/banUserId.ts +++ b/backend/src/plugins/ModActions/functions/banUserId.ts @@ -1,7 +1,7 @@ import { DiscordAPIError, Snowflake, User } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; @@ -18,6 +18,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { BanOptions, BanResult, IgnoredEventType, ModActionsPluginType } from "../types"; import { getDefaultContactMethods } from "./getDefaultContactMethods"; import { ignoreEvent } from "./ignoreEvent"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Ban the specified user id, whether or not they're actually on the server at the time. Generates a case. @@ -128,14 +129,23 @@ export async function banUserId( // Log the action const mod = await resolveUser(pluginData.client, modId); - const logtype = banTime ? LogType.MEMBER_TIMED_BAN : LogType.MEMBER_BAN; - pluginData.state.serverLogs.log(logtype, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(user), - caseNumber: createdCase.case_number, - reason, - banTime: banTime ? humanizeDuration(banTime) : null, - }); + + if (banTime) { + pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ + mod, + user, + caseNumber: createdCase.case_number, + reason: reason ?? "", + banTime: humanizeDuration(banTime), + }); + } else { + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod, + user, + caseNumber: createdCase.case_number, + reason: reason ?? "", + }); + } pluginData.state.events.emit("ban", user.id, reason, banOptions.isAutomodAction); diff --git a/backend/src/plugins/ModActions/functions/isBanned.ts b/backend/src/plugins/ModActions/functions/isBanned.ts index 09ec1e2e..57ea8bf4 100644 --- a/backend/src/plugins/ModActions/functions/isBanned.ts +++ b/backend/src/plugins/ModActions/functions/isBanned.ts @@ -13,7 +13,7 @@ export async function isBanned( ): Promise { const botMember = pluginData.guild.members.cache.get(pluginData.client.user!.id); if (botMember && !hasDiscordPermissions(botMember.permissions, Permissions.FLAGS.BAN_MEMBERS)) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing "Ban Members" permission to check for existing bans`, }); return false; @@ -37,7 +37,7 @@ export async function isBanned( } if (isDiscordAPIError(e) && e.code === 50013) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing "Ban Members" permission to check for existing bans`, }); } diff --git a/backend/src/plugins/ModActions/functions/kickMember.ts b/backend/src/plugins/ModActions/functions/kickMember.ts index 3ffcab6b..d74cd727 100644 --- a/backend/src/plugins/ModActions/functions/kickMember.ts +++ b/backend/src/plugins/ModActions/functions/kickMember.ts @@ -1,6 +1,6 @@ import { GuildMember } from "discord.js"; import { GuildPluginData } from "knub"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { renderTemplate } from "../../../templateFormatter"; @@ -9,6 +9,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { IgnoredEventType, KickOptions, KickResult, ModActionsPluginType } from "../types"; import { getDefaultContactMethods } from "./getDefaultContactMethods"; import { ignoreEvent } from "./ignoreEvent"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Kick the specified server member. Generates a case. @@ -34,7 +35,7 @@ export async function kickMember( guildName: pluginData.guild.name, reason, moderator: kickOptions.caseArgs?.modId - ? userToConfigAccessibleUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) + ? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) : {}, }); @@ -72,11 +73,11 @@ export async function kickMember( // Log the action const mod = await resolveUser(pluginData.client, modId); - pluginData.state.serverLogs.log(LogType.MEMBER_KICK, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(member.user), + pluginData.getPlugin(LogsPlugin).logMemberKick({ + mod, + user: member.user, caseNumber: createdCase.case_number, - reason, + reason: reason ?? "", }); pluginData.state.events.emit("kick", member.id, reason, kickOptions.isAutomodAction); diff --git a/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts b/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts index 935e16a4..ca3c79ef 100644 --- a/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts +++ b/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts @@ -4,7 +4,7 @@ import { GuildPluginData } from "knub"; import moment from "moment-timezone"; import { LogType } from "src/data/LogType"; import { logger } from "src/logger"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { resolveUser, SECONDS } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; @@ -12,6 +12,7 @@ import { IgnoredEventType, ModActionsPluginType } from "../types"; import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; import { ignoreEvent } from "./ignoreEvent"; import { isBanned } from "./isBanned"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const TEMPBAN_LOOP_TIME = 60 * SECONDS; @@ -34,7 +35,7 @@ export async function outdatedTempbansLoop(pluginData: GuildPluginData, @@ -24,7 +25,7 @@ export async function warnMember( guildName: pluginData.guild.name, reason, moderator: warnOptions.caseArgs?.modId - ? userToConfigAccessibleUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) + ? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) : {}, }); const contactMethods = warnOptions?.contactMethods @@ -70,11 +71,11 @@ export async function warnMember( }); const mod = await pluginData.guild.members.fetch(modId as Snowflake); - pluginData.state.serverLogs.log(LogType.MEMBER_WARN, { - mod: memberToConfigAccessibleMember(mod), - member: memberToConfigAccessibleMember(member), + pluginData.getPlugin(LogsPlugin).logMemberWarn({ + mod, + member, caseNumber: createdCase.case_number, - reason, + reason: reason ?? "", }); pluginData.state.events.emit("warn", member.id, reason, warnOptions.isAutomodAction); diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index 3f0d9aae..b845c299 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -70,9 +70,8 @@ export const MutesPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, - defaultOptions, - dependencies: [CasesPlugin, LogsPlugin], + defaultOptions, // prettier-ignore commands: [ diff --git a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts index 7d4ece2d..20611e45 100644 --- a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts +++ b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts @@ -1,8 +1,9 @@ import { Snowflake } from "discord.js"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; import { mutesEvt } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; /** * Reapply active mutes on join @@ -20,8 +21,8 @@ export const ReapplyActiveMuteOnJoinEvt = mutesEvt({ memberRoleLock.unlock(); } - pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, { - member: memberToConfigAccessibleMember(member), + pluginData.getPlugin(LogsPlugin).logMemberMuteRejoin({ + member, }); } }, diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index d812dd9d..40f2fb9f 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -1,10 +1,11 @@ import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; -import { resolveMember, UnknownUser } from "../../../utils"; +import { resolveMember, UnknownUser, verboseUserMention } from "../../../utils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; import { MutesPluginType } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function clearExpiredMutes(pluginData: GuildPluginData) { const expiredMutes = await pluginData.state.mutes.getExpiredMutes(); @@ -32,19 +33,16 @@ export async function clearExpiredMutes(pluginData: GuildPluginData= 0 && zepRoles.some(zepRole => zepRole.position > actualMuteRole.position)) { lock.unlock(); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Cannot mute user ${member.id}: ${e}`, }); throw e; } else { // Otherwise, throw error that mute role is above zeps roles lock.unlock(); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Cannot mute users, specified mute role is above Zeppelin in the role hierarchy`, }); throw new RecoverablePluginError(ERRORS.MUTE_ROLE_ABOVE_ZEP); @@ -156,7 +156,7 @@ export async function muteUser( reason: reason || "None", time: timeUntilUnmute, moderator: muteOptions.caseArgs?.modId - ? userToConfigAccessibleUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) + ? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) : "", })); @@ -224,19 +224,19 @@ export async function muteUser( // Log the action const mod = await resolveUser(pluginData.client, muteOptions.caseArgs?.modId); if (muteTime) { - pluginData.state.serverLogs.log(LogType.MEMBER_TIMED_MUTE, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberTimedMute({ + mod, + user, time: timeUntilUnmute, caseNumber: theCase.case_number, - reason, + reason: reason ?? "", }); } else { - pluginData.state.serverLogs.log(LogType.MEMBER_MUTE, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberMute({ + mod, + user, caseNumber: theCase.case_number, - reason, + reason: reason ?? "", }); } diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index 35fd1880..04ee3d59 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -1,7 +1,7 @@ import { Snowflake } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { resolveMember, resolveUser } from "../../../utils"; @@ -10,6 +10,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CaseArgs } from "../../Cases/types"; import { MutesPluginType, UnmuteResult } from "../types"; import { memberHasMutedRole } from "./memberHasMutedRole"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function unmuteUser( pluginData: GuildPluginData, @@ -87,17 +88,17 @@ export async function unmuteUser( // Log the action const mod = await pluginData.client.users.fetch(modId as Snowflake); if (unmuteTime) { - pluginData.state.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberTimedUnmute({ + mod, + user, caseNumber: createdCase.case_number, time: timeUntilUnmute, reason: caseArgs.reason, }); } else { - pluginData.state.serverLogs.log(LogType.MEMBER_UNMUTE, { - mod: userToConfigAccessibleUser(mod), - user: userToConfigAccessibleUser(user), + pluginData.getPlugin(LogsPlugin).logMemberUnmute({ + mod, + user, caseNumber: createdCase.case_number, reason: caseArgs.reason, }); diff --git a/backend/src/plugins/Persist/events/LoadDataEvt.ts b/backend/src/plugins/Persist/events/LoadDataEvt.ts index 75c3871d..63569679 100644 --- a/backend/src/plugins/Persist/events/LoadDataEvt.ts +++ b/backend/src/plugins/Persist/events/LoadDataEvt.ts @@ -1,6 +1,6 @@ import { GuildMemberEditData, Permissions } from "discord.js"; import intersection from "lodash.intersection"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { canAssignRole } from "../../../utils/canAssignRole"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; @@ -37,7 +37,7 @@ export const LoadDataEvt = persistEvt({ if (config.persisted_roles) requiredPermissions |= p.MANAGE_ROLES; const missingPermissions = getMissingPermissions(me.permissions, requiredPermissions); if (missingPermissions) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing permissions for persist plugin: ${missingPermissionError(missingPermissions)}`, }); return; @@ -47,7 +47,7 @@ export const LoadDataEvt = persistEvt({ if (config.persisted_roles) { for (const roleId of config.persisted_roles) { if (!canAssignRole(pluginData.guild, me, roleId)) { - pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Missing permissions to assign role \`${roleId}\` in persist plugin`, }); return; @@ -74,8 +74,8 @@ export const LoadDataEvt = persistEvt({ await member.edit(toRestore, "Restored upon rejoin"); await pluginData.state.persistedData.clear(member.id); - pluginData.state.logs.log(LogType.MEMBER_RESTORE, { - member: memberToConfigAccessibleMember(member), + pluginData.getPlugin(LogsPlugin).logMemberRestore({ + member, restoredData: restoredData.join(", "), }); } diff --git a/backend/src/plugins/Post/PostPlugin.ts b/backend/src/plugins/Post/PostPlugin.ts index 493f8adb..e6bd9355 100644 --- a/backend/src/plugins/Post/PostPlugin.ts +++ b/backend/src/plugins/Post/PostPlugin.ts @@ -13,6 +13,7 @@ import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd"; import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd"; import { ConfigSchema, PostPluginType } from "./types"; import { scheduledPostLoop } from "./util/scheduledPostLoop"; +import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions: PluginOptions = { config: { @@ -35,7 +36,7 @@ export const PostPlugin = zeppelinGuildPlugin()({ prettyName: "Post", }, - dependencies: [TimeAndDatePlugin], + dependencies: [TimeAndDatePlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index d6865759..388f03a1 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -2,7 +2,7 @@ import { Channel, Message, TextChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { DBDateFormat, errorMessage, MINUTES, StrictMessageContent } from "../../../utils"; @@ -10,6 +10,7 @@ import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { PostPluginType } from "../types"; import { parseScheduleTime } from "./parseScheduleTime"; import { postMessage } from "./postMessage"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const MIN_REPEAT_TIME = 5 * MINUTES; const MAX_REPEAT_TIME = Math.pow(2, 32); @@ -158,19 +159,19 @@ export async function actualPostCmd( }); if (opts.repeat) { - pluginData.state.logs.log(LogType.SCHEDULED_REPEATED_MESSAGE, { - author: userToConfigAccessibleUser(msg.author), - channel: channelToConfigAccessibleChannel(targetChannel), + pluginData.getPlugin(LogsPlugin).logScheduledRepeatedMessage({ + author: msg.author, + channel: targetChannel, datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), repeatInterval: humanizeDuration(opts.repeat), - repeatDetails: repeatDetailsStr, + repeatDetails: repeatDetailsStr!, }); } else { - pluginData.state.logs.log(LogType.SCHEDULED_MESSAGE, { - author: userToConfigAccessibleUser(msg.author), - channel: channelToConfigAccessibleChannel(targetChannel), + pluginData.getPlugin(LogsPlugin).logScheduledMessage({ + author: msg.author, + channel: targetChannel, datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), @@ -184,9 +185,9 @@ export async function actualPostCmd( } if (opts.repeat) { - pluginData.state.logs.log(LogType.REPEATED_MESSAGE, { - author: userToConfigAccessibleUser(msg.author), - channel: channelToConfigAccessibleChannel(targetChannel), + pluginData.getPlugin(LogsPlugin).logRepeatedMessage({ + author: msg.author, + channel: targetChannel, datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), diff --git a/backend/src/plugins/Post/util/scheduledPostLoop.ts b/backend/src/plugins/Post/util/scheduledPostLoop.ts index 766daed6..1c992311 100644 --- a/backend/src/plugins/Post/util/scheduledPostLoop.ts +++ b/backend/src/plugins/Post/util/scheduledPostLoop.ts @@ -1,12 +1,13 @@ import { Snowflake, TextChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { DBDateFormat, SECONDS } from "../../../utils"; import { PostPluginType } from "../types"; import { postMessage } from "./postMessage"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const SCHEDULED_POST_CHECK_INTERVAL = 5 * SECONDS; @@ -30,16 +31,16 @@ export async function scheduledPostLoop(pluginData: GuildPluginData = { config: { @@ -41,6 +42,7 @@ export const RolesPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, + dependencies: [LogsPlugin], defaultOptions, // prettier-ignore diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index 66dc2096..564b9f77 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -1,10 +1,11 @@ import { GuildChannel } from "discord.js"; -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; import { rolesCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const AddRoleCmd = rolesCmd({ trigger: "addrole", @@ -37,7 +38,7 @@ export const AddRoleCmd = rolesCmd({ // Sanity check: make sure the role is configured properly const role = (msg.channel as GuildChannel).guild.roles.cache.get(roleId); if (!role) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); @@ -53,10 +54,10 @@ export const AddRoleCmd = rolesCmd({ await args.member.roles.add(roleId); - pluginData.state.logs.log(LogType.MEMBER_ROLE_ADD, { - member: memberToConfigAccessibleMember(args.member), + pluginData.getPlugin(LogsPlugin).logMemberRoleAdd(LogType.MEMBER_ROLE_ADD, { + member: memberToTemplateSafeMember(args.member), roles: role.name, - mod: userToConfigAccessibleUser(msg.author), + mod: userToTemplateSafeUser(msg.author), }); sendSuccessMessage( diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index 5f15a3f0..998518da 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -1,11 +1,12 @@ import { GuildMember } from "discord.js"; -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; import { rolesCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const MassAddRoleCmd = rolesCmd({ trigger: "massaddrole", @@ -52,7 +53,7 @@ export const MassAddRoleCmd = rolesCmd({ const role = pluginData.guild.roles.cache.get(roleId); if (!role) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); @@ -74,10 +75,10 @@ export const MassAddRoleCmd = rolesCmd({ try { pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, member.id); await member.roles.add(roleId); - pluginData.state.logs.log(LogType.MEMBER_ROLE_ADD, { - member: memberToConfigAccessibleMember(member), - roles: role.name, - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMemberRoleAdd({ + member, + roles: [role], + mod: msg.author, }); assigned++; } catch (e) { diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index 357f8049..1d4c7b0e 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -1,11 +1,12 @@ import { GuildMember } from "discord.js"; -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; import { rolesCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const MassRemoveRoleCmd = rolesCmd({ trigger: "massremoverole", @@ -52,7 +53,7 @@ export const MassRemoveRoleCmd = rolesCmd({ const role = pluginData.guild.roles.cache.get(roleId); if (!role) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); @@ -74,10 +75,10 @@ export const MassRemoveRoleCmd = rolesCmd({ try { pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_REMOVE, member.id); await member.roles.remove(roleId); - pluginData.state.logs.log(LogType.MEMBER_ROLE_REMOVE, { - member: memberToConfigAccessibleMember(member), - roles: role.name, - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMemberRoleRemove({ + member, + roles: [role], + mod: msg.author, }); assigned++; } catch (e) { diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index fdab1e23..d6e36356 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -1,10 +1,11 @@ import { GuildChannel } from "discord.js"; -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; import { rolesCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const RemoveRoleCmd = rolesCmd({ trigger: "removerole", @@ -37,7 +38,7 @@ export const RemoveRoleCmd = rolesCmd({ // Sanity check: make sure the role is configured properly const role = (msg.channel as GuildChannel).guild.roles.cache.get(roleId); if (!role) { - pluginData.state.logs.log(LogType.BOT_ALERT, { + pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); @@ -53,10 +54,10 @@ export const RemoveRoleCmd = rolesCmd({ await args.member.roles.remove(roleId); - pluginData.state.logs.log(LogType.MEMBER_ROLE_REMOVE, { - member: memberToConfigAccessibleMember(args.member), - roles: role.name, - mod: userToConfigAccessibleUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMemberRoleRemove({ + mod: msg.author, + member: args.member, + roles: [role], }); sendSuccessMessage( diff --git a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts index 3a29a711..0e7c651b 100644 --- a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts +++ b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts @@ -1,10 +1,11 @@ import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { isDiscordAPIError, UnknownUser } from "../../../utils"; +import { isDiscordAPIError, UnknownUser, verboseChannelMention, verboseUserMention } from "../../../utils"; import { SlowmodePluginType } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function applyBotSlowmodeToUserId( pluginData: GuildPluginData, @@ -27,16 +28,14 @@ export async function applyBotSlowmodeToUserId( logger.warn( `Missing permissions to apply bot slowmode to user ${userId} on channel ${channel.name} (${channel.id}) on server ${pluginData.guild.name} (${pluginData.guild.id})`, ); - pluginData.state.logs.log(LogType.BOT_ALERT, { - body: `Missing permissions to apply bot slowmode to {userMention(user)} in {channelMention(channel)}`, - user: userToConfigAccessibleUser(user), - channel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Missing permissions to apply bot slowmode to ${verboseUserMention(user)} in ${verboseChannelMention( + channel, + )}`, }); } else { - pluginData.state.logs.log(LogType.BOT_ALERT, { - body: `Failed to apply bot slowmode to {userMention(user)} in {channelMention(channel)}`, - user: userToConfigAccessibleUser(user), - channel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Failed to apply bot slowmode to ${verboseUserMention(user)} in ${verboseChannelMention(channel)}`, }); throw e; } diff --git a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts index f8490e4d..4dcfe634 100644 --- a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts +++ b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts @@ -1,11 +1,12 @@ import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { UnknownUser } from "../../../utils"; +import { UnknownUser, verboseChannelMention, verboseUserMention } from "../../../utils"; import { SlowmodePluginType } from "../types"; import { clearBotSlowmodeFromUserId } from "./clearBotSlowmodeFromUserId"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function clearExpiredSlowmodes(pluginData: GuildPluginData) { const expiredSlowmodeUsers = await pluginData.state.slowmodes.getExpiredSlowmodeUsers(); @@ -25,10 +26,10 @@ export async function clearExpiredSlowmodes(pluginData: GuildPluginData new UnknownUser({ id: user.user_id })); - pluginData.state.logs.log(LogType.BOT_ALERT, { - body: `Failed to clear slowmode permissions from {userMention(user)} in {channelMention(channel)}`, - user: userToConfigAccessibleUser(await realUser), - channel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Failed to clear slowmode permissions from ${verboseUserMention( + await realUser, + )} in ${verboseChannelMention(channel)}`, }); } } diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 097d10b1..78f8c92b 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -40,7 +40,7 @@ export async function onMessageCreate(pluginData: GuildPluginData. ${missingPermissionError(missingPermissions)}`, }); return; diff --git a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts index e34b34c2..17d9c748 100644 --- a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts @@ -1,10 +1,7 @@ import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; -import { - channelToConfigAccessibleChannel, - memberToConfigAccessibleMember, -} from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; @@ -95,7 +92,7 @@ export async function logAndDetectMessageSpam( ); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Failed to mute <@!${member.id}> in \`spam\` plugin because a mute role has not been specified in server config`, }); } else { @@ -181,9 +178,9 @@ export async function logAndDetectMessageSpam( } // Create a log entry - logs.log(LogType.MESSAGE_SPAM_DETECTED, { - member: memberToConfigAccessibleMember(member!), - channel: channelToConfigAccessibleChannel(channel!), + logs.logMessageSpamDetected({ + member: member!, + channel: channel!, description, limit: spamConfig.count, interval: spamConfig.interval, diff --git a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts index b9d5fd6d..4a1256d1 100644 --- a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts @@ -1,5 +1,5 @@ import { GuildPluginData } from "knub"; -import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; @@ -57,7 +57,7 @@ export async function logAndDetectOtherSpam( ); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: `Failed to mute <@!${member.id}> in \`spam\` plugin because a mute role has not been specified in server config`, }); } else { @@ -78,8 +78,8 @@ export async function logAndDetectOtherSpam( // Clear recent cases clearRecentUserActions(pluginData, RecentActionType.VoiceChannelMove, userId, actionGroupId); - logs.log(LogType.OTHER_SPAM_DETECTED, { - member: memberToConfigAccessibleMember(member!), + logs.logOtherSpamDetected({ + member: member!, description, limit: spamConfig.count, interval: spamConfig.interval, diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 0ea369a0..0952eace 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -21,6 +21,7 @@ import { findTagByName } from "./util/findTagByName"; import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageDelete } from "./util/onMessageDelete"; import { renderTagBody } from "./util/renderTagBody"; +import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions: PluginOptions = { config: { @@ -60,6 +61,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, + dependencies: [LogsPlugin], defaultOptions, // prettier-ignore diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index 9ce1bf00..ee8c340f 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -1,4 +1,4 @@ -import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; import { TemplateParseError } from "../../../templateFormatter"; @@ -20,8 +20,8 @@ export const TagEvalCmd = tagsCmd({ args.body, [], { - member: memberToConfigAccessibleMember(msg.member), - user: userToConfigAccessibleUser(msg.member.user), + member: memberToTemplateSafeMember(msg.member), + user: userToTemplateSafeUser(msg.member.user), }, { member: msg.member }, ); diff --git a/backend/src/plugins/Tags/util/onMessageCreate.ts b/backend/src/plugins/Tags/util/onMessageCreate.ts index 43448df5..46dcf570 100644 --- a/backend/src/plugins/Tags/util/onMessageCreate.ts +++ b/backend/src/plugins/Tags/util/onMessageCreate.ts @@ -8,6 +8,7 @@ import { messageIsEmpty } from "../../../utils/messageIsEmpty"; import { validate } from "../../../validatorUtils"; import { TagsPluginType } from "../types"; import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; @@ -87,14 +88,14 @@ export async function onMessageCreate(pluginData: GuildPluginData = { config: { @@ -118,7 +119,7 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ prettyName: "Utility", }, - dependencies: [TimeAndDatePlugin, ModActionsPlugin], + dependencies: [TimeAndDatePlugin, ModActionsPlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index c7f8e6c3..0d42bf8c 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -1,7 +1,7 @@ import { Message, Snowflake, TextChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; -import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; @@ -11,6 +11,7 @@ import { allowTimeout } from "../../../RegExpRunner"; import { DAYS, getInviteCodesInString, noop, SECONDS } from "../../../utils"; import { utilityCmd, UtilityPluginType } from "../types"; import { boolean, number } from "io-ts"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; const MAX_CLEAN_COUNT = 150; const MAX_CLEAN_TIME = 1 * DAYS; @@ -42,9 +43,9 @@ export async function cleanMessages( const baseUrl = getBaseUrl(pluginData); const archiveUrl = pluginData.state.archives.getUrl(baseUrl, archiveId); - pluginData.state.logs.log(LogType.CLEAN, { - mod: userToConfigAccessibleUser(mod), - channel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logClean({ + mod, + channel, count: savedMessages.length, archiveUrl, }); diff --git a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts index 5bb69961..def3201c 100644 --- a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts +++ b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts @@ -1,13 +1,14 @@ import { VoiceChannel } from "discord.js"; import { - channelToConfigAccessibleChannel, - memberToConfigAccessibleMember, - userToConfigAccessibleUser, -} from "../../../utils/configAccessibleObjects"; + channelToTemplateSafeChannel, + memberToTemplateSafeMember, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { utilityCmd } from "../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const VcdisconnectCmd = utilityCmd({ trigger: ["vcdisconnect", "vcdisc", "vcdc", "vckick", "vck"], @@ -38,10 +39,10 @@ export const VcdisconnectCmd = utilityCmd({ return; } - pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_DISCONNECT, { - mod: userToConfigAccessibleUser(msg.author), - member: memberToConfigAccessibleMember(args.member), - oldChannel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logVoiceChannelForceDisconnect({ + mod: msg.author, + member: args.member, + oldChannel: channel, }); sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** disconnected from **${channel.name}**`); diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index f2feb8ef..e7a834a7 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -1,15 +1,16 @@ import { Snowflake, VoiceChannel } from "discord.js"; import { - channelToConfigAccessibleChannel, - memberToConfigAccessibleMember, - userToConfigAccessibleUser, -} from "../../../utils/configAccessibleObjects"; + channelToTemplateSafeChannel, + memberToTemplateSafeMember, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { channelMentionRegex, isSnowflake, simpleClosestStringMatch } from "../../../utils"; import { utilityCmd } from "../types"; import { ChannelTypeStrings } from "../../../types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; export const VcmoveCmd = utilityCmd({ trigger: "vcmove", @@ -79,11 +80,11 @@ export const VcmoveCmd = utilityCmd({ return; } - pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_MOVE, { - mod: userToConfigAccessibleUser(msg.author), - member: memberToConfigAccessibleMember(args.member), - oldChannel: channelToConfigAccessibleChannel(oldVoiceChannel!), - newChannel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logVoiceChannelForceMove({ + mod: msg.author, + member: args.member, + oldChannel: oldVoiceChannel!, + newChannel: channel, }); sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** moved to **${channel.name}**`); @@ -179,11 +180,11 @@ export const VcmoveAllCmd = utilityCmd({ continue; } - pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_MOVE, { - mod: userToConfigAccessibleUser(msg.author), - member: memberToConfigAccessibleMember(currMember), - oldChannel: channelToConfigAccessibleChannel(args.oldChannel), - newChannel: channelToConfigAccessibleChannel(channel), + pluginData.getPlugin(LogsPlugin).logVoiceChannelForceMove({ + mod: msg.author, + member: currMember, + oldChannel: args.oldChannel, + newChannel: channel, }); } diff --git a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts index c311c794..400eff28 100644 --- a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts +++ b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts @@ -3,6 +3,7 @@ import { GuildLogs } from "../../data/GuildLogs"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { SendWelcomeMessageEvt } from "./events/SendWelcomeMessageEvt"; import { ConfigSchema, WelcomeMessagePluginType } from "./types"; +import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions: PluginOptions = { config: { @@ -20,6 +21,7 @@ export const WelcomeMessagePlugin = zeppelinGuildPlugin; +export type TemplateSafeValue = + | string + | number + | boolean + | null + | undefined + | ((...args: any[]) => TemplateSafeValue | Promise) + | TemplateSafeValueContainer + | TemplateSafeValue[]; + +function isTemplateSafeValue(value: unknown): value is TemplateSafeValue { + return ( + value == null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" || + typeof value === "function" || + (Array.isArray(value) && value.every(v => isTemplateSafeValue(v))) || + value instanceof TemplateSafeValueContainer + ); +} + +export class TemplateSafeValueContainer { + [key: string]: TemplateSafeValue; + + constructor(data: Record = {}) { + for (const [key, value] of Object.entries(data)) { + if (!isTemplateSafeValue(value)) { + throw new Error(`Unsafe value for key "${key}" in SafeTemplateValueContainer`); + } + + this[key] = value; + } + } +} + +export type TypedTemplateSafeValueContainer = TemplateSafeValueContainer & T; + +export function createTypedTemplateSafeValueContainer>( + data: T, +): TypedTemplateSafeValueContainer { + return new TemplateSafeValueContainer(data) as TypedTemplateSafeValueContainer; +} + function cleanUpParseResult(arr) { arr.forEach(item => { if (typeof item === "object") { @@ -218,7 +262,14 @@ export function parseTemplate(str: string): ParsedTemplate { return result; } -async function evaluateTemplateVariable(theVar: ITemplateVar, values) { +async function evaluateTemplateVariable( + theVar: ITemplateVar, + values: TemplateSafeValueContainer, +): Promise { + if (!(values instanceof TemplateSafeValueContainer)) { + throw new Error("evaluateTemplateVariable() called with unsafe values"); + } + const value = has(values, theVar.identifier) ? get(values, theVar.identifier) : undefined; if (typeof value === "function") { @@ -238,13 +289,17 @@ async function evaluateTemplateVariable(theVar: ITemplateVar, values) { } const result = await value(...args); + if (!isTemplateSafeValue(result)) { + throw new Error(`Template function ${theVar.identifier} returned unsafe value`); + } + return result == null ? "" : result; } return value == null ? "" : value; } -export async function renderParsedTemplate(parsedTemplate: ParsedTemplate, values: any) { +export async function renderParsedTemplate(parsedTemplate: ParsedTemplate, values: TemplateSafeValueContainer) { let result = ""; for (const part of parsedTemplate) { @@ -380,9 +435,13 @@ const baseValues = { }, }; -export async function renderTemplate(template: string, values = {}, includeBaseValues = true) { +export async function renderTemplate( + template: string, + values: TemplateSafeValueContainer = new TemplateSafeValueContainer(), + includeBaseValues = true, +) { if (includeBaseValues) { - values = Object.assign({}, baseValues, values); + values = new TemplateSafeValueContainer(Object.assign({}, baseValues, values)); } let parseResult: ParsedTemplate; diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 370bf048..54f16c46 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -37,7 +37,7 @@ import moment from "moment-timezone"; import tlds from "tlds"; import tmp from "tmp"; import { URL } from "url"; -import { SavedMessage } from "./data/entities/SavedMessage"; +import { ISavedMessageAttachmentData, SavedMessage } from "./data/entities/SavedMessage"; import { SimpleCache } from "./SimpleCache"; import { ChannelTypeStrings } from "./types"; import { sendDM } from "./utils/sendDM"; @@ -1332,7 +1332,7 @@ export function messageSummary(msg: SavedMessage) { if (msg.data.attachments) { result += "Attachments:\n" + - msg.data.attachments.map((a: MessageAttachment) => disableLinkPreviews(a.url)).join("\n") + + msg.data.attachments.map((a: ISavedMessageAttachmentData) => disableLinkPreviews(a.url)).join("\n") + "\n"; } @@ -1355,7 +1355,7 @@ export function verboseUserName(user: User | UnknownUser): string { return `**${user.tag}** (\`${user.id}\`)`; } -export function verboseChannelMention(channel: GuildChannel): string { +export function verboseChannelMention(channel: GuildChannel | ThreadChannel): string { const plainTextName = channel.type === ChannelTypeStrings.VOICE || channel.type === ChannelTypeStrings.STAGE ? channel.name @@ -1500,4 +1500,9 @@ export function unique(arr: T[]): T[] { return Array.from(new Set(arr)); } +// From https://github.com/microsoft/TypeScript/pull/29955#issuecomment-470062531 +export function isTruthy(value: T): value is Exclude { + return Boolean(value); +} + export const DBDateFormat = "YYYY-MM-DD HH:mm:ss"; diff --git a/backend/src/utils/configAccessibleObjects.ts b/backend/src/utils/configAccessibleObjects.ts deleted file mode 100644 index 0a776629..00000000 --- a/backend/src/utils/configAccessibleObjects.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - Emoji, - GuildChannel, - GuildMember, - PartialGuildMember, - Role, - Snowflake, - StageInstance, - Sticker, - ThreadChannel, - User, -} from "discord.js"; -import { UnknownUser } from "src/utils"; -import { GuildPluginData } from "knub"; - -export interface IConfigAccessibleUser { - id: Snowflake | string; - username: string; - discriminator: string; - mention: string; - tag: string; - avatarURL?: string; - bot?: boolean; - createdAt?: number; -} - -export interface IConfigAccessibleRole { - id: Snowflake; - name: string; - createdAt: number; - hexColor: string; - hoist: boolean; -} - -export interface IConfigAccessibleMember extends IConfigAccessibleUser { - user: IConfigAccessibleUser; - nick: string; - roles: IConfigAccessibleRole[]; - joinedAt?: number; - // guildAvatarURL: string, Once DJS supports per-server avatars - guildName: string; -} - -export function userToConfigAccessibleUser(user: User | UnknownUser): IConfigAccessibleUser { - if (user.tag === "Unknown#0000") { - const toReturnPartial: IConfigAccessibleUser = { - id: user.id, - username: "Unknown", - discriminator: "0000", - mention: `<@${user.id}>`, - tag: "Unknown#0000", - }; - - return toReturnPartial; - } - - const properUser = user as User; - const toReturn: IConfigAccessibleUser = { - id: properUser.id, - username: properUser.username, - discriminator: properUser.discriminator, - mention: `<@${properUser.id}>`, - tag: properUser.tag, - avatarURL: properUser.displayAvatarURL({ dynamic: true }), - bot: properUser.bot, - createdAt: properUser.createdTimestamp, - }; - - return toReturn; -} - -export function roleToConfigAccessibleRole(role: Role): IConfigAccessibleRole { - const toReturn: IConfigAccessibleRole = { - id: role.id, - name: role.name, - createdAt: role.createdTimestamp, - hexColor: role.hexColor, - hoist: role.hoist, - }; - - return toReturn; -} - -export function memberToConfigAccessibleMember(member: GuildMember | PartialGuildMember): IConfigAccessibleMember { - const user = userToConfigAccessibleUser(member.user!); - - const toReturn: IConfigAccessibleMember = { - ...user, - user, - nick: member.nickname ?? "*None*", - roles: [...member.roles.cache.mapValues(r => roleToConfigAccessibleRole(r)).values()], - joinedAt: member.joinedTimestamp ?? undefined, - guildName: member.guild.name, - }; - - return toReturn; -} - -export interface IConfigAccessibleChannel { - id: Snowflake; - name: string; - mention: string; - parentId?: Snowflake; -} - -export function channelToConfigAccessibleChannel(channel: GuildChannel | ThreadChannel): IConfigAccessibleChannel { - const toReturn: IConfigAccessibleChannel = { - id: channel.id, - name: channel.name, - mention: `<#${channel.id}>`, - parentId: channel.parentId ?? undefined, - }; - - return toReturn; -} - -export interface IConfigAccessibleStage { - channelId: Snowflake; - channelMention: string; - createdAt: number; - discoverable: boolean; - topic: string; -} - -export function stageToConfigAccessibleStage(stage: StageInstance): IConfigAccessibleStage { - const toReturn: IConfigAccessibleStage = { - channelId: stage.channelId, - channelMention: `<#${stage.channelId}>`, - createdAt: stage.createdTimestamp, - discoverable: !stage.discoverableDisabled, - topic: stage.topic, - }; - - return toReturn; -} - -export interface IConfigAccessibleEmoji { - id: Snowflake; - name: string; - createdAt?: number; - animated: boolean; - identifier: string; - mention: string; -} - -export function emojiToConfigAccessibleEmoji(emoji: Emoji): IConfigAccessibleEmoji { - const toReturn: IConfigAccessibleEmoji = { - id: emoji.id!, - name: emoji.name!, - createdAt: emoji.createdTimestamp ?? undefined, - animated: emoji.animated ?? false, - identifier: emoji.identifier, - mention: emoji.animated ? `` : `<:${emoji.name}:${emoji.id}>`, - }; - - return toReturn; -} - -export interface IConfigAccessibleSticker { - id: Snowflake; - guildId?: Snowflake; - packId?: Snowflake; - name: string; - description: string; - tags: string; - format: string; - animated: boolean; - url: string; -} - -export function stickerToConfigAccessibleSticker(sticker: Sticker): IConfigAccessibleSticker { - const toReturn: IConfigAccessibleSticker = { - id: sticker.id, - guildId: sticker.guildId ?? undefined, - packId: sticker.packId ?? undefined, - name: sticker.name, - description: sticker.description ?? "", - tags: sticker.tags?.join(", ") ?? "", - format: sticker.format, - animated: sticker.format === "PNG" ? false : true, - url: sticker.url, - }; - - return toReturn; -} - -export function getConfigAccessibleMemberLevel( - pluginData: GuildPluginData, - member: IConfigAccessibleMember, -): number { - if (member.id === pluginData.guild.ownerId) { - return 99999; - } - - const levels = pluginData.fullConfig.levels ?? {}; - for (const [id, level] of Object.entries(levels)) { - if (member.id === id || member.roles?.find(r => r.id === id)) { - return level as number; - } - } - - return 0; -} diff --git a/backend/src/utils/safeFindRelevantAuditLogEntry.ts b/backend/src/utils/safeFindRelevantAuditLogEntry.ts index 8845e52e..717c9923 100644 --- a/backend/src/utils/safeFindRelevantAuditLogEntry.ts +++ b/backend/src/utils/safeFindRelevantAuditLogEntry.ts @@ -19,7 +19,7 @@ export async function safeFindRelevantAuditLogEntry( } catch (e) { if (isDiscordAPIError(e) && e.code === 50013) { const logs = pluginData.getPlugin(LogsPlugin); - logs.log(LogType.BOT_ALERT, { + logs.logBotAlert({ body: "Missing permissions to read audit log", }); return; diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts new file mode 100644 index 00000000..47bcf3de --- /dev/null +++ b/backend/src/utils/templateSafeObjects.ts @@ -0,0 +1,444 @@ +import { + Emoji, + Guild, + GuildChannel, + GuildMember, + PartialGuildMember, + Role, + Snowflake, + StageInstance, + Sticker, + ThreadChannel, + User, +} from "discord.js"; +import { UnknownUser } from "src/utils"; +import { GuildPluginData } from "knub"; +import { TemplateSafeValueContainer, TypedTemplateSafeValueContainer } from "../templateFormatter"; +import { + ISavedMessageAttachmentData, + ISavedMessageData, + ISavedMessageEmbedData, + ISavedMessageStickerData, + SavedMessage, +} from "../data/entities/SavedMessage"; +import { Case } from "../data/entities/Case"; + +type Props = { + [K in keyof T]: T[K]; +}; + +export class TemplateSafeGuild extends TemplateSafeValueContainer { + id: Snowflake; + name: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeUser extends TemplateSafeValueContainer { + id: Snowflake | string; + username: string; + discriminator: string; + mention: string; + tag: string; + avatarURL?: string; + bot?: boolean; + createdAt?: number; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeUnknownUser extends TemplateSafeValueContainer { + id: Snowflake; + username: string; + discriminator: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeRole extends TemplateSafeValueContainer { + id: Snowflake; + name: string; + createdAt: number; + hexColor: string; + hoist: boolean; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeMember extends TemplateSafeUser { + user: TemplateSafeUser; + nick: string; + roles: TemplateSafeRole[]; + joinedAt?: number; + // guildAvatarURL: string, Once DJS supports per-server avatars + guildName: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeUnknownMember extends TemplateSafeUnknownUser { + user: TemplateSafeUnknownUser; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeChannel extends TemplateSafeValueContainer { + id: Snowflake; + name: string; + mention: string; + parentId?: Snowflake; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeStage extends TemplateSafeValueContainer { + channelId: Snowflake; + channelMention: string; + createdAt: number; + discoverable: boolean; + topic: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeEmoji extends TemplateSafeValueContainer { + id: Snowflake; + name: string; + createdAt?: number; + animated: boolean; + identifier: string; + mention: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeSticker extends TemplateSafeValueContainer { + id: Snowflake; + guildId?: Snowflake; + packId?: Snowflake; + name: string; + description: string; + tags: string; + format: string; + animated: boolean; + url: string; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeSavedMessage extends TemplateSafeValueContainer { + id: string; + guild_id: string; + channel_id: string; + user_id: string; + is_bot: boolean; + data: TemplateSafeSavedMessageData; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeSavedMessageData extends TemplateSafeValueContainer { + attachments?: Array>; + author: TypedTemplateSafeValueContainer<{ + username: string; + discriminator: string; + }>; + content: string; + embeds?: Array>; + stickers?: Array>; + timestamp: number; + + constructor(data: Props) { + super(data); + } +} + +export class TemplateSafeCase extends TemplateSafeValueContainer { + id: number; + guild_id: string; + case_number: number; + user_id: string; + user_name: string; + mod_id: string | null; + mod_name: string | null; + type: number; + audit_log_id: string | null; + created_at: string; + is_hidden: boolean; + pp_id: string | null; + pp_name: string | null; + log_message_id: string | null; + + constructor(data: Props) { + super(data); + } +} + +// =================== +// CONVERTER FUNCTIONS +// =================== + +export function guildToTemplateSafeGuild(guild: Guild): TemplateSafeGuild { + return new TemplateSafeGuild({ + id: guild.id, + name: guild.name, + }); +} + +export function userToTemplateSafeUser(user: User | UnknownUser): TemplateSafeUser { + if (user instanceof UnknownUser) { + return new TemplateSafeUser({ + id: user.id, + username: "Unknown", + discriminator: "0000", + mention: `<@${user.id}>`, + tag: "Unknown#0000", + }); + } + + return new TemplateSafeUser({ + id: user.id, + username: user.username, + discriminator: user.discriminator, + mention: `<@${user.id}>`, + tag: user.tag, + avatarURL: user.displayAvatarURL({ dynamic: true }), + bot: user.bot, + createdAt: user.createdTimestamp, + }); +} + +export function roleToTemplateSafeRole(role: Role): TemplateSafeRole { + return new TemplateSafeRole({ + id: role.id, + name: role.name, + createdAt: role.createdTimestamp, + hexColor: role.hexColor, + hoist: role.hoist, + }); +} + +export function memberToTemplateSafeMember(member: GuildMember | PartialGuildMember): TemplateSafeMember { + const templateSafeUser = userToTemplateSafeUser(member.user!); + + return new TemplateSafeMember({ + ...templateSafeUser, + user: templateSafeUser, + nick: member.nickname ?? "*None*", + roles: [...member.roles.cache.mapValues(r => roleToTemplateSafeRole(r)).values()], + joinedAt: member.joinedTimestamp ?? undefined, + guildName: member.guild.name, + }); +} + +export function channelToTemplateSafeChannel(channel: GuildChannel | ThreadChannel): TemplateSafeChannel { + return new TemplateSafeChannel({ + id: channel.id, + name: channel.name, + mention: `<#${channel.id}>`, + parentId: channel.parentId ?? undefined, + }); +} + +export function stageToTemplateSafeStage(stage: StageInstance): TemplateSafeStage { + return new TemplateSafeStage({ + channelId: stage.channelId, + channelMention: `<#${stage.channelId}>`, + createdAt: stage.createdTimestamp, + discoverable: !stage.discoverableDisabled, + topic: stage.topic, + }); +} + +export function emojiToTemplateSafeEmoji(emoji: Emoji): TemplateSafeEmoji { + return new TemplateSafeEmoji({ + id: emoji.id!, + name: emoji.name!, + createdAt: emoji.createdTimestamp ?? undefined, + animated: emoji.animated ?? false, + identifier: emoji.identifier, + mention: emoji.animated ? `` : `<:${emoji.name}:${emoji.id}>`, + }); +} + +export function stickerToTemplateSafeSticker(sticker: Sticker): TemplateSafeSticker { + return new TemplateSafeSticker({ + id: sticker.id, + guildId: sticker.guildId ?? undefined, + packId: sticker.packId ?? undefined, + name: sticker.name, + description: sticker.description ?? "", + tags: sticker.tags?.join(", ") ?? "", + format: sticker.format, + animated: sticker.format === "PNG" ? false : true, + url: sticker.url, + }); +} + +export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessage): TemplateSafeSavedMessage { + return new TemplateSafeSavedMessage({ + id: savedMessage.id, + channel_id: savedMessage.channel_id, + guild_id: savedMessage.guild_id, + is_bot: savedMessage.is_bot, + user_id: savedMessage.user_id, + + data: new TemplateSafeSavedMessageData({ + attachments: (savedMessage.data.attachments ?? []).map( + att => + new TemplateSafeValueContainer({ + id: att.id, + contentType: att.contentType, + name: att.name, + proxyURL: att.proxyURL, + size: att.size, + spoiler: att.spoiler, + url: att.url, + width: att.width, + }) as TypedTemplateSafeValueContainer, + ), + + author: new TemplateSafeValueContainer({ + username: savedMessage.data.author.username, + discriminator: savedMessage.data.author.discriminator, + }) as TypedTemplateSafeValueContainer, + + content: savedMessage.data.content, + + embeds: (savedMessage.data.embeds ?? []).map( + embed => + new TemplateSafeValueContainer({ + title: embed.title, + description: embed.description, + url: embed.url, + timestamp: embed.timestamp, + color: embed.color, + + fields: (embed.fields ?? []).map( + field => + new TemplateSafeValueContainer({ + name: field.name, + value: field.value, + inline: field.inline, + }), + ), + + author: embed.author + ? new TemplateSafeValueContainer({ + name: embed.author?.name, + url: embed.author?.url, + iconURL: embed.author?.iconURL, + proxyIconURL: embed.author?.proxyIconURL, + }) + : undefined, + + thumbnail: embed.thumbnail + ? new TemplateSafeValueContainer({ + url: embed.thumbnail?.url, + proxyURL: embed.thumbnail?.url, + height: embed.thumbnail?.height, + width: embed.thumbnail?.width, + }) + : undefined, + + image: embed.image + ? new TemplateSafeValueContainer({ + url: embed.image?.url, + proxyURL: embed.image?.url, + height: embed.image?.height, + width: embed.image?.width, + }) + : undefined, + + video: embed.video + ? new TemplateSafeValueContainer({ + url: embed.video?.url, + proxyURL: embed.video?.url, + height: embed.video?.height, + width: embed.video?.width, + }) + : undefined, + + footer: embed.footer + ? new TemplateSafeValueContainer({ + text: embed.footer.text, + iconURL: embed.footer.iconURL, + proxyIconURL: embed.footer.proxyIconURL, + }) + : undefined, + }) as TypedTemplateSafeValueContainer, + ), + + stickers: (savedMessage.data.stickers ?? []).map( + sticker => + new TemplateSafeValueContainer({ + format: sticker.format, + guildId: sticker.guildId, + id: sticker.id, + name: sticker.name, + description: sticker.description, + available: sticker.available, + type: sticker.type, + }) as TypedTemplateSafeValueContainer, + ), + + timestamp: savedMessage.data.timestamp, + }), + }); +} + +export function caseToTemplateSafeCase(theCase: Case): TemplateSafeCase { + return new TemplateSafeCase({ + id: theCase.id, + guild_id: theCase.guild_id, + case_number: theCase.case_number, + user_id: theCase.user_id, + user_name: theCase.user_name, + mod_id: theCase.mod_id, + mod_name: theCase.mod_name, + type: theCase.type, + audit_log_id: theCase.audit_log_id, + created_at: theCase.created_at, + is_hidden: theCase.is_hidden, + pp_id: theCase.pp_id, + pp_name: theCase.pp_name, + log_message_id: theCase.log_message_id, + }); +} + +export function getTemplateSafeMemberLevel(pluginData: GuildPluginData, member: TemplateSafeMember): number { + if (member.id === pluginData.guild.ownerId) { + return 99999; + } + + const levels = pluginData.fullConfig.levels ?? {}; + for (const [id, level] of Object.entries(levels)) { + if (member.id === id || member.roles?.find(r => r.id === id)) { + return level as number; + } + } + + return 0; +}