From 7cf75f32554e12d7844828bce1484d387efb7d90 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Sun, 15 Aug 2021 01:09:04 +0200 Subject: [PATCH 001/191] Change ContextMenu to hardcoded default actions Sadge (More complex self-defined stuff coming at some point tho) --- .../plugins/ContextMenus/ContextMenuPlugin.ts | 45 +++---- .../ContextMenus/actions/availableActions.ts | 19 --- .../src/plugins/ContextMenus/actions/clean.ts | 96 +++++++-------- .../src/plugins/ContextMenus/actions/mute.ts | 115 +++++++----------- .../plugins/ContextMenus/actions/userInfo.ts | 21 ++++ backend/src/plugins/ContextMenus/helpers.ts | 25 ---- backend/src/plugins/ContextMenus/types.ts | 33 ++--- .../ContextMenus/utils/contextRouter.ts | 19 +-- .../utils/hardcodedContextOptions.ts | 23 ++++ .../ContextMenus/utils/loadAllCommands.ts | 19 +-- .../utils/resolveActionContactMethods.ts | 32 ----- backend/src/plugins/Utility/UtilityPlugin.ts | 8 ++ 12 files changed, 183 insertions(+), 272 deletions(-) delete mode 100644 backend/src/plugins/ContextMenus/actions/availableActions.ts create mode 100644 backend/src/plugins/ContextMenus/actions/userInfo.ts delete mode 100644 backend/src/plugins/ContextMenus/helpers.ts create mode 100644 backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts delete mode 100644 backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index ee4a663a..d9952d48 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -1,38 +1,34 @@ import { PluginOptions } from "knub"; -import { StrictValidationError } from "src/validatorUtils"; -import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { availableTypes } from "./actions/availableActions"; import { ContextClickedEvt } from "./events/ContextClickedEvt"; import { ConfigSchema, ContextMenuPluginType } from "./types"; import { loadAllCommands } from "./utils/loadAllCommands"; +import { UtilityPlugin } from "../Utility/UtilityPlugin"; const defaultOptions: PluginOptions = { config: { - context_actions: {}, + can_use: false, + + user_muteindef: false, + user_mute1d: false, + user_mute1h: false, + user_info: false, + + message_clean10: false, + message_clean25: false, + message_clean50: false, }, -}; - -const configPreprocessor: ConfigPreprocessorFn = options => { - if (options.config.context_actions) { - for (const [name, contextMenu] of Object.entries(options.config.context_actions)) { - if (Object.entries(contextMenu.action).length !== 1) { - throw new StrictValidationError([`Invalid value for context_actions/${name}: Must have exactly one action.`]); - } - - const actionName = Object.entries(contextMenu.action)[0][0]; - if (!availableTypes[actionName].includes(contextMenu.type)) { - throw new StrictValidationError([ - `Invalid value for context_actions/${name}/${actionName}: ${actionName} is not allowed on type ${contextMenu.type}.`, - ]); - } - } - } - - return options; + overrides: [ + { + level: ">=50", + config: { + can_use: true, + }, + }, + ], }; export const ContextMenuPlugin = zeppelinGuildPlugin()({ @@ -40,7 +36,6 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ configSchema: ConfigSchema, defaultOptions, - configPreprocessor, // prettier-ignore events: [ @@ -57,5 +52,5 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ loadAllCommands(pluginData); }, - dependencies: [MutesPlugin, LogsPlugin], + dependencies: [MutesPlugin, LogsPlugin, UtilityPlugin], }); diff --git a/backend/src/plugins/ContextMenus/actions/availableActions.ts b/backend/src/plugins/ContextMenus/actions/availableActions.ts deleted file mode 100644 index 24816790..00000000 --- a/backend/src/plugins/ContextMenus/actions/availableActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as t from "io-ts"; -import { ContextActionBlueprint } from "../helpers"; -import { CleanAction } from "./clean"; -import { MuteAction } from "./mute"; - -export const availableActions: Record> = { - mute: MuteAction, - clean: CleanAction, -}; - -export const AvailableActions = t.type({ - mute: MuteAction.configType, - clean: CleanAction.configType, -}); - -export const availableTypes: Record = { - mute: ["USER"], - clean: ["MESSAGE"], -}; diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 8184d480..55ff9d4e 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -1,64 +1,52 @@ -import { TextChannel } from "discord.js"; -import * as t from "io-ts"; -import { canActOn } from "src/pluginUtils"; +import { ContextMenuInteraction, TextChannel } from "discord.js"; +import { GuildPluginData } from "knub"; import { LogType } from "../../../data/LogType"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { tNullable } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { contextMenuAction } from "../helpers"; +import { ContextMenuPluginType } from "../types"; -export const CleanAction = contextMenuAction({ - configType: t.type({ - amount: tNullable(t.number), - targetUserOnly: tNullable(t.boolean), - "delete-pins": tNullable(t.boolean), - }), +export async function cleanAction( + pluginData: GuildPluginData, + amount: number, + interaction: ContextMenuInteraction, +) { + interaction.deferReply({ ephemeral: true }); + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); - defaultConfig: { - amount: 10, - targetUserOnly: false, - "delete-pins": false, - }, + // TODO: Add perm check for can_clean in util + if (!userCfg.can_use) { + await interaction.followUp({ content: "Cannot clean: insufficient permissions" }); + return; + } - async apply({ pluginData, actionConfig, actionName, interaction }) { - interaction.deferReply({ ephemeral: true }); - const targetMessage = interaction.channel - ? await interaction.channel.messages.fetch(interaction.targetId) - : await (pluginData.guild.channels.resolve(interaction.channelId) as TextChannel).messages.fetch( - interaction.targetId, - ); - - const amount = actionConfig.amount ?? 10; - const targetUserOnly = actionConfig.targetUserOnly ?? false; - const deletePins = actionConfig["delete-pins"] ?? false; - - const user = targetUserOnly ? targetMessage.author.id : undefined; - const targetMember = await pluginData.guild.members.fetch(targetMessage.author.id); - const executingMember = await pluginData.guild.members.fetch(interaction.user.id); - const utility = pluginData.getPlugin(UtilityPlugin); - - if (targetUserOnly && !canActOn(pluginData, executingMember, targetMember)) { - interaction.followUp({ ephemeral: true, content: "Cannot clean users messages: insufficient permissions" }); - return; - } - - try { - interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`); - utility.clean( - { count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, - targetMessage, + const targetMessage = interaction.channel + ? await interaction.channel.messages.fetch(interaction.targetId) + : await (pluginData.guild.channels.resolve(interaction.channelId) as TextChannel).messages.fetch( + interaction.targetId, ); - } catch (e) { - 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, { - body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`${actionName}\``, - }); - } else { - throw e; - } + const targetUserOnly = false; + const deletePins = false; + const user = undefined; + + try { + interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`); + const utility = pluginData.getPlugin(UtilityPlugin); + utility.clean({ count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, targetMessage); + } catch (e) { + 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, { + body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`clean\`:_ ${e}`, + }); + } else { + throw e; } - }, -}); + } +} diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 00a9c131..af9b71ed 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -1,83 +1,60 @@ import humanizeDuration from "humanize-duration"; -import * as t from "io-ts"; +import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; import { LogType } from "../../../data/LogType"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { convertDelayStringToMS, tDelayString, tNullable } from "../../../utils"; +import { convertDelayStringToMS } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin"; -import { contextMenuAction } from "../helpers"; -import { resolveActionContactMethods } from "../utils/resolveActionContactMethods"; +import { ContextMenuPluginType } from "../types"; -export const MuteAction = contextMenuAction({ - configType: t.type({ - reason: tNullable(t.string), - duration: tNullable(tDelayString), - notify: tNullable(t.string), - notifyChannel: tNullable(t.string), - remove_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), - restore_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), - postInCaseLog: tNullable(t.boolean), - hide_case: tNullable(t.boolean), - }), +export async function muteAction(pluginData: GuildPluginData, duration, interaction) { + interaction.deferReply({ ephemeral: true }); + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); - defaultConfig: { - notify: null, // Use defaults from ModActions - hide_case: false, - }, + // TODO: Add perm check for can_mute + if (!userCfg.can_use) { + await interaction.followUp({ content: "Cannot mute: insufficient permissions" }); + return; + } - async apply({ pluginData, actionConfig, actionName, interaction }) { - const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined; - const reason = actionConfig.reason || "Context Menu Action"; - const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; - const rolesToRemove = actionConfig.remove_roles_on_mute; - const rolesToRestore = actionConfig.restore_roles_on_mute; + const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; + const mutes = pluginData.getPlugin(MutesPlugin); + const userId = interaction.targetId; + const targetMember = await pluginData.guild.members.fetch(interaction.targetId); - const caseArgs: Partial = { - modId: pluginData.client.user!.id, - automatic: true, - postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, - hide: Boolean(actionConfig.hide_case), - }; + if (!canActOn(pluginData, executingMember, targetMember)) { + interaction.followUp({ ephemeral: true, content: "Cannot mute: insufficient permissions" }); + return; + } - interaction.deferReply({ ephemeral: true }); - const mutes = pluginData.getPlugin(MutesPlugin); - const userId = interaction.targetId; - const targetMember = await pluginData.guild.members.fetch(interaction.targetId); - const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const caseArgs: Partial = { + modId: executingMember.id, + }; - if (!canActOn(pluginData, executingMember, targetMember)) { - interaction.followUp({ ephemeral: true, content: "Cannot mute: insufficient permissions" }); - return; + try { + const result = await mutes.muteUser(userId, durationMs, "Context Menu Action", { caseArgs }); + + const muteMessage = `Muted **${result.case.user_name}** ${ + durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" + } (Case #${result.case.case_number}) (user notified via ${result.notifyResult.method ?? + "dm"})\nPlease update the new case with the \`update\` command`; + + interaction.followUp({ ephemeral: true, content: muteMessage }); + } catch (e) { + 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, { + body: `Failed to mute <@!${userId}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`, + }); + } else { + throw e; } - - try { - const result = await mutes.muteUser( - userId, - duration, - reason, - { contactMethods, caseArgs, isAutomodAction: true }, - rolesToRemove, - rolesToRestore, - ); - - const muteMessage = `Muted **${result.case.user_name}** ${ - duration ? `for ${humanizeDuration(duration)}` : "indefinitely" - } (Case #${result.case.case_number}) (user notified via ${result.notifyResult.method ?? - "dm"})\nPlease update the new case with the \`update\` command`; - - interaction.followUp({ ephemeral: true, content: muteMessage }); - } catch (e) { - 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, { - body: `Failed to mute <@!${userId}> in ContextMenu action \`${actionName}\` because a mute role has not been specified in server config`, - }); - } else { - throw e; - } - } - }, -}); + } +} diff --git a/backend/src/plugins/ContextMenus/actions/userInfo.ts b/backend/src/plugins/ContextMenus/actions/userInfo.ts new file mode 100644 index 00000000..85bfae44 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/userInfo.ts @@ -0,0 +1,21 @@ +import { GuildPluginData } from "knub"; +import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; +import { ContextMenuPluginType } from "../types"; + +export async function userInfoAction(pluginData: GuildPluginData, interaction) { + interaction.deferReply({ ephemeral: true }); + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); + + // TODO: Add can_userinfo perm check + if (userCfg.can_use) { + const util = pluginData.getPlugin(UtilityPlugin); + const embed = await util.userInfo(interaction.targetId, interaction.user.id); + await interaction.followUp({ embeds: [embed] }); + } else { + await interaction.followUp({ content: "Cannot info: insufficient permissions" }); + } +} diff --git a/backend/src/plugins/ContextMenus/helpers.ts b/backend/src/plugins/ContextMenus/helpers.ts deleted file mode 100644 index d6e4ac6c..00000000 --- a/backend/src/plugins/ContextMenus/helpers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ContextMenuInteraction } from "discord.js"; -import * as t from "io-ts"; -import { GuildPluginData } from "knub"; -import { Awaitable } from "knub/dist/utils"; -import { ContextMenuPluginType } from "./types"; - -type ContextActionApplyFn = (meta: { - actionName: string; - pluginData: GuildPluginData; - actionConfig: TConfigType; - interaction: ContextMenuInteraction; -}) => Awaitable; - -export interface ContextActionBlueprint { - configType: TConfigType; - defaultConfig: Partial>; - - apply: ContextActionApplyFn>; -} - -export function contextMenuAction( - blueprint: ContextActionBlueprint, -): ContextActionBlueprint { - return blueprint; -} diff --git a/backend/src/plugins/ContextMenus/types.ts b/backend/src/plugins/ContextMenus/types.ts index 9b34cb6a..e73d686d 100644 --- a/backend/src/plugins/ContextMenus/types.ts +++ b/backend/src/plugins/ContextMenus/types.ts @@ -1,29 +1,17 @@ import * as t from "io-ts"; -import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; +import { BasePluginType, typedGuildEventListener } from "knub"; import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; -import { tNullable } from "../../utils"; -import { AvailableActions } from "./actions/availableActions"; - -export enum ContextMenuTypes { - USER = 2, - MESSAGE = 3, -} - -export const ContextMenuTypeNameToNumber: Record = { - USER: 2, - MESSAGE: 3, -}; - -const ContextActionOpts = t.type({ - enabled: tNullable(t.boolean), - label: t.string, - type: t.keyof(ContextMenuTypes), - action: t.partial(AvailableActions.props), -}); -export type TContextActionOpts = t.TypeOf; export const ConfigSchema = t.type({ - context_actions: t.record(t.string, ContextActionOpts), + can_use: t.boolean, + + user_muteindef: t.boolean, + user_mute1d: t.boolean, + user_mute1h: t.boolean, + user_info: t.boolean, + message_clean10: t.boolean, + message_clean25: t.boolean, + message_clean50: t.boolean, }); export type TConfigSchema = t.TypeOf; @@ -34,5 +22,4 @@ export interface ContextMenuPluginType extends BasePluginType { }; } -export const contextMenuCmd = typedGuildCommand(); export const contextMenuEvt = typedGuildEventListener(); diff --git a/backend/src/plugins/ContextMenus/utils/contextRouter.ts b/backend/src/plugins/ContextMenus/utils/contextRouter.ts index 5004807f..1bda0ff8 100644 --- a/backend/src/plugins/ContextMenus/utils/contextRouter.ts +++ b/backend/src/plugins/ContextMenus/utils/contextRouter.ts @@ -1,7 +1,7 @@ import { ContextMenuInteraction } from "discord.js"; import { GuildPluginData } from "knub"; -import { availableActions } from "../actions/availableActions"; import { ContextMenuPluginType } from "../types"; +import { hardcodedActions } from "./hardcodedContextOptions"; export async function routeContextAction( pluginData: GuildPluginData, @@ -9,20 +9,5 @@ export async function routeContextAction( ) { const contextLink = await pluginData.state.contextMenuLinks.get(interaction.commandId); if (!contextLink) return; - const contextActions = Object.entries(pluginData.config.get().context_actions); - - const configLink = contextActions.find(x => x[0] === contextLink.action_name); - if (!configLink) return; - - for (const [actionName, actionConfig] of Object.entries(configLink[1].action)) { - if (actionConfig == null) return; - const action = availableActions[actionName]; - action.apply({ - actionName, - pluginData, - actionConfig, - interaction, - }); - return; - } + hardcodedActions[contextLink.action_name](pluginData, interaction); } diff --git a/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts b/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts new file mode 100644 index 00000000..84593ace --- /dev/null +++ b/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts @@ -0,0 +1,23 @@ +import { cleanAction } from "../actions/clean"; +import { muteAction } from "../actions/mute"; +import { userInfoAction } from "../actions/userInfo"; + +export const hardcodedContext: Record = { + user_muteindef: "Mute Indefinitely", + user_mute1d: "Mute for 1 day", + user_mute1h: "Mute for 1 hour", + user_info: "Get Info", + message_clean10: "Clean 10 messages", + message_clean25: "Clean 25 messages", + message_clean50: "Clean 50 messages", +}; + +export const hardcodedActions = { + user_muteindef: (pluginData, interaction) => muteAction(pluginData, undefined, interaction), + user_mute1d: (pluginData, interaction) => muteAction(pluginData, "1d", interaction), + user_mute1h: (pluginData, interaction) => muteAction(pluginData, "1h", interaction), + user_info: (pluginData, interaction) => userInfoAction(pluginData, interaction), + message_clean10: (pluginData, interaction) => cleanAction(pluginData, 10, interaction), + message_clean25: (pluginData, interaction) => cleanAction(pluginData, 25, interaction), + message_clean50: (pluginData, interaction) => cleanAction(pluginData, 50, interaction), +}; diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts index 93449819..074b4440 100644 --- a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts +++ b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts @@ -1,22 +1,25 @@ import { ApplicationCommandData } from "discord.js"; -import { LogType } from "src/data/LogType"; -import { LogsPlugin } from "src/plugins/Logs/LogsPlugin"; +import { LogType } from "../../../data/LogType"; +import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; import { GuildPluginData } from "knub"; -import { ContextMenuPluginType, ContextMenuTypeNameToNumber } from "../types"; +import { ContextMenuPluginType } from "../types"; +import { hardcodedContext } from "./hardcodedContextOptions"; export async function loadAllCommands(pluginData: GuildPluginData) { const comms = await pluginData.client.application!.commands; - const actions = pluginData.config.get().context_actions; + const cfg = pluginData.config.get(); const newCommands: ApplicationCommandData[] = []; const addedNames: string[] = []; - for (const [name, configAction] of Object.entries(actions)) { - if (!configAction.enabled) continue; + for (const [name, label] of Object.entries(hardcodedContext)) { + if (!cfg[name]) continue; + const type = name.startsWith("user") ? 2 : 3; const data: ApplicationCommandData = { - type: ContextMenuTypeNameToNumber[configAction.type], - name: configAction.label, + type, + name: label, }; + addedNames.push(name); newCommands.push(data); } diff --git a/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts b/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts deleted file mode 100644 index bec64765..00000000 --- a/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Snowflake, TextChannel } from "discord.js"; -import { GuildPluginData } from "knub"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; -import { ContextMenuPluginType } from "../types"; - -export function resolveActionContactMethods( - pluginData: GuildPluginData, - actionConfig: { - notify?: string | null; - notifyChannel?: string | null; - }, -): UserNotificationMethod[] { - if (actionConfig.notify === "dm") { - return [{ type: "dm" }]; - } else if (actionConfig.notify === "channel") { - if (!actionConfig.notifyChannel) { - throw new RecoverablePluginError(ERRORS.NO_USER_NOTIFICATION_CHANNEL); - } - - const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake); - if (!(channel instanceof TextChannel)) { - throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL); - } - - return [{ type: "channel", channel }]; - } else if (actionConfig.notify && disableUserNotificationStrings.includes(actionConfig.notify)) { - return []; - } - - return []; -} diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index a2e9a6e7..9a624ed8 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -1,3 +1,4 @@ +import { MessageEmbedOptions } from "discord.js"; import { PluginOptions } from "knub"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; @@ -36,6 +37,7 @@ import { UserInfoCmd } from "./commands/UserInfoCmd"; import { VcdisconnectCmd } from "./commands/VcdisconnectCmd"; import { VcmoveAllCmd, VcmoveCmd } from "./commands/VcmoveCmd"; import { AutoJoinThreadEvt, AutoJoinThreadSyncEvt } from "./events/AutoJoinThreadEvt"; +import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; import { activeReloads } from "./guildReloads"; import { refreshMembersIfNeeded } from "./refreshMembers"; import { ConfigSchema, UtilityPluginType } from "./types"; @@ -162,6 +164,12 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ cleanCmd(pluginData, args, msg); }; }, + + userInfo(pluginData) { + return (userId: string, requestMemberId?: string) => { + return getUserInfoEmbed(pluginData, userId, false, requestMemberId); + }; + }, }, beforeLoad(pluginData) { From 1c8a2232645b9d4a53ef665972957801c13b700c Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Sun, 15 Aug 2021 01:44:25 +0200 Subject: [PATCH 002/191] Implement temporary permission check public functions --- backend/src/plugins/ContextMenus/actions/clean.ts | 5 ++--- backend/src/plugins/ContextMenus/actions/mute.ts | 5 +++-- backend/src/plugins/ContextMenus/actions/userInfo.ts | 7 +++---- backend/src/plugins/ModActions/ModActionsPlugin.ts | 7 +++++++ .../src/plugins/ModActions/functions/hasMutePerm.ts | 11 +++++++++++ backend/src/plugins/Utility/UtilityPlugin.ts | 9 ++++++++- .../src/plugins/Utility/functions/hasPermission.ts | 12 ++++++++++++ 7 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 backend/src/plugins/ModActions/functions/hasMutePerm.ts create mode 100644 backend/src/plugins/Utility/functions/hasPermission.ts diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 55ff9d4e..c63ffc1a 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -17,9 +17,9 @@ export async function cleanAction( channelId: interaction.channelId, member: executingMember, }); + const utility = pluginData.getPlugin(UtilityPlugin); - // TODO: Add perm check for can_clean in util - if (!userCfg.can_use) { + if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) { await interaction.followUp({ content: "Cannot clean: insufficient permissions" }); return; } @@ -36,7 +36,6 @@ export async function cleanAction( try { interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`); - const utility = pluginData.getPlugin(UtilityPlugin); utility.clean({ count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, targetMessage); } catch (e) { interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index af9b71ed..d0e85764 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -1,5 +1,6 @@ import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; +import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; import { canActOn } from "src/pluginUtils"; import { LogType } from "../../../data/LogType"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; @@ -17,8 +18,8 @@ export async function muteAction(pluginData: GuildPluginData()({ }; }, + hasMutePermission(pluginData) { + return (member: GuildMember, channelId: string) => { + return hasMutePermission(pluginData, member, channelId); + }; + }, + on: mapToPublicFn(onModActionsEvent), off: mapToPublicFn(offModActionsEvent), getEventEmitter(pluginData) { diff --git a/backend/src/plugins/ModActions/functions/hasMutePerm.ts b/backend/src/plugins/ModActions/functions/hasMutePerm.ts new file mode 100644 index 00000000..b26edd4d --- /dev/null +++ b/backend/src/plugins/ModActions/functions/hasMutePerm.ts @@ -0,0 +1,11 @@ +import { GuildMember, Snowflake } from "discord.js"; +import { GuildPluginData } from "knub"; +import { ModActionsPluginType } from "../types"; + +export async function hasMutePermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId })).can_mute; +} diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 9a624ed8..138100af 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -1,4 +1,4 @@ -import { MessageEmbedOptions } from "discord.js"; +import { GuildMember, MessageEmbedOptions } from "discord.js"; import { PluginOptions } from "knub"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; @@ -38,6 +38,7 @@ import { VcdisconnectCmd } from "./commands/VcdisconnectCmd"; import { VcmoveAllCmd, VcmoveCmd } from "./commands/VcmoveCmd"; import { AutoJoinThreadEvt, AutoJoinThreadSyncEvt } from "./events/AutoJoinThreadEvt"; import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; +import { hasPermission } from "./functions/hasPermission"; import { activeReloads } from "./guildReloads"; import { refreshMembersIfNeeded } from "./refreshMembers"; import { ConfigSchema, UtilityPluginType } from "./types"; @@ -170,6 +171,12 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ return getUserInfoEmbed(pluginData, userId, false, requestMemberId); }; }, + + hasPermission(pluginData) { + return (member: GuildMember, channelId: string, permission: string) => { + return hasPermission(pluginData, member, channelId, permission); + }; + }, }, beforeLoad(pluginData) { diff --git a/backend/src/plugins/Utility/functions/hasPermission.ts b/backend/src/plugins/Utility/functions/hasPermission.ts new file mode 100644 index 00000000..919d28f7 --- /dev/null +++ b/backend/src/plugins/Utility/functions/hasPermission.ts @@ -0,0 +1,12 @@ +import { GuildMember, Snowflake } from "discord.js"; +import { GuildPluginData } from "knub"; +import { UtilityPluginType } from "../types"; + +export async function hasPermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, + permission: string, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId }))[permission]; +} From 99c8dbabb7e33b726e738c412d0a818475096013 Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Sun, 15 Aug 2021 22:02:18 +0200 Subject: [PATCH 003/191] Apply suggestions from code review Co-authored-by: Almeida --- backend/src/plugins/ModActions/ModActionsPlugin.ts | 2 +- backend/src/plugins/Utility/UtilityPlugin.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 20d4bf35..f2dea9a0 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -183,7 +183,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ }, hasMutePermission(pluginData) { - return (member: GuildMember, channelId: string) => { + return (member: GuildMember, channelId: Snowflake) => { return hasMutePermission(pluginData, member, channelId); }; }, diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 138100af..c16927ce 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -167,13 +167,13 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ }, userInfo(pluginData) { - return (userId: string, requestMemberId?: string) => { + return (userId: Snowflake, requestMemberId?: Snowflake) => { return getUserInfoEmbed(pluginData, userId, false, requestMemberId); }; }, hasPermission(pluginData) { - return (member: GuildMember, channelId: string, permission: string) => { + return (member: GuildMember, channelId: Snowflake, permission: string) => { return hasPermission(pluginData, member, channelId, permission); }; }, From 9a5e911cf29d1a2f9db529be840ba90fd6b5a642 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Sun, 15 Aug 2021 22:14:04 +0200 Subject: [PATCH 004/191] Add typings, comment magic numbers --- backend/src/plugins/ContextMenus/actions/mute.ts | 7 ++++++- backend/src/plugins/ContextMenus/actions/userInfo.ts | 10 +++++++++- .../src/plugins/ContextMenus/utils/loadAllCommands.ts | 2 +- backend/src/plugins/ModActions/ModActionsPlugin.ts | 2 +- backend/src/plugins/Utility/UtilityPlugin.ts | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index d0e85764..089dcd26 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -1,3 +1,4 @@ +import { ContextMenuInteraction } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; @@ -10,7 +11,11 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { ContextMenuPluginType } from "../types"; -export async function muteAction(pluginData: GuildPluginData, duration, interaction) { +export async function muteAction( + pluginData: GuildPluginData, + duration: string | undefined, + interaction: ContextMenuInteraction, +) { interaction.deferReply({ ephemeral: true }); const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ diff --git a/backend/src/plugins/ContextMenus/actions/userInfo.ts b/backend/src/plugins/ContextMenus/actions/userInfo.ts index 70ceb616..c4dd4a34 100644 --- a/backend/src/plugins/ContextMenus/actions/userInfo.ts +++ b/backend/src/plugins/ContextMenus/actions/userInfo.ts @@ -1,8 +1,12 @@ +import { ContextMenuInteraction } from "discord.js"; import { GuildPluginData } from "knub"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; import { ContextMenuPluginType } from "../types"; -export async function userInfoAction(pluginData: GuildPluginData, interaction) { +export async function userInfoAction( + pluginData: GuildPluginData, + interaction: ContextMenuInteraction, +) { interaction.deferReply({ ephemeral: true }); const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ @@ -13,6 +17,10 @@ export async function userInfoAction(pluginData: GuildPluginData Date: Mon, 16 Aug 2021 01:23:54 +0200 Subject: [PATCH 005/191] Actually import the constants and not what VSC wants to import --- backend/src/plugins/ContextMenus/utils/loadAllCommands.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts index 77c242a1..b38d885c 100644 --- a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts +++ b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts @@ -1,4 +1,4 @@ -import { ApplicationCommandData } from "discord.js"; +import { ApplicationCommandData, Constants } from "discord.js"; import { LogType } from "../../../data/LogType"; import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; import { GuildPluginData } from "knub"; @@ -14,7 +14,9 @@ export async function loadAllCommands(pluginData: GuildPluginData Date: Tue, 17 Aug 2021 19:23:56 +0300 Subject: [PATCH 006/191] Fix emoji mentions in default log messages --- backend/src/data/DefaultLogMessages.json | 6 +++--- backend/src/utils/configAccessibleObjects.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/data/DefaultLogMessages.json b/backend/src/data/DefaultLogMessages.json index c666ff43..1adb760f 100644 --- a/backend/src/data/DefaultLogMessages.json +++ b/backend/src/data/DefaultLogMessages.json @@ -50,9 +50,9 @@ "STAGE_INSTANCE_DELETE": "📣 Stage Instance `{stageInstance.topic}` was deleted in Stage Channel <#{stageChannel.id}>", "STAGE_INSTANCE_UPDATE": "📣 Stage Instance `{newStageInstance.topic}` was edited in Stage Channel <#{stageChannel.id}>. Changes:\n{differenceString}", - "EMOJI_CREATE": "<{emoji.identifier}> Emoji `{emoji.name} ({emoji.id})` was created", - "EMOJI_DELETE": "👋 Emoji `{emoji.name} ({emoji.id})` was deleted", - "EMOJI_UPDATE": "<{newEmoji.identifier}> Emoji `{newEmoji.name} ({newEmoji.id})` was updated. Changes:\n{differenceString}", + "EMOJI_CREATE": "{emoji.mention} Emoji **{emoji.name}** (`{emoji.id}`) was created", + "EMOJI_DELETE": "👋 Emoji **{emoji.name}** (`{emoji.id}`) was deleted", + "EMOJI_UPDATE": "{newEmoji.mention} Emoji **{newEmoji.name}** (`{newEmoji.id}`) was updated. Changes:\n{differenceString}", "STICKER_CREATE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was created. Description: `{sticker.description}` Format: {emoji.format}", "STICKER_DELETE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was deleted.", diff --git a/backend/src/utils/configAccessibleObjects.ts b/backend/src/utils/configAccessibleObjects.ts index 2e80c7f0..0a776629 100644 --- a/backend/src/utils/configAccessibleObjects.ts +++ b/backend/src/utils/configAccessibleObjects.ts @@ -140,6 +140,7 @@ export interface IConfigAccessibleEmoji { createdAt?: number; animated: boolean; identifier: string; + mention: string; } export function emojiToConfigAccessibleEmoji(emoji: Emoji): IConfigAccessibleEmoji { @@ -149,6 +150,7 @@ export function emojiToConfigAccessibleEmoji(emoji: Emoji): IConfigAccessibleEmo createdAt: emoji.createdTimestamp ?? undefined, animated: emoji.animated ?? false, identifier: emoji.identifier, + mention: emoji.animated ? `` : `<:${emoji.name}:${emoji.id}>`, }; return toReturn; From d2ac700143605010a249ae4c32ac23896e5f7ac4 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Tue, 17 Aug 2021 19:24:28 +0300 Subject: [PATCH 007/191] Upgrade Knub to v30.0.0-beta.40 --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 8e71edda..860c5573 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.39", + "knub": "^30.0.0-beta.40", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3042,9 +3042,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.39", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.39.tgz", - "integrity": "sha512-L9RYkqh7YcWfw0ZXdGrKEZru/J+mkiyn+8vi1xCvjEdKMPdq4Gov/SG4suajMFhhX3RXdvh8BoE/3gbR2cq4xA==", + "version": "30.0.0-beta.40", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.40.tgz", + "integrity": "sha512-oGuc2Q57Zpgr3iRqCo5dzZdinNsDr1uiJs3TB/4eQNCIaaLqD+a5HfwGbDBW11ix+zTCqbLUI8/4hjJIKpJT8g==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8281,9 +8281,9 @@ } }, "knub": { - "version": "30.0.0-beta.39", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.39.tgz", - "integrity": "sha512-L9RYkqh7YcWfw0ZXdGrKEZru/J+mkiyn+8vi1xCvjEdKMPdq4Gov/SG4suajMFhhX3RXdvh8BoE/3gbR2cq4xA==", + "version": "30.0.0-beta.40", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.40.tgz", + "integrity": "sha512-oGuc2Q57Zpgr3iRqCo5dzZdinNsDr1uiJs3TB/4eQNCIaaLqD+a5HfwGbDBW11ix+zTCqbLUI8/4hjJIKpJT8g==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index 75a9b6eb..86fe6360 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.39", + "knub": "^30.0.0-beta.40", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", From bed6589d48ccaeb7ef0084064d9d8be0ceaaf671 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 01:51:42 +0300 Subject: [PATCH 008/191] Typed log functions + more --- backend/src/data/GuildArchives.ts | 22 +- backend/src/data/GuildSavedMessages.ts | 87 +++- backend/src/data/entities/SavedMessage.ts | 71 ++- .../plugins/AutoDelete/util/deleteNextItem.ts | 12 +- .../AutoDelete/util/onMessageCreate.ts | 3 +- .../AutoReactions/events/AddReactionsEvt.ts | 6 +- .../src/plugins/Automod/actions/addRoles.ts | 4 +- .../plugins/Automod/actions/addToCounter.ts | 3 +- backend/src/plugins/Automod/actions/alert.ts | 57 ++- .../plugins/Automod/actions/changeNickname.ts | 2 +- backend/src/plugins/Automod/actions/log.ts | 15 +- backend/src/plugins/Automod/actions/mute.ts | 2 +- .../plugins/Automod/actions/removeRoles.ts | 4 +- backend/src/plugins/Automod/actions/reply.ts | 9 +- .../src/plugins/Automod/actions/setCounter.ts | 3 +- .../plugins/Automod/actions/setSlowmode.ts | 3 +- .../Automod/functions/setAntiraidLevel.ts | 8 +- backend/src/plugins/Cases/CasesPlugin.ts | 3 +- .../Cases/functions/postToCaseLogChannel.ts | 5 +- .../src/plugins/Censor/util/censorMessage.ts | 17 +- .../functions/handleCompanionPermissions.ts | 2 +- .../src/plugins/ContextMenus/actions/clean.ts | 2 +- .../src/plugins/ContextMenus/actions/mute.ts | 2 +- .../ContextMenus/utils/loadAllCommands.ts | 2 +- backend/src/plugins/Logs/LogsPlugin.ts | 158 ++++++- .../Logs/events/LogsChannelModifyEvts.ts | 27 +- .../events/LogsEmojiAndStickerModifyEvts.ts | 48 +- .../plugins/Logs/events/LogsGuildBanEvts.ts | 42 +- .../Logs/events/LogsGuildMemberAddEvt.ts | 20 +- .../Logs/events/LogsGuildMemberRemoveEvt.ts | 7 +- .../plugins/Logs/events/LogsRoleModifyEvts.ts | 19 +- .../events/LogsStageInstanceModifyEvts.ts | 32 +- .../Logs/events/LogsThreadModifyEvts.ts | 27 +- .../plugins/Logs/events/LogsUserUpdateEvts.ts | 80 ++-- .../Logs/events/LogsVoiceChannelEvts.ts | 30 +- .../Logs/logFunctions/logAutomodAction.ts | 33 ++ .../plugins/Logs/logFunctions/logBotAlert.ts | 20 + .../Logs/logFunctions/logCaseCreate.ts | 32 ++ .../Logs/logFunctions/logCaseDelete.ts | 25 + .../Logs/logFunctions/logCaseUpdate.ts | 28 ++ .../plugins/Logs/logFunctions/logCensor.ts | 41 ++ .../Logs/logFunctions/logChannelCreate.ts | 25 + .../Logs/logFunctions/logChannelDelete.ts | 25 + .../Logs/logFunctions/logChannelUpdate.ts | 29 ++ .../src/plugins/Logs/logFunctions/logClean.ts | 31 ++ .../plugins/Logs/logFunctions/logDmFailed.ts | 28 ++ .../Logs/logFunctions/logEmojiCreate.ts | 22 + .../Logs/logFunctions/logEmojiDelete.ts | 22 + .../Logs/logFunctions/logEmojiUpdate.ts | 26 + .../plugins/Logs/logFunctions/logMassBan.ts | 26 + .../plugins/Logs/logFunctions/logMassMute.ts | 24 + .../plugins/Logs/logFunctions/logMassUnban.ts | 26 + .../plugins/Logs/logFunctions/logMemberBan.ts | 32 ++ .../Logs/logFunctions/logMemberForceban.ts | 30 ++ .../Logs/logFunctions/logMemberJoin.ts | 35 ++ .../logMemberJoinWithPriorRecords.ts | 30 ++ .../Logs/logFunctions/logMemberKick.ts | 32 ++ .../Logs/logFunctions/logMemberLeave.ts | 25 + .../Logs/logFunctions/logMemberMute.ts | 32 ++ .../Logs/logFunctions/logMemberMuteExpired.ts | 40 ++ .../Logs/logFunctions/logMemberMuteRejoin.ts | 25 + .../Logs/logFunctions/logMemberNickChange.ts | 30 ++ .../Logs/logFunctions/logMemberNote.ts | 32 ++ .../Logs/logFunctions/logMemberRestore.ts | 28 ++ .../Logs/logFunctions/logMemberRoleAdd.ts | 30 ++ .../Logs/logFunctions/logMemberRoleChanges.ts | 32 ++ .../Logs/logFunctions/logMemberRoleRemove.ts | 30 ++ .../Logs/logFunctions/logMemberTimedBan.ts | 34 ++ .../Logs/logFunctions/logMemberTimedMute.ts | 31 ++ .../Logs/logFunctions/logMemberTimedUnban.ts | 33 ++ .../Logs/logFunctions/logMemberTimedUnmute.ts | 30 ++ .../Logs/logFunctions/logMemberUnban.ts | 31 ++ .../Logs/logFunctions/logMemberUnmute.ts | 31 ++ .../Logs/logFunctions/logMemberWarn.ts | 32 ++ .../Logs/logFunctions/logMessageDelete.ts | 56 +++ .../Logs/logFunctions/logMessageDeleteAuto.ts | 39 ++ .../Logs/logFunctions/logMessageDeleteBare.ts | 27 ++ .../Logs/logFunctions/logMessageDeleteBulk.ts | 31 ++ .../Logs/logFunctions/logMessageEdit.ts | 39 ++ .../logFunctions/logMessageSpamDetected.ts | 38 ++ .../Logs/logFunctions/logOtherSpamDetected.ts | 31 ++ .../logFunctions/logPostedScheduledMessage.ts | 34 ++ .../Logs/logFunctions/logRepeatedMessage.ts | 39 ++ .../Logs/logFunctions/logRoleCreate.ts | 22 + .../Logs/logFunctions/logRoleDelete.ts | 22 + .../Logs/logFunctions/logRoleUpdate.ts | 26 + .../Logs/logFunctions/logScheduledMessage.ts | 35 ++ .../logScheduledRepeatedMessage.ts | 42 ++ .../Logs/logFunctions/logSetAntiraidAuto.ts | 20 + .../Logs/logFunctions/logSetAntiraidUser.ts | 27 ++ .../logFunctions/logStageInstanceCreate.ts | 27 ++ .../logFunctions/logStageInstanceDelete.ts | 27 ++ .../logFunctions/logStageInstanceUpdate.ts | 31 ++ .../Logs/logFunctions/logStickerCreate.ts | 22 + .../Logs/logFunctions/logStickerDelete.ts | 22 + .../Logs/logFunctions/logStickerUpdate.ts | 26 + .../Logs/logFunctions/logThreadCreate.ts | 25 + .../Logs/logFunctions/logThreadDelete.ts | 25 + .../Logs/logFunctions/logThreadUpdate.ts | 29 ++ .../logVoiceChannelForceDisconnect.ts | 39 ++ .../logFunctions/logVoiceChannelForceMove.ts | 41 ++ .../Logs/logFunctions/logVoiceChannelJoin.ts | 30 ++ .../Logs/logFunctions/logVoiceChannelLeave.ts | 30 ++ .../Logs/logFunctions/logVoiceChannelMove.ts | 32 ++ backend/src/plugins/Logs/types.ts | 446 ++++++++++++++++++ .../src/plugins/Logs/util/getLogMessage.ts | 40 +- backend/src/plugins/Logs/util/isLogIgnored.ts | 7 + backend/src/plugins/Logs/util/log.ts | 117 ++--- .../src/plugins/Logs/util/onMessageDelete.ts | 62 +-- .../plugins/Logs/util/onMessageDeleteBulk.ts | 30 +- .../src/plugins/Logs/util/onMessageUpdate.ts | 15 +- .../plugins/ModActions/ModActionsPlugin.ts | 3 +- .../plugins/ModActions/commands/AddCaseCmd.ts | 7 +- .../src/plugins/ModActions/commands/BanCmd.ts | 27 +- .../ModActions/commands/DeleteCaseCmd.ts | 8 +- .../ModActions/commands/ForcebanCmd.ts | 7 +- .../plugins/ModActions/commands/MassBanCmd.ts | 7 +- .../ModActions/commands/MassUnbanCmd.ts | 7 +- .../ModActions/commands/MassmuteCmd.ts | 7 +- .../plugins/ModActions/commands/NoteCmd.ts | 9 +- .../plugins/ModActions/commands/UnbanCmd.ts | 9 +- .../events/CreateBanCaseOnManualBanEvt.ts | 9 +- .../events/CreateKickCaseOnManualKickEvt.ts | 9 +- .../events/CreateUnbanCaseOnManualUnbanEvt.ts | 8 +- .../events/PostAlertOnMemberJoinEvt.ts | 6 +- .../plugins/ModActions/functions/banUserId.ts | 28 +- .../plugins/ModActions/functions/isBanned.ts | 4 +- .../ModActions/functions/kickMember.ts | 13 +- .../functions/outdatedTempbansLoop.ts | 9 +- .../ModActions/functions/updateCase.ts | 3 +- .../ModActions/functions/warnMember.ts | 13 +- backend/src/plugins/Mutes/MutesPlugin.ts | 3 +- .../events/ReapplyActiveMuteOnJoinEvt.ts | 7 +- .../Mutes/functions/clearExpiredMutes.ts | 16 +- .../src/plugins/Mutes/functions/muteUser.ts | 26 +- .../src/plugins/Mutes/functions/unmuteUser.ts | 15 +- .../src/plugins/Persist/events/LoadDataEvt.ts | 10 +- backend/src/plugins/Post/PostPlugin.ts | 3 +- .../src/plugins/Post/util/actualPostCmd.ts | 23 +- .../plugins/Post/util/scheduledPostLoop.ts | 15 +- .../applyReactionRoleReactionsToMessage.ts | 10 +- backend/src/plugins/Roles/RolesPlugin.ts | 2 + .../src/plugins/Roles/commands/AddRoleCmd.ts | 11 +- .../plugins/Roles/commands/MassAddRoleCmd.ts | 13 +- .../Roles/commands/MassRemoveRoleCmd.ts | 13 +- .../plugins/Roles/commands/RemoveRoleCmd.ts | 13 +- .../Slowmode/util/applyBotSlowmodeToUserId.ts | 19 +- .../Slowmode/util/clearExpiredSlowmodes.ts | 13 +- .../plugins/Slowmode/util/onMessageCreate.ts | 2 +- .../Spam/util/logAndDetectMessageSpam.ts | 13 +- .../Spam/util/logAndDetectOtherSpam.ts | 8 +- backend/src/plugins/Tags/TagsPlugin.ts | 2 + .../src/plugins/Tags/commands/TagEvalCmd.ts | 6 +- .../src/plugins/Tags/util/onMessageCreate.ts | 5 +- .../plugins/Tags/util/renderTagFromString.ts | 8 +- backend/src/plugins/Utility/UtilityPlugin.ts | 3 +- .../src/plugins/Utility/commands/CleanCmd.ts | 9 +- .../Utility/commands/VcdisconnectCmd.ts | 17 +- .../src/plugins/Utility/commands/VcmoveCmd.ts | 29 +- .../WelcomeMessage/WelcomeMessagePlugin.ts | 2 + .../events/SendWelcomeMessageEvt.ts | 21 +- backend/src/templateFormatter.ts | 67 ++- backend/src/utils.ts | 11 +- backend/src/utils/configAccessibleObjects.ts | 203 -------- .../utils/safeFindRelevantAuditLogEntry.ts | 2 +- backend/src/utils/templateSafeObjects.ts | 444 +++++++++++++++++ 166 files changed, 4021 insertions(+), 869 deletions(-) create mode 100644 backend/src/plugins/Logs/logFunctions/logAutomodAction.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logBotAlert.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logCaseCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logCaseDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logCaseUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logCensor.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logChannelCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logChannelDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logClean.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logDmFailed.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logEmojiCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logEmojiDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logEmojiUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMassBan.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMassMute.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMassUnban.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberBan.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberForceban.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberJoin.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberJoinWithPriorRecords.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberKick.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberLeave.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberMute.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberMuteRejoin.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberNickChange.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberNote.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberRestore.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberTimedBan.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberTimedMute.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberTimedUnban.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberUnban.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMemberWarn.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageEdit.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logOtherSpamDetected.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logPostedScheduledMessage.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logRoleCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logRoleDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logRoleUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logSetAntiraidAuto.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logSetAntiraidUser.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStageInstanceDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStageInstanceUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStickerCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStickerDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logStickerUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logThreadCreate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logThreadDelete.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts create mode 100644 backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts create mode 100644 backend/src/plugins/Logs/util/isLogIgnored.ts delete mode 100644 backend/src/utils/configAccessibleObjects.ts create mode 100644 backend/src/utils/templateSafeObjects.ts 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; +} From 8edaa7506438bd35fb0391e18ca540ab5c4cd01d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 02:01:56 +0300 Subject: [PATCH 009/191] Temp fix for circular dependency --- backend/src/plugins/Logs/LogsPlugin.ts | 3 +- .../Logs/events/LogsGuildMemberAddEvt.ts | 53 ++++++++++--------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index d324cb11..c01ab594 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -7,7 +7,6 @@ import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { LogType } from "../../data/LogType"; import { logger } from "../../logger"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; -import { CasesPlugin } from "../Cases/CasesPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { LogsChannelCreateEvt, LogsChannelDeleteEvt, LogsChannelUpdateEvt } from "./events/LogsChannelModifyEvts"; @@ -140,7 +139,7 @@ export const LogsPlugin = zeppelinGuildPlugin()({ prettyName: "Logs", }, - dependencies: [TimeAndDatePlugin, CasesPlugin], + dependencies: [TimeAndDatePlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts index 60d7036a..c86a1fc3 100644 --- a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts +++ b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts @@ -2,7 +2,6 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; import { LogType } from "../../../data/LogType"; import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; import { logsEvt } from "../types"; import { logMemberJoin } from "../logFunctions/logMemberJoin"; import { logMemberJoinWithPriorRecords } from "../logFunctions/logMemberJoinWithPriorRecords"; @@ -18,31 +17,33 @@ export const LogsGuildMemberAddEvt = logsEvt({ member, }); - const cases = (await pluginData.state.cases.with("notes").getByUserId(member.id)).filter(c => !c.is_hidden); - cases.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)); + // TODO: Uncomment below once circular dependencies in Knub have been fixed - if (cases.length) { - const recentCaseLines: string[] = []; - const recentCases = cases.slice(0, 2); - const casesPlugin = pluginData.getPlugin(CasesPlugin); - for (const theCase of recentCases) { - recentCaseLines.push((await casesPlugin.getCaseSummary(theCase))!); - } - - let recentCaseSummary = recentCaseLines.join("\n"); - if (recentCases.length < cases.length) { - const remaining = cases.length - recentCases.length; - if (remaining === 1) { - recentCaseSummary += `\n*+${remaining} case*`; - } else { - recentCaseSummary += `\n*+${remaining} cases*`; - } - } - - logMemberJoinWithPriorRecords(pluginData, { - member, - recentCaseSummary, - }); - } + // const cases = (await pluginData.state.cases.with("notes").getByUserId(member.id)).filter(c => !c.is_hidden); + // cases.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)); + // + // if (cases.length) { + // const recentCaseLines: string[] = []; + // const recentCases = cases.slice(0, 2); + // const casesPlugin = pluginData.getPlugin(CasesPlugin); + // for (const theCase of recentCases) { + // recentCaseLines.push((await casesPlugin.getCaseSummary(theCase))!); + // } + // + // let recentCaseSummary = recentCaseLines.join("\n"); + // if (recentCases.length < cases.length) { + // const remaining = cases.length - recentCases.length; + // if (remaining === 1) { + // recentCaseSummary += `\n*+${remaining} case*`; + // } else { + // recentCaseSummary += `\n*+${remaining} cases*`; + // } + // } + // + // logMemberJoinWithPriorRecords(pluginData, { + // member, + // recentCaseSummary, + // }); + // } }, }); From 62a6e59b7abf510efec06183979c59df1f9adc21 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 02:02:33 +0300 Subject: [PATCH 010/191] Package updates that should've been part of bed6589d48ccaeb7ef0084064d9d8be0ceaaf671 --- backend/package-lock.json | 16 +++++++++++++++- backend/package.json | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 860c5573..805b34cc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -53,7 +53,8 @@ "utf-8-validate": "^5.0.5", "uuid": "^3.3.2", "yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build", - "zlib-sync": "^0.1.7" + "zlib-sync": "^0.1.7", + "zod": "^3.7.2" }, "devDependencies": { "@types/cors": "^2.8.5", @@ -5965,6 +5966,14 @@ "dependencies": { "nan": "^2.14.0" } + }, + "node_modules/zod": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.7.2.tgz", + "integrity": "sha512-JhYYcj+TS/a0+3kqxnbmuXMVtA+QkJUPu91beQTo1Y3xA891pHeMPQgVOSu97FdzAd056Yp87lpEi8Xvmd3zhw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -10527,6 +10536,11 @@ "requires": { "nan": "^2.14.0" } + }, + "zod": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.7.2.tgz", + "integrity": "sha512-JhYYcj+TS/a0+3kqxnbmuXMVtA+QkJUPu91beQTo1Y3xA891pHeMPQgVOSu97FdzAd056Yp87lpEi8Xvmd3zhw==" } } } diff --git a/backend/package.json b/backend/package.json index 86fe6360..df881336 100644 --- a/backend/package.json +++ b/backend/package.json @@ -68,7 +68,8 @@ "utf-8-validate": "^5.0.5", "uuid": "^3.3.2", "yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build", - "zlib-sync": "^0.1.7" + "zlib-sync": "^0.1.7", + "zod": "^3.7.2" }, "devDependencies": { "@types/cors": "^2.8.5", From e3b6b017a2e1e771bdf79458797e6d94eb692512 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 19:33:22 +0300 Subject: [PATCH 011/191] Fix issues with circular dependencies between plugins --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- backend/src/plugins/AutoDelete/AutoDeletePlugin.ts | 2 +- .../plugins/AutoReactions/AutoReactionsPlugin.ts | 2 +- backend/src/plugins/Automod/AutomodPlugin.ts | 2 +- backend/src/plugins/Cases/CasesPlugin.ts | 5 ++++- backend/src/plugins/Censor/CensorPlugin.ts | 2 +- .../ChannelArchiver/ChannelArchiverPlugin.ts | 2 +- .../CompanionChannels/CompanionChannelsPlugin.ts | 2 +- .../src/plugins/ContextMenus/ContextMenuPlugin.ts | 2 +- backend/src/plugins/Logs/LogsPlugin.ts | 6 +++++- backend/src/plugins/ModActions/ModActionsPlugin.ts | 2 +- backend/src/plugins/Mutes/MutesPlugin.ts | 2 +- backend/src/plugins/Persist/PersistPlugin.ts | 2 +- backend/src/plugins/Post/PostPlugin.ts | 2 +- .../plugins/ReactionRoles/ReactionRolesPlugin.ts | 2 +- backend/src/plugins/Reminders/RemindersPlugin.ts | 2 +- backend/src/plugins/Roles/RolesPlugin.ts | 2 +- backend/src/plugins/Slowmode/SlowmodePlugin.ts | 2 +- backend/src/plugins/Spam/SpamPlugin.ts | 2 +- backend/src/plugins/Tags/TagsPlugin.ts | 2 +- backend/src/plugins/Utility/UtilityPlugin.ts | 2 +- .../plugins/WelcomeMessage/WelcomeMessagePlugin.ts | 2 +- 23 files changed, 36 insertions(+), 29 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 805b34cc..c3e38feb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.40", + "knub": "^30.0.0-beta.41", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3043,9 +3043,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.40", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.40.tgz", - "integrity": "sha512-oGuc2Q57Zpgr3iRqCo5dzZdinNsDr1uiJs3TB/4eQNCIaaLqD+a5HfwGbDBW11ix+zTCqbLUI8/4hjJIKpJT8g==", + "version": "30.0.0-beta.41", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.41.tgz", + "integrity": "sha512-tKHGG9vh62ZN0JIi2ULYHvJbR28WXhym8c9vRtyQCPQqJ1w3n7Fow9JXbh6BUTSD8QszWbvq1Ri22wbQtBEyNw==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8290,9 +8290,9 @@ } }, "knub": { - "version": "30.0.0-beta.40", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.40.tgz", - "integrity": "sha512-oGuc2Q57Zpgr3iRqCo5dzZdinNsDr1uiJs3TB/4eQNCIaaLqD+a5HfwGbDBW11ix+zTCqbLUI8/4hjJIKpJT8g==", + "version": "30.0.0-beta.41", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.41.tgz", + "integrity": "sha512-tKHGG9vh62ZN0JIi2ULYHvJbR28WXhym8c9vRtyQCPQqJ1w3n7Fow9JXbh6BUTSD8QszWbvq1Ri22wbQtBEyNw==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index df881336..67724ff9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.40", + "knub": "^30.0.0-beta.41", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", diff --git a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts index aa7ffd50..593ffb31 100644 --- a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts +++ b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts @@ -25,7 +25,7 @@ export const AutoDeletePlugin = zeppelinGuildPlugin()({ configurationGuide: "Maximum deletion delay is currently 5 minutes", }, - dependencies: [TimeAndDatePlugin, LogsPlugin], + dependencies: () => [TimeAndDatePlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts index 886a9922..6633427d 100644 --- a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts +++ b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts @@ -33,7 +33,7 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin( `), }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 8170f632..80d160e3 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -159,7 +159,7 @@ export const AutomodPlugin = zeppelinGuildPlugin()({ info: pluginInfo, // prettier-ignore - dependencies: [ + dependencies: () => [ LogsPlugin, ModActionsPlugin, MutesPlugin, diff --git a/backend/src/plugins/Cases/CasesPlugin.ts b/backend/src/plugins/Cases/CasesPlugin.ts index 159e45fc..fd6c1e41 100644 --- a/backend/src/plugins/Cases/CasesPlugin.ts +++ b/backend/src/plugins/Cases/CasesPlugin.ts @@ -18,6 +18,9 @@ import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel"; import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types"; import { LogsPlugin } from "../Logs/LogsPlugin"; +// Workaround for circular dependency +const AnyTypedLogsPlugin = LogsPlugin as any; + const defaultOptions = { config: { log_automatic_actions: true, @@ -39,7 +42,7 @@ export const CasesPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: [TimeAndDatePlugin, LogsPlugin], + dependencies: () => [TimeAndDatePlugin, AnyTypedLogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Censor/CensorPlugin.ts b/backend/src/plugins/Censor/CensorPlugin.ts index 27f8ed9f..8a533093 100644 --- a/backend/src/plugins/Censor/CensorPlugin.ts +++ b/backend/src/plugins/Censor/CensorPlugin.ts @@ -55,7 +55,7 @@ export const CensorPlugin = zeppelinGuildPlugin()({ legacy: true, }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts index 3349c242..8bdd4133 100644 --- a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts +++ b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts @@ -8,7 +8,7 @@ export const ChannelArchiverPlugin = zeppelinGuildPlugin [TimeAndDatePlugin], configSchema: t.type({}), // prettier-ignore diff --git a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts index 54196d78..cadee696 100644 --- a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts +++ b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts @@ -24,7 +24,7 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index d9952d48..8cffe3d1 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -52,5 +52,5 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ loadAllCommands(pluginData); }, - dependencies: [MutesPlugin, LogsPlugin, UtilityPlugin], + dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin], }); diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index c01ab594..69cedd1e 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -108,6 +108,10 @@ import { logVoiceChannelLeave } from "./logFunctions/logVoiceChannelLeave"; import { logVoiceChannelMove } from "./logFunctions/logVoiceChannelMove"; import { logMemberTimedUnban } from "./logFunctions/logMemberTimedUnban"; import { logDmFailed } from "./logFunctions/logDmFailed"; +import { CasesPlugin } from "../Cases/CasesPlugin"; + +// Workaround for circular dependency +const AnyTypedCasesPlugin = CasesPlugin as any; const defaultOptions: PluginOptions = { config: { @@ -139,7 +143,7 @@ export const LogsPlugin = zeppelinGuildPlugin()({ prettyName: "Logs", }, - dependencies: [TimeAndDatePlugin], + dependencies: () => [TimeAndDatePlugin, AnyTypedCasesPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 0a1ac232..4453efe8 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -122,7 +122,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: [TimeAndDatePlugin, CasesPlugin, MutesPlugin, LogsPlugin], + dependencies: () => [TimeAndDatePlugin, CasesPlugin, MutesPlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index b845c299..91490491 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -70,7 +70,7 @@ export const MutesPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, - dependencies: [CasesPlugin, LogsPlugin], + dependencies: () => [CasesPlugin, LogsPlugin], defaultOptions, // prettier-ignore diff --git a/backend/src/plugins/Persist/PersistPlugin.ts b/backend/src/plugins/Persist/PersistPlugin.ts index ede40235..3ba19fc3 100644 --- a/backend/src/plugins/Persist/PersistPlugin.ts +++ b/backend/src/plugins/Persist/PersistPlugin.ts @@ -27,7 +27,7 @@ export const PersistPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Post/PostPlugin.ts b/backend/src/plugins/Post/PostPlugin.ts index e6bd9355..1b176252 100644 --- a/backend/src/plugins/Post/PostPlugin.ts +++ b/backend/src/plugins/Post/PostPlugin.ts @@ -36,7 +36,7 @@ export const PostPlugin = zeppelinGuildPlugin()({ prettyName: "Post", }, - dependencies: [TimeAndDatePlugin, LogsPlugin], + dependencies: () => [TimeAndDatePlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index d289f6db..752c4c36 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -108,7 +108,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin( prettyName: "Reaction roles", }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Reminders/RemindersPlugin.ts b/backend/src/plugins/Reminders/RemindersPlugin.ts index e1a39417..058c5a46 100644 --- a/backend/src/plugins/Reminders/RemindersPlugin.ts +++ b/backend/src/plugins/Reminders/RemindersPlugin.ts @@ -29,7 +29,7 @@ export const RemindersPlugin = zeppelinGuildPlugin()({ prettyName: "Reminders", }, - dependencies: [TimeAndDatePlugin], + dependencies: () => [TimeAndDatePlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Roles/RolesPlugin.ts b/backend/src/plugins/Roles/RolesPlugin.ts index c55e42c1..c643b6d1 100644 --- a/backend/src/plugins/Roles/RolesPlugin.ts +++ b/backend/src/plugins/Roles/RolesPlugin.ts @@ -42,7 +42,7 @@ export const RolesPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], defaultOptions, // prettier-ignore diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index f7dd06a3..44fa92d8 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -42,7 +42,7 @@ export const SlowmodePlugin = zeppelinGuildPlugin()({ prettyName: "Slowmode", }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Spam/SpamPlugin.ts b/backend/src/plugins/Spam/SpamPlugin.ts index 87dc50ad..28cae2be 100644 --- a/backend/src/plugins/Spam/SpamPlugin.ts +++ b/backend/src/plugins/Spam/SpamPlugin.ts @@ -54,7 +54,7 @@ export const SpamPlugin = zeppelinGuildPlugin()({ legacy: true, }, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 0952eace..0b5763ff 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -61,7 +61,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ }, configSchema: ConfigSchema, - dependencies: [LogsPlugin], + dependencies: () => [LogsPlugin], defaultOptions, // prettier-ignore diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 3bad5d72..5a3e6636 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -119,7 +119,7 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ prettyName: "Utility", }, - dependencies: [TimeAndDatePlugin, ModActionsPlugin, LogsPlugin], + dependencies: () => [TimeAndDatePlugin, ModActionsPlugin, LogsPlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts index 400eff28..1bbcdb45 100644 --- a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts +++ b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts @@ -21,7 +21,7 @@ export const WelcomeMessagePlugin = zeppelinGuildPlugin [LogsPlugin], defaultOptions, // prettier-ignore From 59e75e058482070f5474a6799230ea6129df14f3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:01:06 +0300 Subject: [PATCH 012/191] Type fixes + circular dependency fix --- backend/package-lock.json | 14 ++++---- backend/package.json | 2 +- backend/src/plugins/Automod/actions/log.ts | 2 +- backend/src/plugins/Cases/CasesPlugin.ts | 10 +++--- backend/src/plugins/Logs/LogsPlugin.ts | 10 +++--- .../Logs/logFunctions/logAutomodAction.ts | 2 +- .../Logs/logFunctions/logMemberRoleAdd.ts | 2 +- .../Logs/logFunctions/logMemberRoleRemove.ts | 2 +- .../Logs/logFunctions/logMemberTimedUnmute.ts | 8 +++-- .../Logs/logFunctions/logMemberUnmute.ts | 9 ++--- .../Logs/logFunctions/logMessageDeleteAuto.ts | 2 +- .../src/plugins/ModActions/commands/BanCmd.ts | 2 +- .../src/plugins/Mutes/functions/unmuteUser.ts | 4 +-- .../src/plugins/Post/util/actualPostCmd.ts | 2 +- .../plugins/Post/util/scheduledPostLoop.ts | 8 ++--- .../events/ButtonInteractionEvt.ts | 18 ++++------ .../util/buttonActionHandlers.ts | 36 +++++++------------ .../src/plugins/Roles/commands/AddRoleCmd.ts | 8 ++--- .../src/plugins/Utility/commands/VcmoveCmd.ts | 4 +-- .../events/SendWelcomeMessageEvt.ts | 8 ++--- 20 files changed, 70 insertions(+), 83 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index c3e38feb..ab298c68 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.41", + "knub": "^30.0.0-beta.42", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3043,9 +3043,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.41", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.41.tgz", - "integrity": "sha512-tKHGG9vh62ZN0JIi2ULYHvJbR28WXhym8c9vRtyQCPQqJ1w3n7Fow9JXbh6BUTSD8QszWbvq1Ri22wbQtBEyNw==", + "version": "30.0.0-beta.42", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz", + "integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8290,9 +8290,9 @@ } }, "knub": { - "version": "30.0.0-beta.41", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.41.tgz", - "integrity": "sha512-tKHGG9vh62ZN0JIi2ULYHvJbR28WXhym8c9vRtyQCPQqJ1w3n7Fow9JXbh6BUTSD8QszWbvq1Ri22wbQtBEyNw==", + "version": "30.0.0-beta.42", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz", + "integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index 67724ff9..5ef83427 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.41", + "knub": "^30.0.0-beta.42", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", diff --git a/backend/src/plugins/Automod/actions/log.ts b/backend/src/plugins/Automod/actions/log.ts index b7470427..4a78442d 100644 --- a/backend/src/plugins/Automod/actions/log.ts +++ b/backend/src/plugins/Automod/actions/log.ts @@ -19,7 +19,7 @@ export const LogAction = automodAction({ user, users, actionsTaken, - matchSummary: matchResult.summary, + matchSummary: matchResult.summary ?? "", }); }, }); diff --git a/backend/src/plugins/Cases/CasesPlugin.ts b/backend/src/plugins/Cases/CasesPlugin.ts index fd6c1e41..5a8c1b45 100644 --- a/backend/src/plugins/Cases/CasesPlugin.ts +++ b/backend/src/plugins/Cases/CasesPlugin.ts @@ -16,10 +16,6 @@ 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"; - -// Workaround for circular dependency -const AnyTypedLogsPlugin = LogsPlugin as any; const defaultOptions = { config: { @@ -42,7 +38,11 @@ export const CasesPlugin = zeppelinGuildPlugin()({ `), }, - dependencies: () => [TimeAndDatePlugin, AnyTypedLogsPlugin], + dependencies: async () => [ + TimeAndDatePlugin, + // The `as any` cast here is to prevent TypeScript from locking up from the circular dependency + ((await import("../Logs/LogsPlugin")) as any).LogsPlugin, + ], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index 69cedd1e..7fe0ea6c 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -108,10 +108,6 @@ import { logVoiceChannelLeave } from "./logFunctions/logVoiceChannelLeave"; import { logVoiceChannelMove } from "./logFunctions/logVoiceChannelMove"; import { logMemberTimedUnban } from "./logFunctions/logMemberTimedUnban"; import { logDmFailed } from "./logFunctions/logDmFailed"; -import { CasesPlugin } from "../Cases/CasesPlugin"; - -// Workaround for circular dependency -const AnyTypedCasesPlugin = CasesPlugin as any; const defaultOptions: PluginOptions = { config: { @@ -143,7 +139,11 @@ export const LogsPlugin = zeppelinGuildPlugin()({ prettyName: "Logs", }, - dependencies: () => [TimeAndDatePlugin, AnyTypedCasesPlugin], + dependencies: async () => [ + TimeAndDatePlugin, + // The `as any` cast here is to prevent TypeScript from locking up from the circular dependency + ((await import("../Cases/CasesPlugin")) as any).CasesPlugin, + ], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts index 1874f24a..b76194a9 100644 --- a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts +++ b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts @@ -23,7 +23,7 @@ export function logAutomodAction(pluginData: GuildPluginData, da user: userToTemplateSafeUser(data.user), users: data.users.map(user => userToTemplateSafeUser(user)), actionsTaken: data.actionsTaken, - matchSummary: data.matchSummary, + matchSummary: data.matchSummary ?? "", }), { userId: data.user.id, diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts index 032e5781..bfbc03fc 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts @@ -17,7 +17,7 @@ export function logMemberRoleAdd(pluginData: GuildPluginData, da pluginData, LogType.MEMBER_ROLE_ADD, createTypedTemplateSafeValueContainer({ - mod: userToTemplateSafeUser(data.mod), + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), roles: data.roles.map(r => r.name).join(", "), }), diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts index 98a7acdb..a32f9cd9 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts @@ -17,7 +17,7 @@ export function logMemberRoleRemove(pluginData: GuildPluginData, pluginData, LogType.MEMBER_ROLE_REMOVE, createTypedTemplateSafeValueContainer({ - mod: userToTemplateSafeUser(data.mod), + mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), roles: data.roles.map(r => r.name).join(", "), }), diff --git a/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts index be850eb8..72ccea64 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberTimedUnmute.ts @@ -5,10 +5,11 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { User } from "discord.js"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { UnknownUser } from "../../../utils"; interface LogMemberTimedUnmuteData { mod: User; - user: User; + user: User | UnknownUser; time: string; caseNumber: number; reason: string; @@ -25,6 +26,9 @@ export function logMemberTimedUnmute(pluginData: GuildPluginData 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/logMemberUnmute.ts b/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts index a3b9b942..ef2aca40 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberUnmute.ts @@ -5,10 +5,11 @@ 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 LogMemberUnmuteData { - mod: GuildMember; - user: User; + mod: User; + user: User | UnknownUser; caseNumber: number; reason: string; } @@ -18,14 +19,14 @@ export function logMemberUnmute(pluginData: GuildPluginData, dat pluginData, LogType.MEMBER_UNMUTE, createTypedTemplateSafeValueContainer({ - mod: memberToTemplateSafeMember(data.mod), + mod: userToTemplateSafeUser(data.mod), user: userToTemplateSafeUser(data.user), caseNumber: data.caseNumber, reason: data.reason, }), { userId: data.user.id, - bot: data.user.bot, + 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 index ad8b9b72..4ce4694c 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts @@ -31,7 +31,7 @@ export function logMessageDeleteAuto(pluginData: GuildPluginData }), { userId: data.user.id, - bot: data.user.bot, + bot: data.user instanceof User ? data.user.bot : false, channel: data.channel.id, category: data.channel.parentId, }, diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 34512a14..40a352c7 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -119,7 +119,7 @@ export const BanCmd = modActionsCmd({ }); } else { pluginData.getPlugin(LogsPlugin).logMemberBan({ - mod, + mod: mod.user, user, caseNumber: createdCase.case_number, reason, diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index 04ee3d59..73a07b80 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -93,14 +93,14 @@ export async function unmuteUser( user, caseNumber: createdCase.case_number, time: timeUntilUnmute, - reason: caseArgs.reason, + reason: caseArgs.reason ?? "", }); } else { pluginData.getPlugin(LogsPlugin).logMemberUnmute({ mod, user, caseNumber: createdCase.case_number, - reason: caseArgs.reason, + reason: caseArgs.reason ?? "", }); } diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index 388f03a1..e5e25d70 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -192,7 +192,7 @@ export async function actualPostCmd( date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), repeatInterval: humanizeDuration(opts.repeat), - repeatDetails: repeatDetailsStr, + repeatDetails: repeatDetailsStr ?? "", }); } diff --git a/backend/src/plugins/Post/util/scheduledPostLoop.ts b/backend/src/plugins/Post/util/scheduledPostLoop.ts index 1c992311..2a946c1e 100644 --- a/backend/src/plugins/Post/util/scheduledPostLoop.ts +++ b/backend/src/plugins/Post/util/scheduledPostLoop.ts @@ -4,7 +4,7 @@ import moment from "moment-timezone"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { DBDateFormat, SECONDS } from "../../../utils"; +import { DBDateFormat, SECONDS, verboseChannelMention, verboseUserMention } from "../../../utils"; import { PostPluginType } from "../types"; import { postMessage } from "./postMessage"; import { LogsPlugin } from "../../Logs/LogsPlugin"; @@ -38,9 +38,9 @@ export async function scheduledPostLoop(pluginData: GuildPluginDataObject).values(ButtonMenuActions).includes(context.action)) { await sendEphemeralReply(int, `A internal error was encountered, please contact the Administrators!`); - meta.pluginData - .getPlugin(LogsPlugin) - .log( - LogType.BOT_ALERT, - `**A internal error occurred** on buttons for message ${int.message.id}, action **${context.action}** is not known`, - ); + meta.pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `**A internal error occurred** on buttons for message ${int.message.id}, action **${context.action}** is not known`, + }); return; } diff --git a/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts b/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts index 446cc0ee..8c9884a9 100644 --- a/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts +++ b/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts @@ -18,12 +18,9 @@ export async function handleOpenMenu( content: `A configuration error was encountered, please contact the Administrators!`, ephemeral: true, }); - pluginData - .getPlugin(LogsPlugin) - .log( - LogType.BOT_ALERT, - `**A configuration error occurred** on buttons for message ${int.message.id}, no menus found in config`, - ); + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `**A configuration error occurred** on buttons for message ${int.message.id}, no menus found in config`, + }); return; } @@ -48,12 +45,9 @@ export async function handleOpenMenu( content: `A configuration error was encountered, please contact the Administrators!`, ephemeral: true, }); - pluginData - .getPlugin(LogsPlugin) - .log( - LogType.BOT_ALERT, - `**A configuration error occurred** on buttons for message ${int.message.id}, menu **${context.roleOrMenu}** not found in config`, - ); + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `**A configuration error occurred** on buttons for message ${int.message.id}, menu **${context.roleOrMenu}** not found in config`, + }); return; } const rows = splitButtonsIntoRows(menuButtons, Object.values(group.button_menus[context.roleOrMenu])); // new MessageActionRow().addComponents(menuButtons); @@ -73,12 +67,9 @@ export async function handleModifyRole( content: `A configuration error was encountered, please contact the Administrators!`, ephemeral: true, }); - pluginData - .getPlugin(LogsPlugin) - .log( - LogType.BOT_ALERT, - `**A configuration error occurred** on buttons for message ${int.message.id}, role **${context.roleOrMenu}** not found on server`, - ); + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `**A configuration error occurred** on buttons for message ${int.message.id}, role **${context.roleOrMenu}** not found on server`, + }); return; } @@ -96,11 +87,8 @@ export async function handleModifyRole( content: "A configuration error was encountered, please contact the Administrators!", ephemeral: true, }); - pluginData - .getPlugin(LogsPlugin) - .log( - LogType.BOT_ALERT, - `**A configuration error occurred** on buttons for message ${int.message.id}, error: ${e}. We might be missing permissions!`, - ); + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `**A configuration error occurred** on buttons for message ${int.message.id}, error: ${e}. We might be missing permissions!`, + }); } } diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index 564b9f77..23229ca5 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -54,10 +54,10 @@ export const AddRoleCmd = rolesCmd({ await args.member.roles.add(roleId); - pluginData.getPlugin(LogsPlugin).logMemberRoleAdd(LogType.MEMBER_ROLE_ADD, { - member: memberToTemplateSafeMember(args.member), - roles: role.name, - mod: userToTemplateSafeUser(msg.author), + pluginData.getPlugin(LogsPlugin).logMemberRoleAdd({ + mod: msg.author, + member: args.member, + roles: [role], }); sendSuccessMessage( diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index e7a834a7..0f852e14 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -69,7 +69,7 @@ export const VcmoveCmd = utilityCmd({ return; } - const oldVoiceChannel = pluginData.guild.channels.cache.get(args.member.voice.channelId); + const oldVoiceChannel = pluginData.guild.channels.cache.get(args.member.voice.channelId) as VoiceChannel; try { await args.member.edit({ @@ -83,7 +83,7 @@ export const VcmoveCmd = utilityCmd({ pluginData.getPlugin(LogsPlugin).logVoiceChannelForceMove({ mod: msg.author, member: args.member, - oldChannel: oldVoiceChannel!, + oldChannel: oldVoiceChannel, newChannel: channel, }); diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index 11afa607..bf124fa0 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -6,7 +6,7 @@ import { } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; -import { createChunkedMessage, stripObjectToScalars } from "../../../utils"; +import { createChunkedMessage, stripObjectToScalars, verboseChannelMention, verboseUserMention } from "../../../utils"; import { sendDM } from "../../../utils/sendDM"; import { welcomeMessageEvt } from "../types"; import { LogsPlugin } from "../../Logs/LogsPlugin"; @@ -68,9 +68,9 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ await createChunkedMessage(channel, formatted); } catch { pluginData.getPlugin(LogsPlugin).logBotAlert({ - body: `Failed send a welcome message for {userMention(member)} to {channelMention(channel)}`, - member, - channel, + body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention( + channel, + )}`, }); } } From e6286c3eaa196e7ed328cb27693ae0471e3412f5 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:09:25 +0300 Subject: [PATCH 013/191] Fix missing content in message archives --- backend/src/data/GuildArchives.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/backend/src/data/GuildArchives.ts b/backend/src/data/GuildArchives.ts index 066714a1..2056ed8d 100644 --- a/backend/src/data/GuildArchives.ts +++ b/backend/src/data/GuildArchives.ts @@ -82,20 +82,23 @@ export class GuildArchives extends BaseGuildRepository { const channel = guild.channels.cache.get(msg.channel_id as Snowflake); const partialUser = new TemplateSafeValueContainer({ ...msg.data.author, id: msg.user_id }); - const values = new TemplateSafeValueContainer({ - id: msg.id, - timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), - content: msg.data.content, - attachments: msg.data.attachments?.map(att => { - return JSON.stringify({ name: att.name, url: att.url, type: att.contentType }); + const line = await renderTemplate( + MESSAGE_ARCHIVE_MESSAGE_FORMAT, + new TemplateSafeValueContainer({ + id: msg.id, + timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), + content: msg.data.content, + attachments: msg.data.attachments?.map(att => { + return JSON.stringify({ name: att.name, url: att.url, type: att.contentType }); + }), + stickers: msg.data.stickers?.map(sti => { + return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) }); + }), + user: partialUser, + channel: channel ? channelToTemplateSafeChannel(channel) : null, }), - stickers: msg.data.stickers?.map(sti => { - return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) }); - }), - user: partialUser, - channel: channel ? channelToTemplateSafeChannel(channel) : null, - }); - const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, {}); + ); + msgLines.push(line); } return msgLines; From e16eb8c8d15fc121f3dd4da21eb48935847af8de Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:23:34 +0300 Subject: [PATCH 014/191] Add stricter type check for TemplateSafeValueContainer in templateFormatter --- backend/src/templateFormatter.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/templateFormatter.ts b/backend/src/templateFormatter.ts index f7508ca4..f9e80d96 100644 --- a/backend/src/templateFormatter.ts +++ b/backend/src/templateFormatter.ts @@ -57,6 +57,9 @@ function isTemplateSafeValue(value: unknown): value is TemplateSafeValue { } export class TemplateSafeValueContainer { + // Fake property used for stricter type checks since TypeScript uses structural typing + _isTemplateSafeValueContainer: true; + [key: string]: TemplateSafeValue; constructor(data: Record = {}) { From d109a58cb75b550a07b18c7e4744bb93983db35f Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:32:45 +0300 Subject: [PATCH 015/191] Type fixes + use template safe values for renderTemplate() everywhere --- backend/src/data/GuildArchives.ts | 9 ++- backend/src/plugins/Automod/actions/reply.ts | 11 ++-- .../CustomEvents/CustomEventsPlugin.ts | 32 ++++++++++- .../CustomEvents/actions/addRoleAction.ts | 4 +- .../CustomEvents/actions/createCaseAction.ts | 4 +- .../actions/makeRoleMentionableAction.ts | 3 +- .../actions/makeRoleUnmentionableAction.ts | 3 +- .../CustomEvents/actions/messageAction.ts | 4 +- .../actions/moveToVoiceChannelAction.ts | 4 +- .../actions/setChannelPermissionOverrides.ts | 3 +- .../CustomEvents/functions/runEvent.ts | 3 +- backend/src/plugins/Logs/LogsPlugin.ts | 24 +++++--- .../Logs/logFunctions/logMemberMuteExpired.ts | 5 +- .../plugins/ModActions/functions/banUserId.ts | 38 +++++++------ .../ModActions/functions/kickMember.ts | 19 ++++--- .../ModActions/functions/warnMember.ts | 19 ++++--- .../src/plugins/Mutes/functions/muteUser.ts | 21 ++++--- .../events/SendWelcomeMessageEvt.ts | 17 +++--- backend/src/templateFormatter.test.ts | 6 +- backend/src/utils/isScalar.ts | 3 + backend/src/utils/templateSafeObjects.ts | 56 +++++++++++++------ 21 files changed, 190 insertions(+), 98 deletions(-) create mode 100644 backend/src/utils/isScalar.ts diff --git a/backend/src/data/GuildArchives.ts b/backend/src/data/GuildArchives.ts index 2056ed8d..4fae975b 100644 --- a/backend/src/data/GuildArchives.ts +++ b/backend/src/data/GuildArchives.ts @@ -109,9 +109,12 @@ export class GuildArchives extends BaseGuildRepository { expiresAt = moment.utc().add(DEFAULT_EXPIRY_DAYS, "days"); } - const headerStr = await renderTemplate(MESSAGE_ARCHIVE_HEADER_FORMAT, { - guild: guildToTemplateSafeGuild(guild), - }); + const headerStr = await renderTemplate( + MESSAGE_ARCHIVE_HEADER_FORMAT, + new TemplateSafeValueContainer({ + guild: guildToTemplateSafeGuild(guild), + }), + ); const msgLines = await this.renderLinesFromSavedMessages(savedMessages, guild); const messagesStr = msgLines.join("\n"); diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index 69fcf937..c3dcded6 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -2,7 +2,7 @@ import { MessageOptions, Permissions, Snowflake, TextChannel, User } from "disco import * as t from "io-ts"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { convertDelayStringToMS, noop, @@ -48,9 +48,12 @@ export const ReplyAction = automodAction({ const user = users[0]; const renderReplyText = async str => - renderTemplate(str, { - user: userToTemplateSafeUser(user), - }); + renderTemplate( + str, + new TemplateSafeValueContainer({ + user: userToTemplateSafeUser(user), + }), + ); const formatted = typeof actionConfig === "string" ? await renderReplyText(actionConfig) diff --git a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts index c1c183eb..009bf5a6 100644 --- a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts +++ b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts @@ -1,9 +1,18 @@ import { parseSignature, typedGuildCommand } from "knub"; import { commandTypes } from "../../commandTypes"; -import { stripObjectToScalars } from "../../utils"; +import { stripObjectToScalars, UnknownUser } from "../../utils"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { runEvent } from "./functions/runEvent"; import { ConfigSchema, CustomEventsPluginType } from "./types"; +import { createTypedTemplateSafeValueContainer, TemplateSafeValueContainer } from "../../templateFormatter"; +import { Channel, GuildChannel, GuildMember, ThreadChannel, User } from "discord.js"; +import { + channelToTemplateSafeChannel, + memberToTemplateSafeMember, + messageToTemplateSafeMessage, + userToTemplateSafeUser, +} from "../../utils/templateSafeObjects"; +import { isScalar } from "../../utils/isScalar"; const defaultOptions = { config: { @@ -28,8 +37,25 @@ export const CustomEventsPlugin = zeppelinGuildPlugin()( permission: `events.${key}.trigger.can_use`, signature, run({ message, args }) { - const strippedMsg = stripObjectToScalars(message, ["channel", "author"]); - runEvent(pluginData, event, { msg: message, args }, { args, msg: strippedMsg }); + const safeArgs = new TemplateSafeValueContainer(); + for (const [argKey, argValue] of Object.entries(args as Record)) { + if (argValue instanceof User || argValue instanceof UnknownUser) { + safeArgs[argKey] = userToTemplateSafeUser(argValue); + } else if (argValue instanceof GuildMember) { + safeArgs[argKey] = memberToTemplateSafeMember(argValue); + } else if (argValue instanceof GuildChannel || argValue instanceof ThreadChannel) { + safeArgs[argKey] = channelToTemplateSafeChannel(argValue); + } else if (isScalar(argValue)) { + safeArgs[argKey] = argValue; + } + } + + const values = createTypedTemplateSafeValueContainer({ + ...args, + msg: messageToTemplateSafeMessage(message), + }); + + runEvent(pluginData, event, { msg: message, args }, values); }, }); pluginData.commands.add(eventCommand); diff --git a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts index 6621763d..4fb21bf1 100644 --- a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts +++ b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts @@ -2,7 +2,7 @@ import { Snowflake } from "discord.js"; import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { canActOn } from "../../../pluginUtils"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { resolveMember } from "../../../utils"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; @@ -17,7 +17,7 @@ export type TAddRoleAction = t.TypeOf; export async function addRoleAction( pluginData: GuildPluginData, action: TAddRoleAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/actions/createCaseAction.ts b/backend/src/plugins/CustomEvents/actions/createCaseAction.ts index e9e4fac1..c3203389 100644 --- a/backend/src/plugins/CustomEvents/actions/createCaseAction.ts +++ b/backend/src/plugins/CustomEvents/actions/createCaseAction.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; @@ -18,7 +18,7 @@ export type TCreateCaseAction = t.TypeOf; export async function createCaseAction( pluginData: GuildPluginData, action: TCreateCaseAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts b/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts index 11974a7a..73994d2e 100644 --- a/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts +++ b/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts @@ -4,6 +4,7 @@ import { GuildPluginData } from "knub"; import { convertDelayStringToMS, noop, tDelayString } from "../../../utils"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { TemplateSafeValueContainer } from "../../../templateFormatter"; export const MakeRoleMentionableAction = t.type({ type: t.literal("make_role_mentionable"), @@ -15,7 +16,7 @@ export type TMakeRoleMentionableAction = t.TypeOf, action: TMakeRoleMentionableAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts b/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts index 505cb3f7..1975b1c1 100644 --- a/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts +++ b/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts @@ -3,6 +3,7 @@ import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { TemplateSafeValueContainer } from "../../../templateFormatter"; export const MakeRoleUnmentionableAction = t.type({ type: t.literal("make_role_unmentionable"), @@ -13,7 +14,7 @@ export type TMakeRoleUnmentionableAction = t.TypeOf, action: TMakeRoleUnmentionableAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/actions/messageAction.ts b/backend/src/plugins/CustomEvents/actions/messageAction.ts index 90630f1f..3c4bba46 100644 --- a/backend/src/plugins/CustomEvents/actions/messageAction.ts +++ b/backend/src/plugins/CustomEvents/actions/messageAction.ts @@ -1,7 +1,7 @@ import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; import { GuildPluginData } from "knub"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType } from "../types"; @@ -15,7 +15,7 @@ export type TMessageAction = t.TypeOf; export async function messageAction( pluginData: GuildPluginData, action: TMessageAction, - values: any, + values: TemplateSafeValueContainer, ) { const targetChannelId = await renderTemplate(action.channel, values, false); const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake); diff --git a/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts b/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts index 08507bac..73c6725c 100644 --- a/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts +++ b/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts @@ -2,7 +2,7 @@ import { Snowflake, VoiceChannel } from "discord.js"; import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { canActOn } from "../../../pluginUtils"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { resolveMember } from "../../../utils"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; @@ -17,7 +17,7 @@ export type TMoveToVoiceChannelAction = t.TypeOf, action: TMoveToVoiceChannelAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts index 80acd61c..31d60ed5 100644 --- a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts +++ b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts @@ -3,6 +3,7 @@ import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ActionError } from "../ActionError"; import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { TemplateSafeValueContainer } from "../../../templateFormatter"; export const SetChannelPermissionOverridesAction = t.type({ type: t.literal("set_channel_permission_overrides"), @@ -21,7 +22,7 @@ export type TSetChannelPermissionOverridesAction = t.TypeOf, action: TSetChannelPermissionOverridesAction, - values: any, + values: TemplateSafeValueContainer, event: TCustomEvent, eventData: any, ) { diff --git a/backend/src/plugins/CustomEvents/functions/runEvent.ts b/backend/src/plugins/CustomEvents/functions/runEvent.ts index 1322d41b..404774fd 100644 --- a/backend/src/plugins/CustomEvents/functions/runEvent.ts +++ b/backend/src/plugins/CustomEvents/functions/runEvent.ts @@ -10,12 +10,13 @@ import { messageAction } from "../actions/messageAction"; import { moveToVoiceChannelAction } from "../actions/moveToVoiceChannelAction"; import { setChannelPermissionOverridesAction } from "../actions/setChannelPermissionOverrides"; import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { TemplateSafeValueContainer } from "../../../templateFormatter"; export async function runEvent( pluginData: GuildPluginData, event: TCustomEvent, eventData: any, - values: any, + values: TemplateSafeValueContainer, ) { try { for (const action of event.actions) { diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index 7fe0ea6c..03423404 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -36,7 +36,11 @@ 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 { + createTypedTemplateSafeValueContainer, + TemplateSafeValueContainer, + TypedTemplateSafeValueContainer, +} from "../../templateFormatter"; import { mapToPublicFn } from "../../pluginUtils"; import { logAutomodAction } from "./logFunctions/logAutomodAction"; @@ -284,15 +288,19 @@ export const LogsPlugin = zeppelinGuildPlugin()({ state.regexRunnerRepeatedTimeoutListener = (regexSource, timeoutMs, failedTimes) => { logger.warn(`Disabled heavy regex temporarily: ${regexSource}`); - log(pluginData, LogType.BOT_ALERT, { - body: - ` + log( + pluginData, + LogType.BOT_ALERT, + createTypedTemplateSafeValueContainer({ + body: + ` The following regex has taken longer than ${timeoutMs}ms for ${failedTimes} times and has been temporarily disabled: `.trim() + - "\n```" + - Util.escapeCodeBlock(regexSource) + - "```", - }); + "\n```" + + Util.escapeCodeBlock(regexSource) + + "```", + }), + ); }; state.regexRunner.on("repeatedTimeout", state.regexRunnerRepeatedTimeoutListener); }, diff --git a/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts b/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts index be203de7..1a969120 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberMuteExpired.ts @@ -19,7 +19,10 @@ export function logMemberMuteExpired(pluginData: GuildPluginData const member = data.member instanceof GuildMember ? memberToTemplateSafeMember(data.member) - : new TemplateSafeUnknownMember({ ...data.member, user: new TemplateSafeUnknownUser({ ...data.member }) }); + : new TemplateSafeUnknownMember({ + ...data.member, + user: new TemplateSafeUnknownUser({ ...data.member }), + }); const roles = data.member instanceof GuildMember ? Array.from(data.member.roles.cache.keys()) : []; diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts index e9275fc2..0fc69371 100644 --- a/backend/src/plugins/ModActions/functions/banUserId.ts +++ b/backend/src/plugins/ModActions/functions/banUserId.ts @@ -5,7 +5,7 @@ import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { createUserNotificationError, notifyUser, @@ -48,24 +48,30 @@ export async function banUserId( if (contactMethods.length) { if (!banTime && config.ban_message) { - const banMessage = await renderTemplate(config.ban_message, { - guildName: pluginData.guild.name, - reason, - moderator: banOptions.caseArgs?.modId - ? stripObjectToScalars(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) - : {}, - }); + const banMessage = await renderTemplate( + config.ban_message, + new TemplateSafeValueContainer({ + guildName: pluginData.guild.name, + reason, + moderator: banOptions.caseArgs?.modId + ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) + : null, + }), + ); notifyResult = await notifyUser(user, banMessage, contactMethods); } else if (banTime && config.tempban_message) { - const banMessage = await renderTemplate(config.tempban_message, { - guildName: pluginData.guild.name, - reason, - moderator: banOptions.caseArgs?.modId - ? stripObjectToScalars(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) - : {}, - banTime: humanizeDuration(banTime), - }); + const banMessage = await renderTemplate( + config.tempban_message, + new TemplateSafeValueContainer({ + guildName: pluginData.guild.name, + reason, + moderator: banOptions.caseArgs?.modId + ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) + : null, + banTime: humanizeDuration(banTime), + }), + ); notifyResult = await notifyUser(user, banMessage, contactMethods); } else { diff --git a/backend/src/plugins/ModActions/functions/kickMember.ts b/backend/src/plugins/ModActions/functions/kickMember.ts index d74cd727..d70722a1 100644 --- a/backend/src/plugins/ModActions/functions/kickMember.ts +++ b/backend/src/plugins/ModActions/functions/kickMember.ts @@ -3,7 +3,7 @@ import { GuildPluginData } from "knub"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { createUserNotificationError, notifyUser, resolveUser, ucfirst, UserNotificationResult } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { IgnoredEventType, KickOptions, KickResult, ModActionsPluginType } from "../types"; @@ -31,13 +31,16 @@ export async function kickMember( if (contactMethods.length) { if (config.kick_message) { - const kickMessage = await renderTemplate(config.kick_message, { - guildName: pluginData.guild.name, - reason, - moderator: kickOptions.caseArgs?.modId - ? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) - : {}, - }); + const kickMessage = await renderTemplate( + config.kick_message, + new TemplateSafeValueContainer({ + guildName: pluginData.guild.name, + reason, + moderator: kickOptions.caseArgs?.modId + ? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) + : null, + }), + ); notifyResult = await notifyUser(member.user, kickMessage, contactMethods); } else { diff --git a/backend/src/plugins/ModActions/functions/warnMember.ts b/backend/src/plugins/ModActions/functions/warnMember.ts index 75d28735..e0f22c8f 100644 --- a/backend/src/plugins/ModActions/functions/warnMember.ts +++ b/backend/src/plugins/ModActions/functions/warnMember.ts @@ -3,7 +3,7 @@ import { GuildPluginData } from "knub"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { createUserNotificationError, notifyUser, resolveUser, ucfirst, UserNotificationResult } from "../../../utils"; import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { CasesPlugin } from "../../Cases/CasesPlugin"; @@ -21,13 +21,16 @@ export async function warnMember( let notifyResult: UserNotificationResult; if (config.warn_message) { - const warnMessage = await renderTemplate(config.warn_message, { - guildName: pluginData.guild.name, - reason, - moderator: warnOptions.caseArgs?.modId - ? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) - : {}, - }); + const warnMessage = await renderTemplate( + config.warn_message, + new TemplateSafeValueContainer({ + guildName: pluginData.guild.name, + reason, + moderator: warnOptions.caseArgs?.modId + ? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) + : null, + }), + ); const contactMethods = warnOptions?.contactMethods ? warnOptions.contactMethods : getDefaultContactMethods(pluginData, "warn"); diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index fffb7587..4c39e091 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -7,7 +7,7 @@ import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { notifyUser, resolveMember, @@ -151,14 +151,17 @@ export async function muteUser( const muteMessage = template && - (await renderTemplate(template, { - guildName: pluginData.guild.name, - reason: reason || "None", - time: timeUntilUnmute, - moderator: muteOptions.caseArgs?.modId - ? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) - : "", - })); + (await renderTemplate( + template, + new TemplateSafeValueContainer({ + guildName: pluginData.guild.name, + reason: reason || "None", + time: timeUntilUnmute, + moderator: muteOptions.caseArgs?.modId + ? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) + : null, + }), + )); if (muteMessage && user instanceof User) { let contactMethods: UserNotificationMethod[] = []; diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index bf124fa0..750c6c29 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -1,11 +1,12 @@ import { Snowflake, TextChannel } from "discord.js"; import { channelToTemplateSafeChannel, + guildToTemplateSafeGuild, memberToTemplateSafeMember, userToTemplateSafeUser, } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; -import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; +import { renderTemplate, TemplateParseError, TemplateSafeValueContainer } from "../../../templateFormatter"; import { createChunkedMessage, stripObjectToScalars, verboseChannelMention, verboseUserMention } from "../../../utils"; import { sendDM } from "../../../utils/sendDM"; import { welcomeMessageEvt } from "../types"; @@ -32,12 +33,14 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ let formatted; try { - const strippedMember = stripObjectToScalars(member, ["user", "guild"]); - formatted = await renderTemplate(config.message, { - member: strippedMember, - user: strippedMember["user"], - guild: strippedMember["guild"], - }); + formatted = await renderTemplate( + config.message, + new TemplateSafeValueContainer({ + member: memberToTemplateSafeMember(member), + user: userToTemplateSafeUser(member.user), + guild: guildToTemplateSafeGuild(member.guild), + }), + ); } catch (e) { if (e instanceof TemplateParseError) { pluginData.getPlugin(LogsPlugin).logBotAlert({ diff --git a/backend/src/templateFormatter.test.ts b/backend/src/templateFormatter.test.ts index 0d692e8b..39132731 100644 --- a/backend/src/templateFormatter.test.ts +++ b/backend/src/templateFormatter.test.ts @@ -1,5 +1,5 @@ import test from "ava"; -import { parseTemplate, renderParsedTemplate, renderTemplate } from "./templateFormatter"; +import { parseTemplate, renderParsedTemplate, renderTemplate, TemplateSafeValueContainer } from "./templateFormatter"; test("Parses plain string templates correctly", t => { const result = parseTemplate("foo bar baz"); @@ -75,7 +75,7 @@ test("Parses function variables with function variable arguments correctly", t = test("Renders a parsed template correctly", async t => { const parseResult = parseTemplate('foo {bar("str", 5.07, deeply(nested(8)))} baz'); - const values = { + const values = new TemplateSafeValueContainer({ bar(strArg, numArg, varArg) { return `${strArg} ${numArg} !${varArg}!`; }, @@ -85,7 +85,7 @@ test("Renders a parsed template correctly", async t => { nested(numArg) { return `?${numArg}?`; }, - }; + }); const renderResult = await renderParsedTemplate(parseResult, values); t.is(renderResult, "foo str 5.07 !! baz"); diff --git a/backend/src/utils/isScalar.ts b/backend/src/utils/isScalar.ts new file mode 100644 index 00000000..b5d9eb64 --- /dev/null +++ b/backend/src/utils/isScalar.ts @@ -0,0 +1,3 @@ +export function isScalar(value: unknown): value is string | number | boolean | null | undefined { + return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean"; +} diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts index 47bcf3de..7f46517f 100644 --- a/backend/src/utils/templateSafeObjects.ts +++ b/backend/src/utils/templateSafeObjects.ts @@ -3,6 +3,7 @@ import { Guild, GuildChannel, GuildMember, + Message, PartialGuildMember, Role, Snowflake, @@ -23,15 +24,18 @@ import { } from "../data/entities/SavedMessage"; import { Case } from "../data/entities/Case"; -type Props = { - [K in keyof T]: T[K]; -}; +type InputProps = Omit< + { + [K in keyof T]: T[K]; + }, + "_isTemplateSafeValueContainer" +>; export class TemplateSafeGuild extends TemplateSafeValueContainer { id: Snowflake; name: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -46,7 +50,7 @@ export class TemplateSafeUser extends TemplateSafeValueContainer { bot?: boolean; createdAt?: number; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -56,7 +60,7 @@ export class TemplateSafeUnknownUser extends TemplateSafeValueContainer { username: string; discriminator: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -68,7 +72,7 @@ export class TemplateSafeRole extends TemplateSafeValueContainer { hexColor: string; hoist: boolean; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -81,7 +85,7 @@ export class TemplateSafeMember extends TemplateSafeUser { // guildAvatarURL: string, Once DJS supports per-server avatars guildName: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -89,7 +93,7 @@ export class TemplateSafeMember extends TemplateSafeUser { export class TemplateSafeUnknownMember extends TemplateSafeUnknownUser { user: TemplateSafeUnknownUser; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -100,7 +104,7 @@ export class TemplateSafeChannel extends TemplateSafeValueContainer { mention: string; parentId?: Snowflake; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -112,7 +116,7 @@ export class TemplateSafeStage extends TemplateSafeValueContainer { discoverable: boolean; topic: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -125,7 +129,7 @@ export class TemplateSafeEmoji extends TemplateSafeValueContainer { identifier: string; mention: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -141,7 +145,7 @@ export class TemplateSafeSticker extends TemplateSafeValueContainer { animated: boolean; url: string; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -154,7 +158,7 @@ export class TemplateSafeSavedMessage extends TemplateSafeValueContainer { is_bot: boolean; data: TemplateSafeSavedMessageData; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -170,7 +174,7 @@ export class TemplateSafeSavedMessageData extends TemplateSafeValueContainer { stickers?: Array>; timestamp: number; - constructor(data: Props) { + constructor(data: InputProps) { super(data); } } @@ -191,7 +195,18 @@ export class TemplateSafeCase extends TemplateSafeValueContainer { pp_name: string | null; log_message_id: string | null; - constructor(data: Props) { + constructor(data: InputProps) { + super(data); + } +} + +export class TemplateSafeMessage extends TemplateSafeValueContainer { + id: string; + content: string; + author: TemplateSafeUser; + channel: TemplateSafeChannel; + + constructor(data: InputProps) { super(data); } } @@ -428,6 +443,15 @@ export function caseToTemplateSafeCase(theCase: Case): TemplateSafeCase { }); } +export function messageToTemplateSafeMessage(message: Message): TemplateSafeMessage { + return new TemplateSafeMessage({ + id: message.id, + content: message.content, + author: userToTemplateSafeUser(message.author), + channel: channelToTemplateSafeChannel(message.channel as GuildChannel | ThreadChannel), + }); +} + export function getTemplateSafeMemberLevel(pluginData: GuildPluginData, member: TemplateSafeMember): number { if (member.id === pluginData.guild.ownerId) { return 99999; From 0e7cf9715f7f3bb13f461f6fbb97eba1da185432 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:47:24 +0300 Subject: [PATCH 016/191] Thread fixes --- backend/src/index.ts | 8 +++++--- backend/src/plugins/Automod/actions/alert.ts | 4 ++-- backend/src/plugins/Automod/actions/reply.ts | 9 +++++++-- .../Automod/functions/resolveActionContactMethods.ts | 4 ++-- backend/src/utils.ts | 4 ++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 33043b24..f5a89f0a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,4 +1,4 @@ -import { Client, Intents, TextChannel } from "discord.js"; +import { Client, Intents, TextChannel, ThreadChannel } from "discord.js"; import yaml from "js-yaml"; import { Knub, PluginError } from "knub"; import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; @@ -239,13 +239,15 @@ connect().then(async () => { }, sendSuccessMessageFn(channel, body) { - const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; + const guildId = + channel instanceof TextChannel || channel instanceof ThreadChannel ? channel.guild.id : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.success_emoji : undefined; channel.send(successMessage(body, emoji)); }, sendErrorMessageFn(channel, body) { - const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; + const guildId = + channel instanceof TextChannel || channel instanceof ThreadChannel ? channel.guild.id : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.error_emoji : undefined; channel.send(errorMessage(body, emoji)); }, diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index d26a9777..6c63e5ce 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -1,4 +1,4 @@ -import { Snowflake, TextChannel } from "discord.js"; +import { Snowflake, TextChannel, ThreadChannel } from "discord.js"; import * as t from "io-ts"; import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions"; import { LogType } from "../../../data/LogType"; @@ -34,7 +34,7 @@ export const AlertAction = automodAction({ const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake); const logs = pluginData.getPlugin(LogsPlugin); - if (channel && channel instanceof TextChannel) { + if (channel && (channel instanceof TextChannel || channel instanceof ThreadChannel)) { const text = actionConfig.text; const theMessageLink = contexts[0].message && messageLink(pluginData.guild.id, contexts[0].message.channel_id, contexts[0].message.id); diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index c3dcded6..9c811b1d 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -1,4 +1,4 @@ -import { MessageOptions, Permissions, Snowflake, TextChannel, User } from "discord.js"; +import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js"; import * as t from "io-ts"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { LogType } from "../../../data/LogType"; @@ -32,7 +32,10 @@ export const ReplyAction = automodAction({ async apply({ pluginData, contexts, actionConfig, ruleName }) { const contextsWithTextChannels = contexts .filter(c => c.message?.channel_id) - .filter(c => pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake) instanceof TextChannel); + .filter(c => { + const channel = pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake); + return channel instanceof TextChannel || channel instanceof ThreadChannel; + }); const contextsByChannelId = contextsWithTextChannels.reduce((map: Map, context) => { if (!map.has(context.message!.channel_id)) { @@ -59,6 +62,7 @@ export const ReplyAction = automodAction({ ? await renderReplyText(actionConfig) : ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageOptions); + console.log("formatted:", formatted); if (formatted) { const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel; @@ -89,6 +93,7 @@ export const ReplyAction = automodAction({ } const messageContent: MessageOptions = typeof formatted === "string" ? { content: formatted } : formatted; + console.log(`sending reply message to ${channel.id}`); const replyMsg = await channel.send({ ...messageContent, allowedMentions: { diff --git a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts index 8dca9ee4..5ac49703 100644 --- a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts +++ b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts @@ -1,4 +1,4 @@ -import { Snowflake, TextChannel } from "discord.js"; +import { Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; @@ -19,7 +19,7 @@ export function resolveActionContactMethods( } const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake); - if (!(channel instanceof TextChannel)) { + if (!(channel instanceof TextChannel || channel instanceof ThreadChannel)) { throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL); } diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 54f16c46..19fc690c 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -871,7 +871,7 @@ export function chunkMessageLines(str: string, maxChunkLength = 1990): string[] } export async function createChunkedMessage( - channel: TextChannel | User, + channel: TextChannel | ThreadChannel | User, messageText: string, allowedMentions?: MessageMentionOptions, ) { @@ -1329,7 +1329,7 @@ export function messageSummary(msg: SavedMessage) { if (richEmbed) result += "Embed:```" + Util.escapeCodeBlock(JSON.stringify(richEmbed)) + "```"; // Attachments - if (msg.data.attachments) { + if (msg.data.attachments && msg.data.attachments.length) { result += "Attachments:\n" + msg.data.attachments.map((a: ISavedMessageAttachmentData) => disableLinkPreviews(a.url)).join("\n") + From a179119f2e6e876c570de58af400a600fb5d0de6 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:55:42 +0300 Subject: [PATCH 017/191] Disable/hide some interaction-based features until after release --- backend/src/plugins/ContextMenus/ContextMenuPlugin.ts | 4 ++-- backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index 8cffe3d1..4443bf37 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -33,8 +33,10 @@ const defaultOptions: PluginOptions = { export const ContextMenuPlugin = zeppelinGuildPlugin()({ name: "context_menu", + showInDocs: false, configSchema: ConfigSchema, + dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin], defaultOptions, // prettier-ignore @@ -51,6 +53,4 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { loadAllCommands(pluginData); }, - - dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin], }); diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index 752c4c36..5690cd07 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -117,13 +117,13 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin( RefreshReactionRolesCmd, ClearReactionRolesCmd, InitReactionRolesCmd, - PostButtonRolesCmd, + // PostButtonRolesCmd, ], // prettier-ignore events: [ AddReactionRoleEvt, - ButtonInteractionEvt, + // ButtonInteractionEvt, MessageDeletedEvt, ], configPreprocessor, From d6c4868ccf46c5217954c2b55ef0c9e1fec0c1d9 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 21:01:57 +0300 Subject: [PATCH 018/191] Make sure waitForButtonConfirm() component custom IDs are unique --- backend/src/utils/waitForInteraction.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index afc3641f..e7e4ae7d 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -1,6 +1,7 @@ import { MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions, TextChannel } from "discord.js"; import { noop } from "knub/dist/utils"; import moment from "moment"; +import uuidv4 from "uuid/v4"; export async function waitForButtonConfirm( channel: TextChannel, @@ -13,12 +14,12 @@ export async function waitForButtonConfirm( new MessageButton() .setStyle("SUCCESS") .setLabel(options?.confirmText || "Confirm") - .setCustomId(`confirmButton:${idMod}`), + .setCustomId(`confirmButton:${idMod}:${uuidv4()}`), new MessageButton() .setStyle("DANGER") .setLabel(options?.cancelText || "Cancel") - .setCustomId(`cancelButton:${idMod}`), + .setCustomId(`cancelButton:${idMod}:${uuidv4()}`), ]); const message = await channel.send({ ...toPost, components: [row] }); @@ -28,10 +29,10 @@ export async function waitForButtonConfirm( if (options?.restrictToId && options.restrictToId !== interaction.user.id) { interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true }); } else { - if (interaction.customId === `confirmButton:${idMod}`) { + if (interaction.customId.startsWith(`confirmButton:${idMod}:`)) { message.delete(); resolve(true); - } else if (interaction.customId === `cancelButton:${idMod}`) { + } else if (interaction.customId.startsWith(`cancelButton:${idMod}:`)) { message.delete(); resolve(false); } From cb1ba4d878349cab3faacaa83f883ef9330da3aa Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 21:02:53 +0300 Subject: [PATCH 019/191] Run format --- backend/src/plugins/Utility/commands/JumboCmd.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index b688b686..c35e1367 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -15,16 +15,13 @@ async function getBufferFromUrl(url: string): Promise { } function bufferToPhotonImage(input: Buffer): photon.PhotonImage { - const base64 = input - .toString("base64") - .replace(/^data:image\/\w+;base64,/, ""); + const base64 = input.toString("base64").replace(/^data:image\/\w+;base64,/, ""); return photon.PhotonImage.new_from_base64(base64); } function photonImageToBuffer(image: photon.PhotonImage): Buffer { - const base64 = image.get_base64() - .replace(/^data:image\/\w+;base64,/, ""); + const base64 = image.get_base64().replace(/^data:image\/\w+;base64,/, ""); return Buffer.from(base64, "base64"); } From 8ffafe1214ead8b874eee5182d0dc4b5d007faff Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 21:08:30 +0300 Subject: [PATCH 020/191] You saw nothing --- backend/src/plugins/Automod/actions/reply.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index 9c811b1d..139308ba 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -62,7 +62,6 @@ export const ReplyAction = automodAction({ ? await renderReplyText(actionConfig) : ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageOptions); - console.log("formatted:", formatted); if (formatted) { const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel; @@ -93,7 +92,6 @@ export const ReplyAction = automodAction({ } const messageContent: MessageOptions = typeof formatted === "string" ? { content: formatted } : formatted; - console.log(`sending reply message to ${channel.id}`); const replyMsg = await channel.send({ ...messageContent, allowedMentions: { From 4c8523adf3e5e04ab77492354d81dd05d537b6fe Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:04:32 +0300 Subject: [PATCH 021/191] Hotfix --- .../src/plugins/Logs/events/LogsUserUpdateEvts.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index 640a62ca..088d3588 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -62,12 +62,10 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ member, addedRoles: addedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), + .map(r => r.name), removedRoles: removedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), + .map(r => r.name), mod, }); } else if (addedRoles.length) { @@ -76,8 +74,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ member, roles: addedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), + .map(r => r.name), mod, }); } else if (removedRoles.length && !addedRoles.length) { @@ -86,8 +83,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ member, roles: removedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name) - .join(", "), + .map(r => r.name), mod, }); } From 78c2fdf4f0612697e551ac7de7147767b988ab11 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:06:57 +0300 Subject: [PATCH 022/191] Hotfix 2 --- backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts index bdb4b1a4..0abfadc8 100644 --- a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts +++ b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts @@ -11,7 +11,11 @@ export const LogsVoiceStateUpdateEvt = logsEvt({ async listener(meta) { const oldChannel = meta.args.oldState.channel; const newChannel = meta.args.newState.channel; - const member = meta.args.newState.member ?? meta.args.oldState.member!; + const member = meta.args.newState.member ?? meta.args.oldState.member; + + if (!member) { + return; + } if (!newChannel && oldChannel) { // Leave evt From 1df2e18fef6eecac9d1c144f0fb4caebc68137c3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:15:56 +0300 Subject: [PATCH 023/191] Hotfix 3 --- backend/src/utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 19fc690c..5879cd1a 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -519,6 +519,9 @@ export async function findRelevantAuditLogEntry( return null; } + // FIXME: Temp hotfix + return null; + let auditLogs: GuildAuditLogs | null = null; try { auditLogs = await guild.fetchAuditLogs({ limit: 5, type: actionType }); From 20fe4346763f3d20392a578a7d76195884e25d6e Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:20:16 +0300 Subject: [PATCH 024/191] Hotfix 4 --- backend/src/plugins/LocateUser/events/SendAlertsEvts.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts index 552fb8ef..eaedc015 100644 --- a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts +++ b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts @@ -6,7 +6,10 @@ export const VoiceStateUpdateAlertEvt = locateUserEvt({ event: "voiceStateUpdate", async listener(meta) { - const memberId = meta.args.oldState.member ? meta.args.oldState.member.id : meta.args.newState.member!.id; + const memberId = meta.args.oldState.member?.id ?? meta.args.newState.member?.id; + if (!memberId) { + return; + } if (meta.args.newState.channel != null) { if (meta.pluginData.state.usersWithAlerts.includes(memberId)) { From 7870b3257b0077db0819d634428e9f9f3ff3943a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:24:23 +0300 Subject: [PATCH 025/191] Hotfix 5 --- backend/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index f5a89f0a..6b0175c0 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -94,6 +94,12 @@ function errorHandler(err) { return; } + // FIXME: Hotfix + if (err.message && err.message.startsWith("Unknown custom override criteria")) { + console.warn(err.message); + return; + } + // tslint:disable:no-console console.error(err); From e3c9dbf85130a64c40fb025d23988723f844851e Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:29:46 +0300 Subject: [PATCH 026/191] Hotfix 6 --- backend/src/plugins/Automod/triggers/matchAttachmentType.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts index ba8e4e59..4ae2b65e 100644 --- a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts +++ b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts @@ -32,6 +32,12 @@ export const MatchAttachmentTypeTrigger = automodTrigger()({ const attachments: any[] = context.message.data.attachments; for (const attachment of attachments) { + // FIXME: Hotfix + if (!attachment.filename) { + console.warn("No attachment filename:", attachment); + continue; + } + const attachmentType = attachment.filename .split(".") .pop() From b405b14e364c1ebf433f5469f091e65f109761ea Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:33:13 +0300 Subject: [PATCH 027/191] Hotfix 7 --- backend/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 6b0175c0..2aff876b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -144,8 +144,8 @@ logger.info("Connecting to database"); connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - restTimeOffset: 150, - restGlobalRateLimit: 50, + // restTimeOffset: 150, + // restGlobalRateLimit: 50, // Disable mentions by default allowedMentions: { parse: [], From c589ed138fa08db870fc7fb101c20b8c49fffdad Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:35:51 +0300 Subject: [PATCH 028/191] Hotfix 8 --- backend/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index 2aff876b..ece94568 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -100,6 +100,12 @@ function errorHandler(err) { return; } + // FIXME: Hotfix + if (err.message && err.message.startsWith("Unknown override criteria")) { + console.warn(err.message); + return; + } + // tslint:disable:no-console console.error(err); From bc1c520521aa95d487aa81804374f56d320bb7a1 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:39:16 +0300 Subject: [PATCH 029/191] Hotfix 9 --- backend/src/utils/templateSafeObjects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts index 7f46517f..14547ff8 100644 --- a/backend/src/utils/templateSafeObjects.ts +++ b/backend/src/utils/templateSafeObjects.ts @@ -239,7 +239,7 @@ export function userToTemplateSafeUser(user: User | UnknownUser): TemplateSafeUs discriminator: user.discriminator, mention: `<@${user.id}>`, tag: user.tag, - avatarURL: user.displayAvatarURL({ dynamic: true }), + avatarURL: user.displayAvatarURL?.({ dynamic: true }), bot: user.bot, createdAt: user.createdTimestamp, }); From 870b6382253576029d25544efbdaa84080a7a924 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:44:03 +0300 Subject: [PATCH 030/191] Hotfix 10 --- backend/src/plugins/Tags/util/renderTagBody.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 725275f1..9e5fede7 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; -import { renderTemplate } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderRecursively, StrictMessageContent } from "../../../utils"; import { Tag, TagsPluginType } from "../types"; import { findTagByName } from "./findTagByName"; @@ -9,7 +9,7 @@ import { findTagByName } from "./findTagByName"; export async function renderTagBody( pluginData: GuildPluginData, body: t.TypeOf, - args: any[] = [], + args: unknown[] = [], extraData = {}, subTagPermissionMatchParams?: ExtendedMatchParams, ): Promise { @@ -17,7 +17,7 @@ export async function renderTagBody( const maxTagFnCalls = 25; let tagFnCalls = 0; - const data = { + const data = new TemplateSafeValueContainer({ args, ...extraData, ...pluginData.state.tagFunctions, @@ -51,7 +51,7 @@ export async function renderTagBody( const rendered = await renderTagBody(pluginData, subTagBody, subTagArgs, subTagPermissionMatchParams); return rendered.content!; }, - }; + }); if (typeof body === "string") { // Plain text tag From f208dc3013d477c4214116ecbe8eed95fe99f9be Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:44:54 +0300 Subject: [PATCH 031/191] matchAttachmentType fix --- .../Automod/triggers/matchAttachmentType.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts index 4ae2b65e..0eb7e764 100644 --- a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts +++ b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts @@ -28,19 +28,14 @@ export const MatchAttachmentTypeTrigger = automodTrigger()({ return; } - if (!context.message.data.attachments) return null; - const attachments: any[] = context.message.data.attachments; + if (!context.message.data.attachments) { + return null; + } - for (const attachment of attachments) { - // FIXME: Hotfix - if (!attachment.filename) { - console.warn("No attachment filename:", attachment); - continue; - } - - const attachmentType = attachment.filename + for (const attachment of context.message.data.attachments) { + const attachmentType = attachment.url .split(".") - .pop() + .pop()! .toLowerCase(); const blacklist = trigger.blacklist_enabled From 929716a958f5d0ec82c44f9d1dd9bb3a8ef7591c Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 22:50:31 +0300 Subject: [PATCH 032/191] Hotfix 11 --- backend/src/data/GuildSavedMessages.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index efaa08c0..37a38791 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -238,7 +238,14 @@ export class GuildSavedMessages extends BaseGuildRepository { posted_at: postedAt, }; - return this.create({ ...data, ...overrides }); + return this.create({ ...data, ...overrides }).catch(err => { + if (err?.code === "ER_DUP_ENTRY") { + console.warn(`Tried to insert duplicate message ID: ${msg.id}`); + return; + } + + throw err; + }); } async createFromMessages(messages: Message[], overrides = {}) { From ad800e335ca1e608f6db0b7f2673db3b8fe1fc0b Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:02:56 +0300 Subject: [PATCH 033/191] Improve companion channel error handling --- .../functions/handleCompanionPermissions.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts index 6ee14cf6..d6e3e764 100644 --- a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -68,13 +68,24 @@ export async function handleCompanionPermissions( }); } } catch (e) { - if (isDiscordAPIError(e) && e.code === 50001) { + if (isDiscordAPIError(e)) { const logs = pluginData.getPlugin(LogsPlugin); - 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); - return; + + if (e.code === 50001) { + logs.logBotAlert({ + body: `One of the companion channels can't be accessed. Pausing companion channels for 5 minutes or until the bot is reloaded on this server.`, + }); + pluginData.state.errorCooldownManager.setCooldown(ERROR_COOLDOWN_KEY, ERROR_COOLDOWN); + return; + } + + if (e.code === 50013) { + 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); + return; + } } throw e; From 9713e2c99bf94abd3614a2581214827683ada9cf Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:03:19 +0300 Subject: [PATCH 034/191] Code clean-up --- backend/src/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 5879cd1a..a1ce91a2 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -1,6 +1,7 @@ import { Client, Constants, + DiscordAPIError, Emoji, Guild, GuildAuditLogs, @@ -84,8 +85,8 @@ export function isDiscordHTTPError(err: Error | string) { return typeof err === "object" && err.constructor?.name === DISCORD_HTTP_ERROR_NAME; } -export function isDiscordAPIError(err: Error | string) { - return typeof err === "object" && err.constructor?.name === DISCORD_REST_ERROR_NAME; +export function isDiscordAPIError(err: Error | string): err is DiscordAPIError { + return err instanceof DiscordAPIError; } export function tNullable>(type: T) { From b06e51f25c830409837e2b199734f27201b00fb8 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:03:28 +0300 Subject: [PATCH 035/191] Hotfix 12 --- backend/src/data/GuildSavedMessages.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index 37a38791..c41a255d 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -225,6 +225,11 @@ export class GuildSavedMessages extends BaseGuildRepository { const existingSavedMsg = await this.find(msg.id); if (existingSavedMsg) return; + // FIXME: Hotfix + if (!msg.channel) { + return; + } + const savedMessageData = this.msgToSavedMessageData(msg); const postedAt = moment.utc(msg.createdTimestamp, "x").format("YYYY-MM-DD HH:mm:ss"); From ef118437f55e61b6e53bf793bc0946bd97909c19 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:20:31 +0300 Subject: [PATCH 036/191] Fix 429/rate limit logging --- backend/src/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index ece94568..2e78e893 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,4 +1,4 @@ -import { Client, Intents, TextChannel, ThreadChannel } from "discord.js"; +import { Client, Constants, Intents, TextChannel, ThreadChannel } from "discord.js"; import yaml from "js-yaml"; import { Knub, PluginError } from "knub"; import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; @@ -178,8 +178,13 @@ connect().then(async () => { }); client.setMaxListeners(200); - client.on("rateLimit", rateLimitData => { - logger.info(`[429] ${JSON.stringify(rateLimitData)}`); + client.on(Constants.Events.DEBUG, errorText => { + if (!errorText.indexOf("429")) { + return; + } + + // tslint:disable-next-line:no-console + console.warn(errorText); }); client.on("error", err => { From 30f184897f96dcfd9022e8e21a10b3052a2401f2 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:21:00 +0300 Subject: [PATCH 037/191] Re-enable audit log checks --- backend/src/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index a1ce91a2..7be56779 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -520,9 +520,6 @@ export async function findRelevantAuditLogEntry( return null; } - // FIXME: Temp hotfix - return null; - let auditLogs: GuildAuditLogs | null = null; try { auditLogs = await guild.fetchAuditLogs({ limit: 5, type: actionType }); From 51d2f61f25a44432d708e74207d7d099a367741d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:22:34 +0300 Subject: [PATCH 038/191] Hotfix 13 --- .../CompanionChannels/events/VoiceStateUpdateEvt.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/CompanionChannels/events/VoiceStateUpdateEvt.ts b/backend/src/plugins/CompanionChannels/events/VoiceStateUpdateEvt.ts index 07251fdb..0e73570e 100644 --- a/backend/src/plugins/CompanionChannels/events/VoiceStateUpdateEvt.ts +++ b/backend/src/plugins/CompanionChannels/events/VoiceStateUpdateEvt.ts @@ -6,7 +6,12 @@ export const VoiceStateUpdateEvt = companionChannelsEvt({ listener({ pluginData, args: { oldState, newState } }) { const oldChannel = oldState.channel; const newChannel = newState.channel; - const memberId = newState.member ? newState.member.id : oldState.member!.id; + + const memberId = newState.member?.id ?? oldState.member?.id; + if (!memberId) { + return; + } + handleCompanionPermissions(pluginData, memberId, newChannel, oldChannel); }, }); From 101367a10345099ae0f95e80a0cc0fced3a05c50 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:25:26 +0300 Subject: [PATCH 039/191] Remove audit log checks from member role changes --- .../src/plugins/Logs/events/LogsUserUpdateEvts.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index 088d3588..eee51e73 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -49,13 +49,6 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ } if (!skip) { - const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( - pluginData, - GuildAuditLogs.Actions.MEMBER_ROLE_UPDATE as number, - member.id, - ); - const mod = relevantAuditLogEntry?.executor ?? null; - if (addedRoles.length && removedRoles.length) { // Roles added *and* removed logMemberRoleChanges(pluginData, { @@ -66,7 +59,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ removedRoles: removedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name), - mod, + mod: null, }); } else if (addedRoles.length) { // Roles added @@ -75,7 +68,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ roles: addedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name), - mod, + mod: null, }); } else if (removedRoles.length && !addedRoles.length) { // Roles removed @@ -84,7 +77,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ roles: removedRoles .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name), - mod, + mod: null, }); } } From 3c96cad8c60d5517595fa8478c2fe43e6caea263 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:57:44 +0300 Subject: [PATCH 040/191] Add bail-out after too many 429 errors --- backend/src/index.ts | 13 ++++++++++++- backend/src/utils/DecayingCounter.ts | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 backend/src/utils/DecayingCounter.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 2e78e893..71eda5a8 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -18,8 +18,9 @@ import { RecoverablePluginError } from "./RecoverablePluginError"; import { SimpleError } from "./SimpleError"; import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types"; import { startUptimeCounter } from "./uptime"; -import { errorMessage, isDiscordAPIError, isDiscordHTTPError, successMessage } from "./utils"; +import { errorMessage, isDiscordAPIError, isDiscordHTTPError, SECONDS, successMessage } from "./utils"; import { loadYamlSafely } from "./utils/loadYamlSafely"; +import { DecayingCounter } from "./utils/DecayingCounter"; if (!process.env.KEY) { // tslint:disable-next-line:no-console @@ -178,6 +179,9 @@ connect().then(async () => { }); client.setMaxListeners(200); + const safe429DecayInterval = 5 * SECONDS; + const safe429MaxCount = 5; + const safe429Counter = new DecayingCounter(safe429DecayInterval); client.on(Constants.Events.DEBUG, errorText => { if (!errorText.indexOf("429")) { return; @@ -185,6 +189,13 @@ connect().then(async () => { // tslint:disable-next-line:no-console console.warn(errorText); + + const value = safe429Counter.add(1); + if (value > safe429MaxCount) { + // tslint:disable-next-line:no-console + console.error(`Too many 429s (over ${safe429MaxCount} in ${safe429MaxCount * safe429DecayInterval}ms), exiting`); + process.exit(1); + } }); client.on("error", err => { diff --git a/backend/src/utils/DecayingCounter.ts b/backend/src/utils/DecayingCounter.ts new file mode 100644 index 00000000..baa59ae0 --- /dev/null +++ b/backend/src/utils/DecayingCounter.ts @@ -0,0 +1,21 @@ +/** + * This is not related to Zeppelin's counters feature + */ +export class DecayingCounter { + protected value = 0; + + constructor(protected decayInterval: number) { + setInterval(() => { + this.value = Math.max(0, this.value - 1); + }, decayInterval); + } + + add(count = 1): number { + this.value += count; + return this.value; + } + + get(): number { + return this.value; + } +} From d9d864698eb09ba8616450f29647d254cb60c75a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 18 Aug 2021 23:58:25 +0300 Subject: [PATCH 041/191] Re-enable restGlobalRateLimit: 50 --- backend/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 71eda5a8..081f68a7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -151,8 +151,9 @@ logger.info("Connecting to database"); connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - // restTimeOffset: 150, - // restGlobalRateLimit: 50, + + restGlobalRateLimit: 50, + // Disable mentions by default allowedMentions: { parse: [], From f07ed5721022d72a39c5fb626cb2241f8453d42a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:00:16 +0300 Subject: [PATCH 042/191] Add more debug logging --- backend/src/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 081f68a7..128c82f7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -180,16 +180,23 @@ connect().then(async () => { }); client.setMaxListeners(200); + client.on(Constants.Events.RATE_LIMIT, data => { + // tslint:disable-next-line:no-console + console.log(`[DEBUG] [RATE_LIMIT] ${JSON.stringify(data)}`); + }); + const safe429DecayInterval = 5 * SECONDS; const safe429MaxCount = 5; const safe429Counter = new DecayingCounter(safe429DecayInterval); client.on(Constants.Events.DEBUG, errorText => { if (!errorText.indexOf("429")) { + // tslint:disable-next-line:no-console + console.debug(`[DEBUG] ${errorText}`); return; } // tslint:disable-next-line:no-console - console.warn(errorText); + console.warn(`[DEBUG] [WARN] [429] ${errorText}`); const value = safe429Counter.add(1); if (value > safe429MaxCount) { From 499511f79dc81c15516cdda63cfa690815e1a765 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:01:29 +0300 Subject: [PATCH 043/191] Fix 429 string check --- backend/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 128c82f7..fdd7986c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -189,7 +189,7 @@ connect().then(async () => { const safe429MaxCount = 5; const safe429Counter = new DecayingCounter(safe429DecayInterval); client.on(Constants.Events.DEBUG, errorText => { - if (!errorText.indexOf("429")) { + if (!errorText.includes("429")) { // tslint:disable-next-line:no-console console.debug(`[DEBUG] ${errorText}`); return; From 8da47e53e6a2c67a56c8505ee090fb0a67d727ed Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:49:06 +0300 Subject: [PATCH 044/191] Ugly workaround/hotfix for message fetching --- backend/src/Queue.ts | 8 ++++---- backend/src/plugins/MessageSaver/saveMessagesToDB.ts | 3 ++- .../ReactionRoles/commands/ClearReactionRolesCmd.ts | 3 ++- .../ReactionRoles/commands/InitReactionRolesCmd.ts | 3 ++- .../util/applyReactionRoleReactionsToMessage.ts | 3 ++- backend/src/plugins/Slowmode/util/onMessageCreate.ts | 3 ++- .../Starboard/events/StarboardReactionAddEvt.ts | 7 +++---- backend/src/plugins/Utility/commands/SourceCmd.ts | 3 ++- backend/src/utils/hotfixMessageFetch.ts | 12 ++++++++++++ 9 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 backend/src/utils/hotfixMessageFetch.ts diff --git a/backend/src/Queue.ts b/backend/src/Queue.ts index b8445f12..ddedc56a 100644 --- a/backend/src/Queue.ts +++ b/backend/src/Queue.ts @@ -28,11 +28,11 @@ export class Queue { return this.queue.length + (this.running ? 1 : 0); } - public add(fn: TQueueFunction): Promise { - const promise = new Promise(resolve => { + public add(fn: TQueueFunction): Promise { + const promise = new Promise(resolve => { this.queue.push(async () => { - await fn(); - resolve(); + const result = await fn(); + resolve(result); }); if (!this.running) this.next(); diff --git a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts index db7e9b9d..da62188d 100644 --- a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts @@ -1,6 +1,7 @@ import { Message, Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { MessageSaverPluginType } from "./types"; +import { hotfixMessageFetch } from "../../utils/hotfixMessageFetch"; export async function saveMessagesToDB( pluginData: GuildPluginData, @@ -15,7 +16,7 @@ export async function saveMessagesToDB( let thisMsg: Message; try { - thisMsg = await channel.messages.fetch(id as Snowflake); + thisMsg = await hotfixMessageFetch(channel, id as Snowflake); if (!thisMsg) { failed.push(id); diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index 229b8fbe..35ddfed7 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -3,6 +3,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isDiscordAPIError } from "../../../utils"; import { reactionRolesCmd } from "../types"; +import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; export const ClearReactionRolesCmd = reactionRolesCmd({ trigger: "reaction_roles clear", @@ -23,7 +24,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ let targetMessage: Message; try { - targetMessage = await args.message.channel.messages.fetch(args.message.messageId as Snowflake); + targetMessage = await hotfixMessageFetch(args.message.channel, args.message.messageId); } catch (err) { if (isDiscordAPIError(err) && err.code === 50001) { sendErrorMessage(pluginData, msg.channel, "Missing access to the specified message"); diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index dba3db59..e3a126fe 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -5,6 +5,7 @@ import { canUseEmoji, isDiscordAPIError, isValidEmoji, noop, trimPluginDescripti import { canReadChannel } from "../../../utils/canReadChannel"; import { reactionRolesCmd, TReactionRolePair } from "../types"; import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; +import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; const CLEAR_ROLES_EMOJI = "❌"; @@ -40,7 +41,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ let targetMessage; try { - targetMessage = await args.message.channel.messages.fetch(args.message.messageId as Snowflake).catch(noop); + targetMessage = await hotfixMessageFetch(args.message.channel, args.message.messageId); } catch (e) { if (isDiscordAPIError(e)) { sendErrorMessage(pluginData, msg.channel, `Error ${e.code} while getting message: ${e.message}`); diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 327ac207..1ed9c17b 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -5,6 +5,7 @@ import { LogType } from "../../../data/LogType"; import { isDiscordAPIError, sleep } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ReactionRolesPluginType } from "../types"; +import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; const CLEAR_ROLES_EMOJI = "❌"; @@ -25,7 +26,7 @@ export async function applyReactionRoleReactionsToMessage( let targetMessage; try { - targetMessage = await channel.messages.fetch(messageId as Snowflake); + targetMessage = await hotfixMessageFetch(channel, messageId as Snowflake); } catch (e) { if (isDiscordAPIError(e)) { if (e.code === 10008) { diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 78f8c92b..2ad8968f 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -11,6 +11,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { BOT_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; import { SlowmodePluginType } from "../types"; import { applyBotSlowmodeToUserId } from "./applyBotSlowmodeToUserId"; +import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; @@ -49,7 +50,7 @@ export async function onMessageCreate(pluginData: GuildPluginData null); + const message = await hotfixMessageFetch(args.message.channel, args.message.messageId); if (!message) { sendErrorMessage(pluginData, cmdMessage.channel, "Unknown message"); return; diff --git a/backend/src/utils/hotfixMessageFetch.ts b/backend/src/utils/hotfixMessageFetch.ts new file mode 100644 index 00000000..b0859fd6 --- /dev/null +++ b/backend/src/utils/hotfixMessageFetch.ts @@ -0,0 +1,12 @@ +import { GuildChannel, Message, TextChannel, ThreadChannel } from "discord.js"; +import { Queue } from "../Queue"; +import { sleep } from "../utils"; + +const queue = new Queue(); + +export function hotfixMessageFetch(channel: TextChannel | ThreadChannel, messageId: string): Promise { + return queue.add(async () => { + await sleep(3000); + return channel.messages.fetch(messageId); + }); +} From 9244ee54a70c0ca957f8a5a6b8cff7d3b9f22356 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:55:45 +0300 Subject: [PATCH 045/191] Hotfix 14 --- backend/src/data/GuildCases.ts | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index 349b1ce1..8c09ecd8 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -1,4 +1,4 @@ -import { getRepository, In, Repository } from "typeorm"; +import { getRepository, In, InsertResult, Repository } from "typeorm"; import { BaseGuildRepository } from "./BaseGuildRepository"; import { CaseTypes } from "./CaseTypes"; import { connection } from "./db"; @@ -116,13 +116,30 @@ export class GuildCases extends BaseGuildRepository { ); } - async create(data): Promise { - const result = await this.cases.insert({ - ...data, - guild_id: this.guildId, - case_number: () => `(SELECT IFNULL(MAX(case_number)+1, 1) FROM cases AS ma2 WHERE guild_id = ${this.guildId})`, - }); + async createInternal(data): Promise { + return this.cases + .insert({ + ...data, + guild_id: this.guildId, + case_number: () => `(SELECT IFNULL(MAX(case_number)+1, 1) FROM cases AS ma2 WHERE guild_id = ${this.guildId})`, + }) + .catch(err => { + if (err?.code === "ER_DUP_ENTRY") { + if (data.audit_log_id) { + console.warn(`Tried to insert case with duplicate audit_log_id`); + return this.createInternal({ + ...data, + audit_log_id: undefined, + }); + } + } + throw err; + }); + } + + async create(data): Promise { + const result = await this.createInternal(data); return (await this.find(result.identifiers[0].id))!; } From 86ccae016b8f893aaacf53048aa6b5aefaf8b3c7 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:59:28 +0300 Subject: [PATCH 046/191] +debug --- backend/src/utils/hotfixMessageFetch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/utils/hotfixMessageFetch.ts b/backend/src/utils/hotfixMessageFetch.ts index b0859fd6..5810f6cc 100644 --- a/backend/src/utils/hotfixMessageFetch.ts +++ b/backend/src/utils/hotfixMessageFetch.ts @@ -7,6 +7,8 @@ const queue = new Queue(); export function hotfixMessageFetch(channel: TextChannel | ThreadChannel, messageId: string): Promise { return queue.add(async () => { await sleep(3000); + // tslint:disable-next-line:no-console + console.trace(`Fetching message id ${messageId} from channel ${channel.id}`); return channel.messages.fetch(messageId); }); } From cc9aaf235ea893ab5ff69e42d73aaaf7cdf644b3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:02:59 +0300 Subject: [PATCH 047/191] ++debug --- backend/src/utils/hotfixMessageFetch.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/utils/hotfixMessageFetch.ts b/backend/src/utils/hotfixMessageFetch.ts index 5810f6cc..5afea774 100644 --- a/backend/src/utils/hotfixMessageFetch.ts +++ b/backend/src/utils/hotfixMessageFetch.ts @@ -3,12 +3,17 @@ import { Queue } from "../Queue"; import { sleep } from "../utils"; const queue = new Queue(); +let n = 0; export function hotfixMessageFetch(channel: TextChannel | ThreadChannel, messageId: string): Promise { + const thisN = ++n; + + // tslint:disable-next-line:no-console + console.log(`[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id}`); return queue.add(async () => { await sleep(3000); // tslint:disable-next-line:no-console - console.trace(`Fetching message id ${messageId} from channel ${channel.id}`); + console.trace(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); return channel.messages.fetch(messageId); }); } From 6a94ec9b3e331a6789039cad292c7a829a6a594d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:04:41 +0300 Subject: [PATCH 048/191] +++debug --- backend/src/utils/hotfixMessageFetch.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/utils/hotfixMessageFetch.ts b/backend/src/utils/hotfixMessageFetch.ts index 5afea774..658c5aaf 100644 --- a/backend/src/utils/hotfixMessageFetch.ts +++ b/backend/src/utils/hotfixMessageFetch.ts @@ -9,11 +9,13 @@ export function hotfixMessageFetch(channel: TextChannel | ThreadChannel, message const thisN = ++n; // tslint:disable-next-line:no-console - console.log(`[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id}`); + console.trace( + `[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id} (queue size: ${queue.length})`, + ); return queue.add(async () => { await sleep(3000); // tslint:disable-next-line:no-console - console.trace(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); + console.log(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); return channel.messages.fetch(messageId); }); } From b9dca2cf2a3465dbef4f358a15cb58e3332058dc Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:22:42 +0300 Subject: [PATCH 049/191] Hotfix error in role restore --- backend/src/plugins/Persist/events/LoadDataEvt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Persist/events/LoadDataEvt.ts b/backend/src/plugins/Persist/events/LoadDataEvt.ts index 63569679..4d2becb4 100644 --- a/backend/src/plugins/Persist/events/LoadDataEvt.ts +++ b/backend/src/plugins/Persist/events/LoadDataEvt.ts @@ -61,7 +61,7 @@ export const LoadDataEvt = persistEvt({ if (rolesToRestore.length) { restoredData.push("roles"); - toRestore.roles = Array.from(new Set([...rolesToRestore, ...member.roles.cache])); + toRestore.roles = Array.from(new Set([...rolesToRestore, ...member.roles.cache.keys()])); } } From 7deb73791814e69cc2a0fc4cd157f731d3618865 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:24:01 +0300 Subject: [PATCH 050/191] -+debug --- backend/src/data/GuildSavedMessages.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index c41a255d..ea114665 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -212,8 +212,12 @@ export class GuildSavedMessages extends BaseGuildRepository { try { await this.messages.insert(data); } catch (e) { - console.warn(e); // tslint:disable-line - return; + if (e?.code === "ER_DUP_ENTRY") { + console.warn(`Tried to insert duplicate message ID: ${data.id}`); + return; + } + + throw e; } const inserted = await this.messages.findOne(data.id); @@ -243,14 +247,7 @@ export class GuildSavedMessages extends BaseGuildRepository { posted_at: postedAt, }; - return this.create({ ...data, ...overrides }).catch(err => { - if (err?.code === "ER_DUP_ENTRY") { - console.warn(`Tried to insert duplicate message ID: ${msg.id}`); - return; - } - - throw err; - }); + return this.create({ ...data, ...overrides }); } async createFromMessages(messages: Message[], overrides = {}) { From 7348262bdbf8aa980c2a70d47d77c792f869571f Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:28:37 +0300 Subject: [PATCH 051/191] Temporarily disable starboards --- .../src/plugins/Starboard/events/StarboardReactionAddEvt.ts | 3 +++ .../plugins/Starboard/events/StarboardReactionRemoveEvts.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index f8232346..f3d4f13c 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -10,6 +10,9 @@ export const StarboardReactionAddEvt = starboardEvt({ event: "messageReactionAdd", async listener(meta) { + // FIXME: Temporarily disabled + return; + const pluginData = meta.pluginData; let msg = meta.args.reaction.message as Message; diff --git a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts index 203c4bde..d2cc72da 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts @@ -5,6 +5,9 @@ export const StarboardReactionRemoveEvt = starboardEvt({ event: "messageReactionRemove", async listener(meta) { + // FIXME: Temporarily disabled + return; + const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteStarboardReaction( meta.args.reaction.message.id, @@ -18,6 +21,9 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({ event: "messageReactionRemoveAll", async listener(meta) { + // FIXME: Temporarily disabled + return; + const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id); boardLock.unlock(); From 83e49474298f2fbf880e3fb9a8e3a59d066aac95 Mon Sep 17 00:00:00 2001 From: Miikka <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:37:25 +0300 Subject: [PATCH 052/191] Desperate hotfix attempt --- backend/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index fdd7986c..9263279b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -152,7 +152,8 @@ connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - restGlobalRateLimit: 50, + restGlobalRateLimit: 5, + restTimeOffset: 1000, // Disable mentions by default allowedMentions: { From 8f790109df4d91f338ffa1f5650450fc92f10032 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:40:15 +0300 Subject: [PATCH 053/191] Hotfix 15 --- backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index f3d4f13c..049768cc 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -30,7 +30,7 @@ export const StarboardReactionAddEvt = starboardEvt({ } const member = await resolveMember(pluginData.client, pluginData.guild, userId); - if (!member || member.user.bot) return; + if (!member || member!.user.bot) return; const config = await pluginData.config.getMatchingConfig({ member, From 3ff559074499c4598d4f077214ffe68d1c8082c8 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:44:06 +0300 Subject: [PATCH 054/191] Tweak --- backend/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 9263279b..8dfbce57 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -152,7 +152,7 @@ connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - restGlobalRateLimit: 5, + restGlobalRateLimit: 10, restTimeOffset: 1000, // Disable mentions by default @@ -183,7 +183,7 @@ connect().then(async () => { client.on(Constants.Events.RATE_LIMIT, data => { // tslint:disable-next-line:no-console - console.log(`[DEBUG] [RATE_LIMIT] ${JSON.stringify(data)}`); + // console.log(`[DEBUG] [RATE_LIMIT] ${JSON.stringify(data)}`); }); const safe429DecayInterval = 5 * SECONDS; From f27a18853507d857c05479054baade8c1655d94b Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:52:24 +0300 Subject: [PATCH 055/191] +tweak --- backend/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 8dfbce57..cdfbda9a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -152,7 +152,7 @@ connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - restGlobalRateLimit: 10, + restGlobalRateLimit: 20, restTimeOffset: 1000, // Disable mentions by default From a7ec5c4808a33209f58bf2937d582e1d7e687954 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 01:59:07 +0300 Subject: [PATCH 056/191] ++tweak --- .../Logs/events/LogsChannelModifyEvts.ts | 4 ++++ .../plugins/Logs/events/LogsUserUpdateEvts.ts | 24 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts index dc3a5a8a..3fc69032 100644 --- a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts @@ -33,6 +33,10 @@ export const LogsChannelUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldChannel, meta.args.newChannel); const differenceString = differenceToString(diff); + if (differenceString.trim() === "") { + return; + } + logChannelUpdate(meta.pluginData, { oldChannel: meta.args.oldChannel, newChannel: meta.args.newChannel, diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index eee51e73..b2b64491 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -53,30 +53,30 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ // Roles added *and* removed logMemberRoleChanges(pluginData, { member, - addedRoles: addedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name), - removedRoles: removedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name), + addedRoles: addedRoles.map( + roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + ), + removedRoles: removedRoles.map( + roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + ), mod: null, }); } else if (addedRoles.length) { // Roles added logMemberRoleAdd(pluginData, { member, - roles: addedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name), + roles: addedRoles.map( + roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + ), mod: null, }); } else if (removedRoles.length && !addedRoles.length) { // Roles removed logMemberRoleRemove(pluginData, { member, - roles: removedRoles - .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) - .map(r => r.name), + roles: removedRoles.map( + roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + ), mod: null, }); } From b2dd432baf23c5b45bf9517a79703c13c3b49b7d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 02:02:03 +0300 Subject: [PATCH 057/191] Hotfix 16 --- .../plugins/Logs/logFunctions/logMemberRoleChanges.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts index bfc72ad0..b4364419 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts @@ -3,15 +3,15 @@ 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 { GuildMember, Role, 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; + addedRoles: Role[]; + removedRoles: Role[]; } export function logMemberRoleChanges(pluginData: GuildPluginData, data: LogMemberRoleChangesData) { @@ -21,8 +21,8 @@ export function logMemberRoleChanges(pluginData: GuildPluginData createTypedTemplateSafeValueContainer({ mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), - addedRoles: data.addedRoles, - removedRoles: data.removedRoles, + addedRoles: data.addedRoles.map(r => r.name).join(", "), + removedRoles: data.removedRoles.map(r => r.name).join(", "), }), { userId: data.member.id, From 2b80297ff2d1301f0d222c9551156a4571ee8457 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 02:17:16 +0300 Subject: [PATCH 058/191] Hotfix 17 --- backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index b2b64491..06b8a516 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -18,7 +18,9 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ const oldMember = meta.args.oldMember; const member = meta.args.newMember; - if (!oldMember) return; + if (!oldMember || oldMember.partial) { + return; + } if (member.nickname !== oldMember.nickname) { logMemberNickChange(pluginData, { From fd73da059ada4a53e42c2cea71bf4625fafae1fb Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 02:17:51 +0300 Subject: [PATCH 059/191] Hotfix 18 --- backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts index 3fc69032..f3a29c2c 100644 --- a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts @@ -30,6 +30,10 @@ export const LogsChannelUpdateEvt = logsEvt({ event: "channelUpdate", async listener(meta) { + if (meta.args.oldChannel?.partial) { + return; + } + const diff = getScalarDifference(meta.args.oldChannel, meta.args.newChannel); const differenceString = differenceToString(diff); From 11a82145b005771d6ed0c820444e2e82b85a1e75 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 02:20:13 +0300 Subject: [PATCH 060/191] Hotfix 19 --- .../src/plugins/MessageSaver/events/SaveMessagesEvts.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 97f4fe6d..c8464258 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -12,6 +12,11 @@ export const MessageCreateEvt = messageSaverEvt({ return; } + // Don't save partial messages + if (meta.args.message.partial) { + return; + } + await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, }); @@ -26,6 +31,10 @@ export const MessageUpdateEvt = messageSaverEvt({ return; } + if (meta.args.oldMessage?.partial) { + return; + } + await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); }, }); From 1e75b81a5e2aec37c6a5bf773245f55bdb4c01b6 Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Thu, 19 Aug 2021 17:25:19 +0200 Subject: [PATCH 061/191] Post: fix `cannot read pipe` error when no file is present on message (#249) --- backend/src/plugins/Post/util/postMessage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Post/util/postMessage.ts b/backend/src/plugins/Post/util/postMessage.ts index 9d4b67f4..5bb13095 100644 --- a/backend/src/plugins/Post/util/postMessage.ts +++ b/backend/src/plugins/Post/util/postMessage.ts @@ -30,6 +30,7 @@ export async function postMessage( name: attachments[0].name, file: await fsp.readFile(downloadedAttachment.path), }; + content.files = [file.file]; } if (enableMentions) { @@ -38,7 +39,7 @@ export async function postMessage( }; } - const createdMsg = await channel.send({ ...content, files: [file] }); + const createdMsg = await channel.send(content); pluginData.state.savedMessages.setPermanent(createdMsg.id); if (downloadedAttachment) { From 854ce1862df8fd682f6dd10540ecc5f43672fe96 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 18:34:24 +0300 Subject: [PATCH 062/191] Fix error in log userMention() --- backend/src/plugins/Logs/util/getLogMessage.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index f39b8ab4..2431128c 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -51,12 +51,16 @@ export async function getLogMessage( const values = new TemplateSafeValueContainer({ ...data, timestamp, - userMention: async ( - inputUserOrMember: TemplateSafeUser | TemplateSafeMember | TemplateSafeUser[] | TemplateSafeMember[], - ) => { - if (!inputUserOrMember) return ""; + userMention: async (inputUserOrMember: unknown) => { + if (!inputUserOrMember) { + return ""; + } - const usersOrMembers = Array.isArray(inputUserOrMember) ? inputUserOrMember : [inputUserOrMember]; + const inputArray = Array.isArray(inputUserOrMember) ? inputUserOrMember : [inputUserOrMember]; + // TODO: Resolve IDs to users/members + const usersOrMembers = inputArray.filter( + v => v instanceof TemplateSafeUser || v instanceof TemplateSafeMember, + ) as Array; const mentions: string[] = []; for (const userOrMember of usersOrMembers) { From e2be447de5fff9e979c8b27f4560097815409cab Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 18:34:52 +0300 Subject: [PATCH 063/191] Ignore empty emoji/sticker updates from logs --- .../plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts index 25a157a2..9832ab41 100644 --- a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts @@ -34,6 +34,10 @@ export const LogsEmojiUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldEmoji, meta.args.newEmoji); const differenceString = differenceToString(diff); + if (differenceString === "") { + return; + } + logEmojiUpdate(meta.pluginData, { oldEmoji: meta.args.oldEmoji, newEmoji: meta.args.newEmoji, @@ -69,6 +73,10 @@ export const LogsStickerUpdateEvt = logsEvt({ const diff = getScalarDifference(meta.args.oldSticker, meta.args.newSticker); const differenceString = differenceToString(diff); + if (differenceString === "") { + return; + } + logStickerUpdate(meta.pluginData, { oldSticker: meta.args.oldSticker, newSticker: meta.args.newSticker, From dc53de8ae3e6e41f23a83cfb2b59dfdd68757a83 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 18:59:11 +0300 Subject: [PATCH 064/191] Fix embed logs --- backend/src/plugins/Logs/util/getLogMessage.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index 2431128c..f44afaa3 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -133,8 +133,17 @@ export async function getLogMessage( if (timestamp) { formatted = `\`[${timestamp}]\` ${formatted}`; } - } else if (formatted != null && formatted.embed && includeEmbedTimestamp) { - formatted.embed.timestamp = isoTimestamp; + } else if (formatted != null) { + if (formatted.embed) { + formatted.embeds = [formatted.embed]; + delete formatted.embed; + } + + if (formatted.embeds && Array.isArray(formatted.embeds) && includeEmbedTimestamp) { + for (const embed of formatted.embeds) { + embed.timestamp = isoTimestamp; + } + } } return formatted; From 81514276e9b0cb296b64755863da79fb71d673e9 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:33:47 +0300 Subject: [PATCH 065/191] Fix issues around embeds --- .../src/plugins/Logs/util/getLogMessage.ts | 6 +- .../src/plugins/Post/commands/PostEmbedCmd.ts | 2 +- .../Post/commands/ScheduledPostsListCmd.ts | 5 +- .../src/plugins/Tags/commands/TagEvalCmd.ts | 2 +- backend/src/plugins/Tags/types.ts | 1 + .../src/plugins/Tags/util/renderTagBody.ts | 5 +- .../plugins/Tags/util/renderTagFromString.ts | 12 +-- backend/src/utils.ts | 99 ++++++++++++++++++- 8 files changed, 111 insertions(+), 21 deletions(-) diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index f44afaa3..81714486 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -13,6 +13,7 @@ import { messageSummary, renderRecursively, resolveMember, + validateAndParseMessageContent, verboseChannelMention, verboseUserMention, verboseUserName, @@ -134,10 +135,7 @@ export async function getLogMessage( formatted = `\`[${timestamp}]\` ${formatted}`; } } else if (formatted != null) { - if (formatted.embed) { - formatted.embeds = [formatted.embed]; - delete formatted.embed; - } + formatted = validateAndParseMessageContent(formatted); if (formatted.embeds && Array.isArray(formatted.embeds) && includeEmbedTimestamp) { for (const embed of formatted.embeds) { diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index 6db7d1cc..e822cd3b 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -82,6 +82,6 @@ export const PostEmbedCmd = postCmd({ ); } - actualPostCmd(pluginData, msg, args.channel, { embed }, args); + actualPostCmd(pluginData, msg, args.channel, { embeds: [embed] }, args); }, }); diff --git a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts index 7ac59698..17b1fc17 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts @@ -22,8 +22,7 @@ export const ScheduledPostsListCmd = postCmd({ let i = 1; const postLines = scheduledPosts.map(p => { - let previewText = - p.content.content || (p.content.embed && (p.content.embed.description || p.content.embed.title)) || ""; + let previewText = p.content.content || p.content.embeds?.[0]?.description || p.content.embeds?.[0]?.title || ""; const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH; @@ -37,7 +36,7 @@ export const ScheduledPostsListCmd = postCmd({ .format(timeAndDate.getDateFormat("pretty_datetime")); const parts = [`\`#${i++}\` \`[${prettyPostAt}]\` ${previewText}${isTruncated ? "..." : ""}`]; if (p.attachments.length) parts.push("*(with attachment)*"); - if (p.content.embed) parts.push("*(embed)*"); + if (p.content.embeds?.length) parts.push("*(embed)*"); if (p.repeat_until) { parts.push(`*(repeated every ${humanizeDuration(p.repeat_interval)} until ${p.repeat_until})*`); } diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index ee8c340f..bf9c3f1f 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -26,7 +26,7 @@ export const TagEvalCmd = tagsCmd({ { member: msg.member }, ); - if (!rendered.content && !rendered.embed) { + if (!rendered.content && !rendered.embeds?.length) { sendErrorMessage(pluginData, msg.channel, "Evaluation resulted in an empty text"); return; } diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 17ad0fc8..71247dec 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -7,6 +7,7 @@ import { GuildTags } from "../../data/GuildTags"; import { tEmbed, tNullable } from "../../utils"; export const Tag = t.union([t.string, tEmbed]); +export type TTag = t.TypeOf; export const TagCategory = t.type({ prefix: tNullable(t.string), diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 9e5fede7..cd8afcfe 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -1,14 +1,13 @@ -import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderRecursively, StrictMessageContent } from "../../../utils"; -import { Tag, TagsPluginType } from "../types"; +import { TagsPluginType, TTag } from "../types"; import { findTagByName } from "./findTagByName"; export async function renderTagBody( pluginData: GuildPluginData, - body: t.TypeOf, + body: TTag, args: unknown[] = [], extraData = {}, subTagPermissionMatchParams?: ExtendedMatchParams, diff --git a/backend/src/plugins/Tags/util/renderTagFromString.ts b/backend/src/plugins/Tags/util/renderTagFromString.ts index 3f5c31b5..4a22310c 100644 --- a/backend/src/plugins/Tags/util/renderTagFromString.ts +++ b/backend/src/plugins/Tags/util/renderTagFromString.ts @@ -1,13 +1,11 @@ import { GuildMember } from "discord.js"; -import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { parseArguments } from "knub-command-manager"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; -import { LogType } from "../../../data/LogType"; import { TemplateParseError } from "../../../templateFormatter"; -import { StrictMessageContent } from "../../../utils"; +import { StrictMessageContent, validateAndParseMessageContent } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { Tag, TagsPluginType } from "../types"; +import { TagsPluginType, TTag } from "../types"; import { renderTagBody } from "./renderTagBody"; export async function renderTagFromString( @@ -15,7 +13,7 @@ export async function renderTagFromString( str: string, prefix: string, tagName: string, - tagBody: t.TypeOf, + tagBody: TTag, member: GuildMember, ): Promise { const variableStr = str.slice(prefix.length + tagName.length).trim(); @@ -23,7 +21,7 @@ export async function renderTagFromString( // Format the string try { - return renderTagBody( + const rendered = await renderTagBody( pluginData, tagBody, tagArgs, @@ -33,6 +31,8 @@ export async function renderTagFromString( }, { member }, ); + + return validateAndParseMessageContent(rendered); } catch (e) { if (e instanceof TemplateParseError) { const logs = pluginData.getPlugin(LogsPlugin); diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 7be56779..57ab0888 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -44,6 +44,7 @@ import { ChannelTypeStrings } from "./types"; import { sendDM } from "./utils/sendDM"; import { waitForButtonConfirm } from "./utils/waitForInteraction"; import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; +import { z } from "zod"; const fsp = fs.promises; @@ -320,25 +321,117 @@ export const tEmbed = t.type({ ), }); +export const zEmbedInput = z.object({ + title: z.string().optional(), + description: z.string().optional(), + url: z.string().optional(), + timestamp: z.number().optional(), + color: z.number().optional(), + + footer: z.optional( + z.object({ + text: z.string(), + icon_url: z.string().optional(), + }), + ), + + image: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + thumbnail: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + video: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + provider: z.optional( + z.object({ + name: z.string(), + url: z.string().optional(), + }), + ), + + fields: z.optional( + z.array( + z.object({ + name: z.string().optional(), + value: z.string().optional(), + inline: z.boolean().optional(), + }), + ), + ), + + author: z + .optional( + z.object({ + name: z.string(), + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ) + .nullable(), +}); + export type EmbedWith = MessageEmbedOptions & Pick, T>; +export const zStrictMessageContent = z.object({ + content: z.string().optional(), + tts: z.boolean().optional(), + embeds: z.array(zEmbedInput).optional(), +}); + +export type ZStrictMessageContent = z.infer; + export type StrictMessageContent = { content?: string; tts?: boolean; - disableEveryone?: boolean; - embed?: MessageEmbedOptions; + embeds?: MessageEmbedOptions[]; }; export const tStrictMessageContent = t.type({ content: tNullable(t.string), tts: tNullable(t.boolean), disableEveryone: tNullable(t.boolean), - embed: tNullable(tEmbed), + embeds: tNullable(t.array(tEmbed)), }); export const tMessageContent = t.union([t.string, tStrictMessageContent]); +export function validateAndParseMessageContent(input: unknown): StrictMessageContent { + if (input == null) { + return {}; + } + + if (typeof input !== "object") { + return { content: String(input) }; + } + + // Migrate embed -> embeds + if ((input as any).embed) { + (input as any).embeds = [(input as any).embed]; + delete (input as any).embed; + } + + return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; +} + /** * Mirrors AllowedMentions from Eris */ From 31b9043b828eff5d5fd7b051a0f39b775a86edf3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:42:50 +0300 Subject: [PATCH 066/191] Fix user mention in unmute logs --- backend/src/plugins/Mutes/functions/clearExpiredMutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index 40f2fb9f..ea94b361 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -42,7 +42,7 @@ export async function clearExpiredMutes(pluginData: GuildPluginData Date: Thu, 19 Aug 2021 19:44:06 +0300 Subject: [PATCH 067/191] Reword default role addition/removal log messages --- backend/src/data/DefaultLogMessages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/data/DefaultLogMessages.json b/backend/src/data/DefaultLogMessages.json index 1adb760f..d50cfda6 100644 --- a/backend/src/data/DefaultLogMessages.json +++ b/backend/src/data/DefaultLogMessages.json @@ -13,9 +13,9 @@ "MEMBER_SOFTBAN": "🔨 {userMention(member)} was softbanned by {userMention(mod)}", "MEMBER_JOIN": "📥 {new} {userMention(member)} joined (created {account_age} ago)", "MEMBER_LEAVE": "📤 {userMention(member)} left the server", - "MEMBER_ROLE_ADD": "🔑 {userMention(member)}: role(s) **{roles}** added by {userMention(mod)}", - "MEMBER_ROLE_REMOVE": "🔑 {userMention(member)}: role(s) **{roles}** removed by {userMention(mod)}", - "MEMBER_ROLE_CHANGES": "🔑 {userMention(member)}: roles changed: added **{addedRoles}**, removed **{removedRoles}** by {userMention(mod)}", + "MEMBER_ROLE_ADD": "🔑 {userMention(member)} received roles: **{roles}**", + "MEMBER_ROLE_REMOVE": "🔑 {userMention(member)} lost roles: **{roles}**", + "MEMBER_ROLE_CHANGES": "🔑 {userMention(member)} had role changes: received **{addedRoles}**, lost **{removedRoles}**", "MEMBER_NICK_CHANGE": "✏ {userMention(member)}: nickname changed from **{oldNick}** to **{newNick}**", "MEMBER_USERNAME_CHANGE": "✏ {userMention(user)}: username changed from **{oldName}** to **{newName}**", "MEMBER_RESTORE": "💿 Restored {restoredData} for {userMention(member)} on rejoin", From f6feb75e296dcd1fb13bfde16b91c693864a6b59 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:49:44 +0300 Subject: [PATCH 068/191] +debug --- .../src/plugins/Mutes/functions/clearExpiredMutes.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index ea94b361..e4159f2e 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -9,6 +9,12 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function clearExpiredMutes(pluginData: GuildPluginData) { const expiredMutes = await pluginData.state.mutes.getExpiredMutes(); + if (pluginData.guild.id === "140933721929940992") { + // tslint:disable-next-line:no-console + console.log( + `Expired mutes (${expiredMutes.length}) for 140933721929940992: ${expiredMutes.map(m => m.user_id).join(", ")}`, + ); + } for (const mute of expiredMutes) { const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id); @@ -47,4 +53,8 @@ export async function clearExpiredMutes(pluginData: GuildPluginData Date: Thu, 19 Aug 2021 19:54:02 +0300 Subject: [PATCH 069/191] Automod reply action embed fix --- backend/src/plugins/Automod/actions/reply.ts | 4 +++- backend/src/utils.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index 139308ba..d40681e3 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -11,6 +11,7 @@ import { tMessageContent, tNullable, unique, + validateAndParseMessageContent, verboseChannelMention, } from "../../../utils"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; @@ -57,6 +58,7 @@ export const ReplyAction = automodAction({ user: userToTemplateSafeUser(user), }), ); + const formatted = typeof actionConfig === "string" ? await renderReplyText(actionConfig) @@ -91,7 +93,7 @@ export const ReplyAction = automodAction({ continue; } - const messageContent: MessageOptions = typeof formatted === "string" ? { content: formatted } : formatted; + const messageContent = validateAndParseMessageContent(formatted); const replyMsg = await channel.send({ ...messageContent, allowedMentions: { diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 57ab0888..1c919770 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -409,6 +409,7 @@ export const tStrictMessageContent = t.type({ content: tNullable(t.string), tts: tNullable(t.boolean), disableEveryone: tNullable(t.boolean), + embed: tNullable(tEmbed), embeds: tNullable(t.array(tEmbed)), }); From 5852adadad1d43cafb4f2016e9fa9d4c4f9c74b2 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:59:25 +0300 Subject: [PATCH 070/191] Hotfix 20 --- backend/src/utils.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 1c919770..b9bd8d92 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -430,9 +430,36 @@ export function validateAndParseMessageContent(input: unknown): StrictMessageCon delete (input as any).embed; } + dropNullValuesRecursively(input); + return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; } +function dropNullValuesRecursively(obj: any) { + if (obj == null) { + return; + } + + if (Array.isArray(obj)) { + for (const item of obj) { + dropNullValuesRecursively(item); + } + } + + if (typeof obj !== "object") { + return; + } + + for (const [key, value] of Object.entries(obj)) { + if (value == null) { + delete obj[key]; + continue; + } + + dropNullValuesRecursively(value); + } +} + /** * Mirrors AllowedMentions from Eris */ From 53fea6b88cdf7240a9bada48b5b218654f64c5f1 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 20:06:11 +0300 Subject: [PATCH 071/191] Fix !cleaning more than 100 messages --- backend/src/plugins/Utility/commands/CleanCmd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 0d42bf8c..185f2036 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -117,7 +117,7 @@ export async function cleanCmd(pluginData: GuildPluginData, a while (messagesToClean.length < args.count) { const potentialMessages = await targetChannel.messages.fetch({ before: beforeId, - limit: args.count, + limit: Math.min(args.count, 100), }); if (potentialMessages.size === 0) break; From 84ff17fd9bb5fcf3c85e3548e3e31b7fa1ebefe5 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:08:29 +0200 Subject: [PATCH 072/191] Fix "invalid role id" in addrole command --- backend/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index b9bd8d92..0c243941 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -1392,8 +1392,8 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string) // Role name const roleList = (await bot.guilds.fetch(guildId as Snowflake)).roles.cache; const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase()); - if (role[0]) { - return role[0].id; + if (role.size >= 1) { + return role.firstKey(); } // Role ID From 187e8235be199075f6d266e682cd9468293e2909 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:15:31 +0300 Subject: [PATCH 073/191] Add REST request/429 debugging, temporarily disable message fetch hotfix --- backend/src/index.ts | 24 ++++++++++++++++++++++-- backend/src/utils/hotfixMessageFetch.ts | 22 ++++++++++++---------- debug/.gitignore | 2 ++ 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 debug/.gitignore diff --git a/backend/src/index.ts b/backend/src/index.ts index cdfbda9a..c2204193 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,6 +22,26 @@ import { errorMessage, isDiscordAPIError, isDiscordHTTPError, SECONDS, successMe import { loadYamlSafely } from "./utils/loadYamlSafely"; import { DecayingCounter } from "./utils/DecayingCounter"; +// === START REST DEBUG === +import fs from "fs"; +import path from "path"; +const APIRequest = require("discord.js/src/rest/APIRequest.js"); + +const dateStr = new Date().toISOString().replace(/[:.]/g, "-"); +const restDebugFile = path.join("../debug", `rest_${dateStr}.log`); +fs.writeFileSync(restDebugFile, ""); + +const originalMake = APIRequest.prototype.make; +// tslint:disable-next-line:only-arrow-functions +APIRequest.prototype.make = function(...args) { + const debugInfo = `${new Date().toISOString()} ${this.method.toUpperCase()} ${this.route}`; + fs.appendFileSync(restDebugFile, debugInfo + "\n", { encoding: "utf8" }); + // tslint:disable-next-line:no-console + console.log(`[API REQUEST] ${this.method} ${this.route}`); + return originalMake.call(this, ...args); +}; +// === END REST DEBUG === + if (!process.env.KEY) { // tslint:disable-next-line:no-console console.error("Project root .env with KEY is required!"); @@ -152,8 +172,8 @@ connect().then(async () => { const client = new Client({ partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], - restGlobalRateLimit: 20, - restTimeOffset: 1000, + restGlobalRateLimit: 50, + // restTimeOffset: 1000, // Disable mentions by default allowedMentions: { diff --git a/backend/src/utils/hotfixMessageFetch.ts b/backend/src/utils/hotfixMessageFetch.ts index 658c5aaf..63ab46c8 100644 --- a/backend/src/utils/hotfixMessageFetch.ts +++ b/backend/src/utils/hotfixMessageFetch.ts @@ -8,14 +8,16 @@ let n = 0; export function hotfixMessageFetch(channel: TextChannel | ThreadChannel, messageId: string): Promise { const thisN = ++n; - // tslint:disable-next-line:no-console - console.trace( - `[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id} (queue size: ${queue.length})`, - ); - return queue.add(async () => { - await sleep(3000); - // tslint:disable-next-line:no-console - console.log(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); - return channel.messages.fetch(messageId); - }); + return channel.messages.fetch(messageId); + + // // tslint:disable-next-line:no-console + // console.trace( + // `[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id} (queue size: ${queue.length})`, + // ); + // return queue.add(async () => { + // await sleep(3000); + // // tslint:disable-next-line:no-console + // console.log(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); + // return channel.messages.fetch(messageId); + // }); } diff --git a/debug/.gitignore b/debug/.gitignore new file mode 100644 index 00000000..120f485d --- /dev/null +++ b/debug/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore From c806e8415a7cc0de977e78240d2a7078fe8ca142 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 22:11:20 +0300 Subject: [PATCH 074/191] Disable reaction role auto-refreshing --- .../src/plugins/ReactionRoles/ReactionRolesPlugin.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index 5690cd07..076e13ed 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -141,11 +141,11 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin( }, afterLoad(pluginData) { - let autoRefreshInterval = pluginData.config.get().auto_refresh_interval; - if (autoRefreshInterval != null) { - autoRefreshInterval = Math.max(MIN_AUTO_REFRESH, autoRefreshInterval); - autoRefreshLoop(pluginData, autoRefreshInterval); - } + // let autoRefreshInterval = pluginData.config.get().auto_refresh_interval; + // if (autoRefreshInterval != null) { + // autoRefreshInterval = Math.max(MIN_AUTO_REFRESH, autoRefreshInterval); + // autoRefreshLoop(pluginData, autoRefreshInterval); + // } }, beforeUnload(pluginData) { From d54f03361a4de11a2ceac336b1880c4cbccdcc4d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 22:12:39 +0300 Subject: [PATCH 075/191] Remove first check delay for expired mute checking --- backend/src/plugins/Mutes/MutesPlugin.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index 91490491..ad1c0c66 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -59,8 +59,6 @@ const defaultOptions = { }; const EXPIRED_MUTE_CHECK_INTERVAL = 60 * 1000; -let FIRST_CHECK_TIME = Date.now(); -const FIRST_CHECK_INCREMENT = 5 * 1000; export const MutesPlugin = zeppelinGuildPlugin()({ name: "mutes", @@ -115,17 +113,11 @@ export const MutesPlugin = zeppelinGuildPlugin()({ }, afterLoad(pluginData) { - // Check for expired mutes every 5s - const firstCheckTime = Math.max(Date.now(), FIRST_CHECK_TIME) + FIRST_CHECK_INCREMENT; - FIRST_CHECK_TIME = firstCheckTime; - - setTimeout(() => { - clearExpiredMutes(pluginData); - pluginData.state.muteClearIntervalId = setInterval( - () => clearExpiredMutes(pluginData), - EXPIRED_MUTE_CHECK_INTERVAL, - ); - }, firstCheckTime - Date.now()); + clearExpiredMutes(pluginData); + pluginData.state.muteClearIntervalId = setInterval( + () => clearExpiredMutes(pluginData), + EXPIRED_MUTE_CHECK_INTERVAL, + ); }, beforeUnload(pluginData) { From 329cd056522f333f538e4ad638883374a42abf9a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:01:32 +0300 Subject: [PATCH 076/191] Remove hotfixMessageFetch() --- .../plugins/MessageSaver/saveMessagesToDB.ts | 3 +-- .../commands/ClearReactionRolesCmd.ts | 3 +-- .../commands/InitReactionRolesCmd.ts | 3 +-- .../applyReactionRoleReactionsToMessage.ts | 3 +-- .../plugins/Slowmode/util/onMessageCreate.ts | 3 +-- .../events/StarboardReactionAddEvt.ts | 5 ++-- .../src/plugins/Utility/commands/SourceCmd.ts | 3 +-- backend/src/utils/hotfixMessageFetch.ts | 23 ------------------- 8 files changed, 8 insertions(+), 38 deletions(-) delete mode 100644 backend/src/utils/hotfixMessageFetch.ts diff --git a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts index da62188d..e4206fce 100644 --- a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts @@ -1,7 +1,6 @@ import { Message, Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { MessageSaverPluginType } from "./types"; -import { hotfixMessageFetch } from "../../utils/hotfixMessageFetch"; export async function saveMessagesToDB( pluginData: GuildPluginData, @@ -16,7 +15,7 @@ export async function saveMessagesToDB( let thisMsg: Message; try { - thisMsg = await hotfixMessageFetch(channel, id as Snowflake); + thisMsg = await channel.messages.fetch(id); if (!thisMsg) { failed.push(id); diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index 35ddfed7..3bdd6243 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isDiscordAPIError } from "../../../utils"; import { reactionRolesCmd } from "../types"; -import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; export const ClearReactionRolesCmd = reactionRolesCmd({ trigger: "reaction_roles clear", @@ -24,7 +23,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ let targetMessage: Message; try { - targetMessage = await hotfixMessageFetch(args.message.channel, args.message.messageId); + targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (err) { if (isDiscordAPIError(err) && err.code === 50001) { sendErrorMessage(pluginData, msg.channel, "Missing access to the specified message"); diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index e3a126fe..64a6e412 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -5,7 +5,6 @@ import { canUseEmoji, isDiscordAPIError, isValidEmoji, noop, trimPluginDescripti import { canReadChannel } from "../../../utils/canReadChannel"; import { reactionRolesCmd, TReactionRolePair } from "../types"; import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; -import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; const CLEAR_ROLES_EMOJI = "❌"; @@ -41,7 +40,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ let targetMessage; try { - targetMessage = await hotfixMessageFetch(args.message.channel, args.message.messageId); + targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (e) { if (isDiscordAPIError(e)) { sendErrorMessage(pluginData, msg.channel, `Error ${e.code} while getting message: ${e.message}`); diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 1ed9c17b..8b15e82c 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -5,7 +5,6 @@ import { LogType } from "../../../data/LogType"; import { isDiscordAPIError, sleep } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ReactionRolesPluginType } from "../types"; -import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; const CLEAR_ROLES_EMOJI = "❌"; @@ -26,7 +25,7 @@ export async function applyReactionRoleReactionsToMessage( let targetMessage; try { - targetMessage = await hotfixMessageFetch(channel, messageId as Snowflake); + targetMessage = channel.messages.fetch(messageId); } catch (e) { if (isDiscordAPIError(e)) { if (e.code === 10008) { diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 2ad8968f..2fac3025 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -11,7 +11,6 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { BOT_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; import { SlowmodePluginType } from "../types"; import { applyBotSlowmodeToUserId } from "./applyBotSlowmodeToUserId"; -import { hotfixMessageFetch } from "../../../utils/hotfixMessageFetch"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; @@ -50,7 +49,7 @@ export async function onMessageCreate(pluginData: GuildPluginData { - const thisN = ++n; - - return channel.messages.fetch(messageId); - - // // tslint:disable-next-line:no-console - // console.trace( - // `[${thisN}] Queueing to fetch message id ${messageId} from channel ${channel.id} (queue size: ${queue.length})`, - // ); - // return queue.add(async () => { - // await sleep(3000); - // // tslint:disable-next-line:no-console - // console.log(`[${thisN}] Fetching message id ${messageId} from channel ${channel.id}`); - // return channel.messages.fetch(messageId); - // }); -} From d190957c7f4cf87073c0906df7bc826b2459ceec Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Fri, 20 Aug 2021 19:01:47 +0200 Subject: [PATCH 077/191] Check if VC actually changed --- backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts index 0abfadc8..51e349e7 100644 --- a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts +++ b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts @@ -30,6 +30,7 @@ export const LogsVoiceStateUpdateEvt = logsEvt({ channel: newChannel, }); } else if (oldChannel && newChannel) { + if (oldChannel.id === newChannel.id) return; logVoiceChannelMove(meta.pluginData, { member, oldChannel, From c86abb04a07bf908f2f21265c6e030dce8537133 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:02:18 +0300 Subject: [PATCH 078/191] Remove REST request debug logging --- backend/src/index.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index c2204193..fd4993fd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,5 +1,4 @@ import { Client, Constants, Intents, TextChannel, ThreadChannel } from "discord.js"; -import yaml from "js-yaml"; import { Knub, PluginError } from "knub"; import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; // Always use UTC internally @@ -22,26 +21,6 @@ import { errorMessage, isDiscordAPIError, isDiscordHTTPError, SECONDS, successMe import { loadYamlSafely } from "./utils/loadYamlSafely"; import { DecayingCounter } from "./utils/DecayingCounter"; -// === START REST DEBUG === -import fs from "fs"; -import path from "path"; -const APIRequest = require("discord.js/src/rest/APIRequest.js"); - -const dateStr = new Date().toISOString().replace(/[:.]/g, "-"); -const restDebugFile = path.join("../debug", `rest_${dateStr}.log`); -fs.writeFileSync(restDebugFile, ""); - -const originalMake = APIRequest.prototype.make; -// tslint:disable-next-line:only-arrow-functions -APIRequest.prototype.make = function(...args) { - const debugInfo = `${new Date().toISOString()} ${this.method.toUpperCase()} ${this.route}`; - fs.appendFileSync(restDebugFile, debugInfo + "\n", { encoding: "utf8" }); - // tslint:disable-next-line:no-console - console.log(`[API REQUEST] ${this.method} ${this.route}`); - return originalMake.call(this, ...args); -}; -// === END REST DEBUG === - if (!process.env.KEY) { // tslint:disable-next-line:no-console console.error("Project root .env with KEY is required!"); From a72dc89293fd2b5d9eec83cd021d629bfd3adc6d Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Fri, 20 Aug 2021 19:05:59 +0200 Subject: [PATCH 079/191] Same fix for Spam plugin --- backend/src/plugins/Spam/events/SpamVoiceEvt.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts index 36aba638..558216b9 100644 --- a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts +++ b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts @@ -9,6 +9,7 @@ export const SpamVoiceStateUpdateEvt = spamEvt({ if (!member) return; const channel = meta.args.newState.channel; if (!channel) return; + if (channel.id === meta.args.oldState?.id) return; const config = await meta.pluginData.config.getMatchingConfig({ member, channelId: channel.id }); const maxVoiceMoves = config.max_voice_moves; From 220b28d4846fc2535de1e0811800eb3866d2f430 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:23:01 +0300 Subject: [PATCH 080/191] Fix extra denied permissions on companion channels --- .../functions/handleCompanionPermissions.ts | 5 ++++- backend/src/utils/filterObject.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 backend/src/utils/filterObject.ts diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts index d6e3e764..c4e66f49 100644 --- a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -5,6 +5,7 @@ import { isDiscordAPIError, MINUTES } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { CompanionChannelsPluginType, TCompanionChannelOpts } from "../types"; import { getCompanionChannelOptsForVoiceChannelId } from "./getCompanionChannelOptsForVoiceChannelId"; +import { filterObject } from "../../../utils/filterObject"; const ERROR_COOLDOWN_KEY = "errorCooldown"; const ERROR_COOLDOWN = 5 * MINUTES; @@ -63,7 +64,9 @@ export async function handleCompanionPermissions( const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) continue; pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channelId, 3 * 1000); - await channel.permissionOverwrites.create(userId as Snowflake, new Permissions(BigInt(permissions)).serialize(), { + const fullSerialized = new Permissions(BigInt(permissions)).serialize(); + const onlyAllowed = filterObject(fullSerialized, v => v === true); + await channel.permissionOverwrites.create(userId, onlyAllowed, { reason: `Companion Channel for ${voiceChannel!.id} | User Joined`, }); } diff --git a/backend/src/utils/filterObject.ts b/backend/src/utils/filterObject.ts new file mode 100644 index 00000000..f5fd6de0 --- /dev/null +++ b/backend/src/utils/filterObject.ts @@ -0,0 +1,13 @@ +/** + * Filter an object's properties based on its values and keys + * @return New object with filtered properties + */ +export function filterObject>( + object: T, + filterFn: (value: T[K], key: K) => boolean, +): Record { + return Object.fromEntries(Object.entries(object).filter(([key, value]) => filterFn(value as any, key))) as Record< + keyof T, + T[keyof T] + >; +} From bd2c51691a0e5161b5a3f82838aa6b2fe9add692 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:23:30 +0300 Subject: [PATCH 081/191] Custom events permissions fix(?) --- .../CustomEvents/actions/setChannelPermissionOverrides.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts index 31d60ed5..6c345f05 100644 --- a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts +++ b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts @@ -34,7 +34,7 @@ export async function setChannelPermissionOverridesAction( for (const override of action.overrides) { channel.permissionOverwrites.create( override.id as Snowflake, - new Permissions(BigInt(override.allow)).add(BigInt(override.deny)).serialize(), + new Permissions(BigInt(override.allow)).remove(BigInt(override.deny)).serialize(), ); /* From 0b28215208eb3b50addd3ee7ea9a90ce24035afb Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:31:36 +0300 Subject: [PATCH 082/191] Fix logMessage variable not working in automod alert --- backend/src/plugins/Automod/actions/alert.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index 6c63e5ce..314bb552 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -16,10 +16,12 @@ import { tNormalizedNullOptional, isTruthy, verboseChannelMention, + validateAndParseMessageContent, } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { automodAction } from "../helpers"; import { TemplateSafeUser, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { messageIsEmpty } from "../../../utils/messageIsEmpty"; export const AlertAction = automodAction({ configType: t.type({ @@ -66,7 +68,7 @@ export const AlertAction = automodAction({ actionsTaken, matchSummary: matchResult.summary, messageLink: theMessageLink, - logMessage: logMessage?.content, + logMessage: validateAndParseMessageContent(logMessage)?.content, }), ); } catch (err) { @@ -80,6 +82,13 @@ export const AlertAction = automodAction({ throw err; } + if (messageIsEmpty(rendered)) { + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Tried to send alert with an empty message for automod rule ${ruleName}`, + }); + return; + } + try { await createChunkedMessage( channel, From d57d563761ffc01b5a2c7de653b7b497dbff95cd Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:43:08 +0300 Subject: [PATCH 083/191] Update supporters formatting in !about --- .../src/plugins/Utility/commands/AboutCmd.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/plugins/Utility/commands/AboutCmd.ts b/backend/src/plugins/Utility/commands/AboutCmd.ts index 0d6f8493..5858d369 100644 --- a/backend/src/plugins/Utility/commands/AboutCmd.ts +++ b/backend/src/plugins/Utility/commands/AboutCmd.ts @@ -7,6 +7,7 @@ import { getCurrentUptime } from "../../../uptime"; import { multiSorter, resolveMember, sorter } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { utilityCmd } from "../types"; +import { shuffle } from "lodash"; export const AboutCmd = utilityCmd({ trigger: "about", @@ -80,17 +81,19 @@ export const AboutCmd = utilityCmd({ }; const supporters = await pluginData.state.supporters.getAll(); - supporters.sort( - multiSorter([ - [r => r.amount, "DESC"], - [r => r.name.toLowerCase(), "ASC"], - ]), - ); + const shuffledSupporters = shuffle(supporters); if (supporters.length) { + const formattedSupporters = shuffledSupporters + // Bold every other supporter to make them easy to recognize from each other + .map((s, i) => (i % 2 === 0 ? `**${s.name}**` : `__${s.name}__`)) + .join(" "); + aboutContent.embeds![0].fields!.push({ name: "Zeppelin supporters 🎉", - value: supporters.map(s => `**${s.name}** ${s.amount ? `${s.amount}€/mo` : ""}`.trim()).join("\n"), + value: + "These amazing people have supported Zeppelin development by pledging on [Patreon](https://www.patreon.com/zeppelinbot):\n\n" + + formattedSupporters, inline: false, }); } From ebe224885e599fd66be4a797c687d5d074578f84 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 20:44:04 +0300 Subject: [PATCH 084/191] Small wording changes to !about --- backend/src/plugins/Utility/commands/AboutCmd.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Utility/commands/AboutCmd.ts b/backend/src/plugins/Utility/commands/AboutCmd.ts index 5858d369..219afc15 100644 --- a/backend/src/plugins/Utility/commands/AboutCmd.ts +++ b/backend/src/plugins/Utility/commands/AboutCmd.ts @@ -47,8 +47,8 @@ export const AboutCmd = utilityCmd({ const basicInfoRows = [ ["Uptime", prettyUptime], - ["Last reload", `${lastReload} ago`], - ["Last update", lastUpdate], + ["Last config reload", `${lastReload} ago`], + ["Last bot update", lastUpdate], ["Version", version], ["API latency", `${pluginData.client.ws.ping}ms`], ["Server timezone", timeAndDate.getGuildTz()], From 7054412640215b95a18b10816eb57b8bb6f482b8 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:00:53 +0300 Subject: [PATCH 085/191] MessageSaver: use a queue, add more logging for duplicate IDs --- backend/src/data/GuildSavedMessages.ts | 2 +- .../MessageSaver/MessageSaverPlugin.ts | 2 + .../MessageSaver/events/SaveMessagesEvts.ts | 37 ++++++++++++++++--- backend/src/plugins/MessageSaver/types.ts | 2 + 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index ea114665..0e7db243 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -213,7 +213,7 @@ export class GuildSavedMessages extends BaseGuildRepository { await this.messages.insert(data); } catch (e) { if (e?.code === "ER_DUP_ENTRY") { - console.warn(`Tried to insert duplicate message ID: ${data.id}`); + console.trace(`Tried to insert duplicate message ID: ${data.id}`); return; } diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index eb529ffa..0f316ad2 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -5,6 +5,7 @@ import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; import { ConfigSchema, MessageSaverPluginType } from "./types"; +import { Queue } from "../../Queue"; const defaultOptions: PluginOptions = { config: { @@ -44,5 +45,6 @@ export const MessageSaverPlugin = zeppelinGuildPlugin()( beforeLoad(pluginData) { const { state, guild } = pluginData; state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); + state.queue = new Queue(); }, }); diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index c8464258..79d1fc5d 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -1,5 +1,16 @@ -import { Message } from "discord.js"; +import { Message, Snowflake } from "discord.js"; import { messageSaverEvt } from "../types"; +import { SECONDS } from "../../../utils"; + +const recentlyCreatedMessages: Snowflake[] = []; +const recentlyCreatedMessagesToKeep = 100; + +setInterval(() => { + const toDelete = recentlyCreatedMessages.length - recentlyCreatedMessagesToKeep; + if (toDelete > 0) { + recentlyCreatedMessages.splice(0, toDelete); + } +}, 60 * SECONDS); export const MessageCreateEvt = messageSaverEvt({ event: "messageCreate", @@ -17,7 +28,17 @@ export const MessageCreateEvt = messageSaverEvt({ return; } - await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); + meta.pluginData.state.queue.add(async () => { + if (recentlyCreatedMessages.includes(meta.args.message.id)) { + console.warn( + `Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`, + ); + return; + } + recentlyCreatedMessages.push(meta.args.message.id); + + await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); + }); }, }); @@ -35,7 +56,9 @@ export const MessageUpdateEvt = messageSaverEvt({ return; } - await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); + meta.pluginData.state.queue.add(async () => { + await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); + }); }, }); @@ -50,7 +73,9 @@ export const MessageDeleteEvt = messageSaverEvt({ return; } - await meta.pluginData.state.savedMessages.markAsDeleted(msg.id); + meta.pluginData.state.queue.add(async () => { + await meta.pluginData.state.savedMessages.markAsDeleted(msg.id); + }); }, }); @@ -61,6 +86,8 @@ export const MessageDeleteBulkEvt = messageSaverEvt({ async listener(meta) { const ids = meta.args.messages.map(m => m.id); - await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); + meta.pluginData.state.queue.add(async () => { + await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); + }); }, }); diff --git a/backend/src/plugins/MessageSaver/types.ts b/backend/src/plugins/MessageSaver/types.ts index 68f815be..f8b481f7 100644 --- a/backend/src/plugins/MessageSaver/types.ts +++ b/backend/src/plugins/MessageSaver/types.ts @@ -1,6 +1,7 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { Queue } from "../../Queue"; export const ConfigSchema = t.type({ can_manage: t.boolean, @@ -11,6 +12,7 @@ export interface MessageSaverPluginType extends BasePluginType { config: TConfigSchema; state: { savedMessages: GuildSavedMessages; + queue: Queue; }; } From 7e179bb06cb8ce77143023dd797d2cfb98c462d0 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:02:16 +0300 Subject: [PATCH 086/191] Fix default role showing up in user info embed --- backend/src/plugins/Utility/functions/getUserInfoEmbed.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index 78970a0e..dde541d7 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -101,9 +101,7 @@ export async function getUserInfoEmbed( largest: 2, round: true, }); - const roles = member.roles.cache - .map(role => pluginData.guild.roles.cache.get(role.id)) - .filter((r): r is Role => !!r); + const roles = Array.from(member.roles.cache.values()).filter(r => r.id !== pluginData.guild.id); roles.sort(sorter("position", "DESC")); embed.fields.push({ From 8585686032767d90e155fdbf049c6901ee6a8b1f Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:21:41 +0300 Subject: [PATCH 087/191] Another fix for voice move spam false positives --- backend/src/plugins/Spam/events/SpamVoiceEvt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts index 558216b9..ea354a98 100644 --- a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts +++ b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts @@ -9,7 +9,7 @@ export const SpamVoiceStateUpdateEvt = spamEvt({ if (!member) return; const channel = meta.args.newState.channel; if (!channel) return; - if (channel.id === meta.args.oldState?.id) return; + if (channel.id === meta.args.oldState?.channelId) return; const config = await meta.pluginData.config.getMatchingConfig({ member, channelId: channel.id }); const maxVoiceMoves = config.max_voice_moves; From 817d38b8ace67c90bcd1194377f00540a56455e5 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:37:25 +0300 Subject: [PATCH 088/191] Fix 100KB config size limit --- backend/src/api/start.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/api/start.ts b/backend/src/api/start.ts index 2e3e2c7d..7d046c12 100644 --- a/backend/src/api/start.ts +++ b/backend/src/api/start.ts @@ -14,7 +14,11 @@ app.use( origin: process.env.DASHBOARD_URL, }), ); -app.use(express.json()); +app.use( + express.json({ + limit: "10mb", + }), +); initAuth(app); initGuildsAPI(app); From 6e4bc17daef615d744f1286012fcddf5023ec247 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:38:35 +0300 Subject: [PATCH 089/191] Reduce d.js debug event logging --- backend/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index fd4993fd..73a79ce4 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -190,8 +190,6 @@ connect().then(async () => { const safe429Counter = new DecayingCounter(safe429DecayInterval); client.on(Constants.Events.DEBUG, errorText => { if (!errorText.includes("429")) { - // tslint:disable-next-line:no-console - console.debug(`[DEBUG] ${errorText}`); return; } From 3a7aaecf9228b631933a132bf9b8d61bfa702542 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:40:07 +0300 Subject: [PATCH 090/191] Suppress unknown override criteria errors temporarily --- backend/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 73a79ce4..9e27b64c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -96,13 +96,13 @@ function errorHandler(err) { // FIXME: Hotfix if (err.message && err.message.startsWith("Unknown custom override criteria")) { - console.warn(err.message); + // console.warn(err.message); return; } // FIXME: Hotfix if (err.message && err.message.startsWith("Unknown override criteria")) { - console.warn(err.message); + // console.warn(err.message); return; } From 41f8e190dd0aaaafac2a15babe7f27fa562d3c2c Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:40:59 +0300 Subject: [PATCH 091/191] Remove server-specific timed unmute logging --- .../src/plugins/Mutes/functions/clearExpiredMutes.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index e4159f2e..ea94b361 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -9,12 +9,6 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function clearExpiredMutes(pluginData: GuildPluginData) { const expiredMutes = await pluginData.state.mutes.getExpiredMutes(); - if (pluginData.guild.id === "140933721929940992") { - // tslint:disable-next-line:no-console - console.log( - `Expired mutes (${expiredMutes.length}) for 140933721929940992: ${expiredMutes.map(m => m.user_id).join(", ")}`, - ); - } for (const mute of expiredMutes) { const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id); @@ -53,8 +47,4 @@ export async function clearExpiredMutes(pluginData: GuildPluginData Date: Fri, 20 Aug 2021 21:49:59 +0300 Subject: [PATCH 092/191] Make sure we always fetch full/fresh target message when applying reaction roles --- .../ReactionRoles/util/applyReactionRoleReactionsToMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 8b15e82c..86c81a29 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -25,7 +25,7 @@ export async function applyReactionRoleReactionsToMessage( let targetMessage; try { - targetMessage = channel.messages.fetch(messageId); + targetMessage = channel.messages.fetch(messageId, { force: true }); } catch (e) { if (isDiscordAPIError(e)) { if (e.code === 10008) { From 4878589786c2349356f1334dd81f30d0e2e97c80 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:10:18 +0300 Subject: [PATCH 093/191] Remove unnecessary message fetch in slowmode onMessageCreate --- backend/src/plugins/Slowmode/util/onMessageCreate.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 2fac3025..612a1822 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -1,7 +1,6 @@ import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { LogType } from "../../../data/LogType"; import { hasPermission } from "../../../pluginUtils"; import { resolveMember } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; @@ -49,13 +48,14 @@ export async function onMessageCreate(pluginData: GuildPluginData Date: Fri, 20 Aug 2021 22:13:02 +0300 Subject: [PATCH 094/191] Don't crash on PluginNotLoadedError --- backend/src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index 9e27b64c..2ab3417c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -20,6 +20,7 @@ import { startUptimeCounter } from "./uptime"; import { errorMessage, isDiscordAPIError, isDiscordHTTPError, SECONDS, successMessage } from "./utils"; import { loadYamlSafely } from "./utils/loadYamlSafely"; import { DecayingCounter } from "./utils/DecayingCounter"; +import { PluginNotLoadedError } from "knub/dist/plugins/PluginNotLoadedError"; if (!process.env.KEY) { // tslint:disable-next-line:no-console @@ -106,6 +107,13 @@ function errorHandler(err) { return; } + if (err instanceof PluginNotLoadedError) { + // We don't want to crash the bot here, although this *should not happen* + // TODO: Proper system for preventing plugin load/unload race conditions + console.error(err); + return; + } + // tslint:disable:no-console console.error(err); From dcdaaf9de82eb0d60765e9d4f99fdf52024f2c01 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:21:13 +0300 Subject: [PATCH 095/191] Add debug logging for unsafe template formatter values --- backend/src/templateFormatter.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/templateFormatter.ts b/backend/src/templateFormatter.ts index f9e80d96..7b61af1e 100644 --- a/backend/src/templateFormatter.ts +++ b/backend/src/templateFormatter.ts @@ -65,6 +65,13 @@ export class TemplateSafeValueContainer { constructor(data: Record = {}) { for (const [key, value] of Object.entries(data)) { if (!isTemplateSafeValue(value)) { + // tslint:disable:no-console + console.error("=== CONTEXT FOR UNSAFE VALUE ==="); + console.error("stringified:", JSON.stringify(value)); + console.error("typeof:", typeof value); + console.error("constructor name:", (value as any)?.constructor?.name); + console.error("=== /CONTEXT FOR UNSAFE VALUE ==="); + // tslint:enable:no-console throw new Error(`Unsafe value for key "${key}" in SafeTemplateValueContainer`); } From 37705d24a4a12868a2afb64ab6026e290329c6e3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:21:42 +0300 Subject: [PATCH 096/191] Add more type safety to renderTagBody()'s args parameter --- backend/src/plugins/Tags/util/renderTagBody.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index cd8afcfe..1d039b44 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; -import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; +import { renderTemplate, TemplateSafeValue, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderRecursively, StrictMessageContent } from "../../../utils"; import { TagsPluginType, TTag } from "../types"; import { findTagByName } from "./findTagByName"; @@ -8,7 +8,7 @@ import { findTagByName } from "./findTagByName"; export async function renderTagBody( pluginData: GuildPluginData, body: TTag, - args: unknown[] = [], + args: TemplateSafeValue[] = [], extraData = {}, subTagPermissionMatchParams?: ExtendedMatchParams, ): Promise { From 3a9caae758138cbfa7ef1c6b7f1a0f45fa0b1567 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:26:40 +0300 Subject: [PATCH 097/191] Always fetch fresh member data when unmuting --- backend/src/plugins/Mutes/functions/clearExpiredMutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index ea94b361..50d6414a 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -10,7 +10,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; export async function clearExpiredMutes(pluginData: GuildPluginData) { const expiredMutes = await pluginData.state.mutes.getExpiredMutes(); for (const mute of expiredMutes) { - const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id); + const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id, true); if (member) { try { From be0f9182a0613fe6c48dd80bc1d054c7ff6b7454 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:27:35 +0300 Subject: [PATCH 098/191] Always fetch fresh member data when unmuting v2: electric boogaloo --- backend/src/plugins/Mutes/functions/unmuteUser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index 73a07b80..fd32fbc5 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -20,7 +20,7 @@ export async function unmuteUser( ): Promise { const existingMute = await pluginData.state.mutes.findExistingMuteForUserId(userId); const user = await resolveUser(pluginData.client, userId); - const member = await resolveMember(pluginData.client, pluginData.guild, userId); // Grab the fresh member so we don't have stale role info + const member = await resolveMember(pluginData.client, pluginData.guild, userId, true); // Grab the fresh member so we don't have stale role info const modId = caseArgs.modId || pluginData.client.user!.id; if (!existingMute && member && !memberHasMutedRole(pluginData, member)) return null; From 2451719155a1fc94154ba30eaecf2797fdc4d9d3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:34:31 +0300 Subject: [PATCH 099/191] Tweaks to member role restoration on unmute --- .../src/plugins/Mutes/functions/clearExpiredMutes.ts | 12 ++++++------ backend/src/plugins/Mutes/functions/unmuteUser.ts | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index 50d6414a..308fa5f1 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -18,17 +18,17 @@ export async function clearExpiredMutes(pluginData: GuildPluginData roleId !== muteRole); for (const toRestore of mute.roles_to_restore) { - if (guildRoles.has(toRestore as Snowflake) && toRestore !== muteRole) newRoles.push(toRestore); + if (guildRoles.has(toRestore) && toRestore !== muteRole && !newRoles.includes(toRestore)) { + newRoles.push(toRestore); + } } - await member.roles.set(newRoles as Snowflake[]); + await member.roles.set(newRoles); } lock.unlock(); diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index fd32fbc5..403d37d8 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -43,12 +43,13 @@ export async function unmuteUser( } if (existingMute?.roles_to_restore) { const guildRoles = pluginData.guild.roles.cache; - let newRoles = [...member.roles.cache.keys()]; - newRoles = muteRole && newRoles.includes(muteRole) ? newRoles.splice(newRoles.indexOf(muteRole), 1) : newRoles; + const newRoles = [...member.roles.cache.keys()].filter(roleId => roleId !== muteRole); for (const toRestore of existingMute.roles_to_restore) { - if (guildRoles.has(toRestore as Snowflake) && toRestore !== muteRole) newRoles.push(toRestore); + if (guildRoles.has(toRestore) && toRestore !== muteRole && !newRoles.includes(toRestore)) { + newRoles.push(toRestore); + } } - await member.roles.set(newRoles as Snowflake[]); + await member.roles.set(newRoles); } lock.unlock(); From ddfdcdccd365a7219639ef9e5f405289ab153368 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:38:53 +0300 Subject: [PATCH 100/191] Don't crash on ZodError from validateAndParseMessageContent() --- backend/src/utils.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 0c243941..8a7f7c55 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -44,7 +44,7 @@ import { ChannelTypeStrings } from "./types"; import { sendDM } from "./utils/sendDM"; import { waitForButtonConfirm } from "./utils/waitForInteraction"; import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; -import { z } from "zod"; +import { z, ZodError } from "zod"; const fsp = fs.promises; @@ -432,7 +432,16 @@ export function validateAndParseMessageContent(input: unknown): StrictMessageCon dropNullValuesRecursively(input); - return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; + try { + return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; + } catch (err) { + if (err instanceof ZodError) { + // TODO: Allow error to be thrown and handle at use location + return {}; + } + + throw err; + } } function dropNullValuesRecursively(obj: any) { From f31ef176b443402f7cdfa97e47c42db38cc569fd Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 20 Aug 2021 22:51:44 +0300 Subject: [PATCH 101/191] Add more 429 debugging --- backend/src/plugins/Slowmode/util/onMessageCreate.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 612a1822..2af5a9bf 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -49,6 +49,11 @@ export async function onMessageCreate(pluginData: GuildPluginData Date: Fri, 20 Aug 2021 23:22:10 +0300 Subject: [PATCH 102/191] Auto-resize config editor when window is resized --- .../dashboard/GuildConfigEditor.vue | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/dashboard/src/components/dashboard/GuildConfigEditor.vue b/dashboard/src/components/dashboard/GuildConfigEditor.vue index e6f780e2..191892ad 100644 --- a/dashboard/src/components/dashboard/GuildConfigEditor.vue +++ b/dashboard/src/components/dashboard/GuildConfigEditor.vue @@ -24,7 +24,6 @@ @init="editorInit" lang="yaml" theme="tomorrow_night" - :width="editorWidth" :height="editorHeight" ref="aceEditor" /> @@ -38,6 +37,7 @@ import AceEditor from "vue2-ace-editor"; let editorKeybindListener; + let windowResizeListener; export default { components: { @@ -69,6 +69,12 @@ window.removeEventListener("keydown", editorKeybindListener); editorKeybindListener = null; } + + if (windowResizeListener) { + window.removeEventListener("resize", windowResizeListener); + windowResizeListener = null; + } + next(); }, data() { @@ -105,6 +111,7 @@ tabSize: 2 }); + // Add Ctrl+S/Cmd+S save shortcut const isMac = /mac/i.test(navigator.platform); const modKeyPressed = (ev: KeyboardEvent) => (isMac ? ev.metaKey : ev.ctrlKey); const nonModKeyPressed = (ev: KeyboardEvent) => (isMac ? ev.ctrlKey : ev.metaKey); @@ -130,7 +137,24 @@ }; window.addEventListener("keydown", editorKeybindListener); + // Auto-fit editor to window this.fitEditorToWindow(); + + if (windowResizeListener) { + window.removeEventListener("resize", windowResizeListener); + } + + let debounceTimeout; + windowResizeListener = (ev: UIEvent) => { + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + + debounceTimeout = setTimeout(() => { + this.fitEditorToWindow(); + }, 350); + }; + window.addEventListener("resize", windowResizeListener); }, fitEditorToWindow() { const mainContainer = document.querySelector('.dashboard'); From ce8618957f4ba686d26209190a7a04f1ce8417bb Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 00:17:29 +0300 Subject: [PATCH 103/191] Add debugging for duplicate audit_log_ids --- backend/src/data/GuildCases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index 8c09ecd8..f0e9de43 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -126,7 +126,7 @@ export class GuildCases extends BaseGuildRepository { .catch(err => { if (err?.code === "ER_DUP_ENTRY") { if (data.audit_log_id) { - console.warn(`Tried to insert case with duplicate audit_log_id`); + console.trace(`Tried to insert case with duplicate audit_log_id`); return this.createInternal({ ...data, audit_log_id: undefined, From 57705656f955fba2fa2e43f48e4ce615ff5663a8 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 00:38:19 +0300 Subject: [PATCH 104/191] Allow Automod to act on application command replies --- .../src/plugins/MessageSaver/events/SaveMessagesEvts.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 79d1fc5d..386549e6 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -1,4 +1,4 @@ -import { Message, Snowflake } from "discord.js"; +import { Constants, Message, MessageType, Snowflake } from "discord.js"; import { messageSaverEvt } from "../types"; import { SECONDS } from "../../../utils"; @@ -12,14 +12,15 @@ setInterval(() => { } }, 60 * SECONDS); +const AFFECTED_MESSAGE_TYPES: MessageType[] = ["DEFAULT", "REPLY", "APPLICATION_COMMAND"]; + export const MessageCreateEvt = messageSaverEvt({ event: "messageCreate", allowBots: true, allowSelf: true, async listener(meta) { - // Only save regular chat messages - if (meta.args.message.type !== "DEFAULT" && meta.args.message.type !== "REPLY") { + if (!AFFECTED_MESSAGE_TYPES.includes(meta.args.message.type)) { return; } From b336e8b5f47ee52936871d6511dc4d6263fac9ad Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 00:39:50 +0300 Subject: [PATCH 105/191] Don't apply Automod on Zeppelin itself --- backend/src/plugins/Automod/functions/runAutomod.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/plugins/Automod/functions/runAutomod.ts b/backend/src/plugins/Automod/functions/runAutomod.ts index 67675565..cba8f029 100644 --- a/backend/src/plugins/Automod/functions/runAutomod.ts +++ b/backend/src/plugins/Automod/functions/runAutomod.ts @@ -15,6 +15,11 @@ export async function runAutomod(pluginData: GuildPluginData, const channel = channelId ? (pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel) : null; const categoryId = channel?.parentId; + // Don't apply Automod on Zeppelin itself + if (userId && userId === pluginData.client.user?.id) { + return; + } + const config = await pluginData.config.getMatchingConfig({ channelId, categoryId, From ca9ea600823dae2d550815381a5bb3feca454a94 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 00:53:58 +0300 Subject: [PATCH 106/191] Fix crash when calling tags from other tags --- backend/src/plugins/Tags/util/renderTagBody.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 1d039b44..90efffe3 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -47,7 +47,7 @@ export async function renderTagBody( return ""; } - const rendered = await renderTagBody(pluginData, subTagBody, subTagArgs, subTagPermissionMatchParams); + const rendered = await renderTagBody(pluginData, subTagBody, subTagArgs, extraData, subTagPermissionMatchParams); return rendered.content!; }, }); From 05df81ddca807fa3fdc5d372cc8407377509a462 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 00:59:51 +0300 Subject: [PATCH 107/191] Fix being unable to !post to announcement channels or threads --- .../plugins/Logs/logFunctions/logRepeatedMessage.ts | 4 ++-- .../plugins/Logs/logFunctions/logScheduledMessage.ts | 4 ++-- .../Logs/logFunctions/logScheduledRepeatedMessage.ts | 4 ++-- backend/src/plugins/Post/util/actualPostCmd.ts | 10 +++++++--- backend/src/plugins/Post/util/postMessage.ts | 4 ++-- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts b/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts index 70fdaf00..d17b5a55 100644 --- a/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts +++ b/backend/src/plugins/Logs/logFunctions/logRepeatedMessage.ts @@ -3,12 +3,12 @@ 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 { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; interface LogRepeatedMessageData { author: User; - channel: BaseGuildTextChannel; + channel: BaseGuildTextChannel | ThreadChannel; datetime: string; date: string; time: string; diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts index 64cce036..27408798 100644 --- a/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts +++ b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts @@ -3,12 +3,12 @@ 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 { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; interface LogScheduledMessageData { author: User; - channel: BaseGuildTextChannel; + channel: BaseGuildTextChannel | ThreadChannel; datetime: string; date: string; time: string; diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts index d020188f..55d24df3 100644 --- a/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts +++ b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts @@ -3,12 +3,12 @@ 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 { BaseGuildTextChannel, ThreadChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; interface LogScheduledRepeatedMessageData { author: User; - channel: BaseGuildTextChannel; + channel: BaseGuildTextChannel | ThreadChannel; datetime: string; date: string; time: string; diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index e5e25d70..dc1d662e 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -1,4 +1,4 @@ -import { Channel, Message, TextChannel } from "discord.js"; +import { Channel, Message, NewsChannel, TextChannel, ThreadChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; @@ -29,8 +29,12 @@ export async function actualPostCmd( "repeat-times"?: number; } = {}, ) { - if (!(targetChannel instanceof TextChannel)) { - msg.channel.send(errorMessage("Channel is not a text channel")); + if ( + !(targetChannel instanceof TextChannel) && + !(targetChannel instanceof NewsChannel) && + !(targetChannel instanceof ThreadChannel) + ) { + msg.channel.send(errorMessage("Specified channel is not a text channel, announcement channel, or thread")); return; } diff --git a/backend/src/plugins/Post/util/postMessage.ts b/backend/src/plugins/Post/util/postMessage.ts index 5bb13095..0395867c 100644 --- a/backend/src/plugins/Post/util/postMessage.ts +++ b/backend/src/plugins/Post/util/postMessage.ts @@ -1,4 +1,4 @@ -import { Message, MessageAttachment, MessageOptions, TextChannel } from "discord.js"; +import { Message, MessageAttachment, MessageOptions, NewsChannel, TextChannel, ThreadChannel } from "discord.js"; import fs from "fs"; import { GuildPluginData } from "knub"; import { downloadFile } from "../../../utils"; @@ -9,7 +9,7 @@ const fsp = fs.promises; export async function postMessage( pluginData: GuildPluginData, - channel: TextChannel, + channel: TextChannel | NewsChannel | ThreadChannel, content: MessageOptions, attachments: MessageAttachment[] = [], enableMentions: boolean = false, From 204a8619ae6d401e093e2b8353de6d8b5071d10e Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 01:05:34 +0300 Subject: [PATCH 108/191] Fix nested tag limiter --- backend/src/plugins/Tags/util/renderTagBody.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 90efffe3..de175d73 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -5,16 +5,17 @@ import { renderRecursively, StrictMessageContent } from "../../../utils"; import { TagsPluginType, TTag } from "../types"; import { findTagByName } from "./findTagByName"; +const MAX_TAG_FN_CALLS = 25; + export async function renderTagBody( pluginData: GuildPluginData, body: TTag, args: TemplateSafeValue[] = [], extraData = {}, subTagPermissionMatchParams?: ExtendedMatchParams, + tagFnCallsObj = { calls: 0 }, ): Promise { const dynamicVars = {}; - const maxTagFnCalls = 25; - let tagFnCalls = 0; const data = new TemplateSafeValueContainer({ args, @@ -33,7 +34,7 @@ export async function renderTagBody( return dynamicVars[name] == null ? "" : dynamicVars[name]; }, tag: async (name, ...subTagArgs) => { - if (tagFnCalls++ > maxTagFnCalls) return "\\_recursion\\_"; + if (++tagFnCallsObj.calls > MAX_TAG_FN_CALLS) return ""; if (typeof name !== "string") return ""; if (name === "") return ""; @@ -47,7 +48,14 @@ export async function renderTagBody( return ""; } - const rendered = await renderTagBody(pluginData, subTagBody, subTagArgs, extraData, subTagPermissionMatchParams); + const rendered = await renderTagBody( + pluginData, + subTagBody, + subTagArgs, + extraData, + subTagPermissionMatchParams, + tagFnCallsObj, + ); return rendered.content!; }, }); From 3c8355babf278f5d42962f9d28b6afa74c779d7f Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 01:41:42 +0300 Subject: [PATCH 109/191] Fix logAutomodAction error if Automod context doesn't include a user --- backend/src/plugins/Logs/logFunctions/logAutomodAction.ts | 8 ++++---- backend/src/plugins/Logs/types.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts index b76194a9..ffa68c03 100644 --- a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts +++ b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts @@ -8,7 +8,7 @@ import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; interface LogAutomodActionData { rule: string; - user: User; + user?: User | null; users: User[]; actionsTaken: string; matchSummary: string; @@ -20,14 +20,14 @@ export function logAutomodAction(pluginData: GuildPluginData, da LogType.AUTOMOD_ACTION, createTypedTemplateSafeValueContainer({ rule: data.rule, - user: userToTemplateSafeUser(data.user), + user: data.user ? userToTemplateSafeUser(data.user) : null, users: data.users.map(user => userToTemplateSafeUser(user)), actionsTaken: data.actionsTaken, matchSummary: data.matchSummary ?? "", }), { - userId: data.user.id, - bot: data.user.bot, + userId: data.user ? data.user.id : null, + bot: data.user ? data.user.bot : false, }, ); } diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index 8dd547f2..c952349d 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -458,7 +458,7 @@ export const LogTypeData = z.object({ [LogType.AUTOMOD_ACTION]: z.object({ rule: z.string(), - user: z.instanceof(TemplateSafeUser), + user: z.instanceof(TemplateSafeUser).nullable(), users: z.array(z.instanceof(TemplateSafeUser)), actionsTaken: z.string(), matchSummary: z.string(), From 16747521beaa0d1a384248125f23896c1e9a3d62 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 21 Aug 2021 01:42:07 +0300 Subject: [PATCH 110/191] Remove redundant awaits --- backend/src/plugins/Utility/commands/JumboCmd.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index c35e1367..e9ee63b5 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -64,7 +64,7 @@ export const JumboCmd = utilityCmd({ } url += `${results[2]}${extension}`; if (extension === ".png") { - const image = await resizeBuffer(await getBufferFromUrl(url), size, size); + const image = resizeBuffer(await getBufferFromUrl(url), size, size); file = new MessageAttachment(image, `emoji${extension}`); } else { const image = await getBufferFromUrl(url); @@ -74,7 +74,7 @@ export const JumboCmd = utilityCmd({ let url = CDN_URL + `/${twemoji.convert.toCodePoint(args.emoji)}.svg`; let image: Buffer | undefined; try { - image = await resizeBuffer(await getBufferFromUrl(url), size, size); + image = resizeBuffer(await getBufferFromUrl(url), size, size); } catch { if (url.toLocaleLowerCase().endsWith("fe0f.svg")) { url = url.slice(0, url.lastIndexOf("-fe0f")) + ".svg"; From 20159edd7fa1aa831cd06f81d7492b300cf0cdc6 Mon Sep 17 00:00:00 2001 From: almeidx Date: Sat, 21 Aug 2021 14:16:40 +0100 Subject: [PATCH 111/191] fix: automod on guildMemberUpdate --- .../plugins/Automod/events/RunAutomodOnMemberUpdate.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts index 611aadde..a3636118 100644 --- a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts +++ b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts @@ -9,10 +9,13 @@ export const RunAutomodOnMemberUpdate = typedGuildEventListener Date: Sat, 4 Sep 2021 09:37:58 +0000 Subject: [PATCH 112/191] check if "channel" exists --- .../Utility/functions/getInviteInfoEmbed.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts index a95de14b..4a7215ed 100644 --- a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts @@ -65,32 +65,33 @@ export async function getInviteInfoEmbed( `), inline: true, }); + if (invite.channel) { + const channelName = + invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`; - const channelName = - invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`; + const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id); + const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x"); + const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, { + largest: 2, + round: true, + }); - const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id); - const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x"); - const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, { - largest: 2, - round: true, - }); - - let channelInfo = trimLines(` + let channelInfo = trimLines(` Name: **${channelName}** ID: \`${invite.channel.id}\` Created: **${channelAge} ago** `); - if (invite.channel.type !== ChannelTypeStrings.VOICE) { - channelInfo += `\nMention: <#${invite.channel.id}>`; - } + if (invite.channel.type !== ChannelTypeStrings.VOICE) { + channelInfo += `\nMention: <#${invite.channel.id}>`; + } - embed.fields.push({ - name: preEmbedPadding + "Channel information", - value: channelInfo, - inline: true, - }); + embed.fields.push({ + name: preEmbedPadding + "Channel information", + value: channelInfo, + inline: true, + }); + } if (invite.inviter) { embed.fields.push({ From 5ae5e7bc1da51fb0a9c2a45074f82cdc23422f0d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 19:02:14 +0300 Subject: [PATCH 113/191] Upgrade to Knub v30.0.0-beta.43 --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index ab298c68..e1970c3f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.42", + "knub": "^30.0.0-beta.43", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3043,9 +3043,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.42", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz", - "integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==", + "version": "30.0.0-beta.43", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.43.tgz", + "integrity": "sha512-Cmmy2+vnIWLoQhEGhXMykdJoBnB4ofli1HLWOeYCK4OuZrASh6hFvkbliLUuMet3yaHLFQkmK5Gw06ndw67OcA==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8290,9 +8290,9 @@ } }, "knub": { - "version": "30.0.0-beta.42", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.42.tgz", - "integrity": "sha512-y7nqQh1bzQniYwEftdv6S8Jp2qBvT5a7vn+3JeA0s0ADXobI+/rRVznpq8o0x2m0+E+EeKxo1Ch8F8Hy+VMX6w==", + "version": "30.0.0-beta.43", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.43.tgz", + "integrity": "sha512-Cmmy2+vnIWLoQhEGhXMykdJoBnB4ofli1HLWOeYCK4OuZrASh6hFvkbliLUuMet3yaHLFQkmK5Gw06ndw67OcA==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index 5ef83427..5ae1c006 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.42", + "knub": "^30.0.0-beta.43", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", From 0d1bfefce0904da3160fd67de5183790da6b6721 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 19:05:47 +0300 Subject: [PATCH 114/191] Optimize/tweak message saving --- backend/src/data/GuildSavedMessages.ts | 3 -- .../MessageSaver/events/SaveMessagesEvts.ts | 35 +++++++++---------- backend/src/plugins/MessageSaver/types.ts | 1 - 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index 0e7db243..462df642 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -226,9 +226,6 @@ export class GuildSavedMessages extends BaseGuildRepository { } async createFromMsg(msg: Message, overrides = {}) { - const existingSavedMsg = await this.find(msg.id); - if (existingSavedMsg) return; - // FIXME: Hotfix if (!msg.channel) { return; diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 386549e6..42a6b982 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -29,17 +29,20 @@ export const MessageCreateEvt = messageSaverEvt({ return; } - meta.pluginData.state.queue.add(async () => { - if (recentlyCreatedMessages.includes(meta.args.message.id)) { - console.warn( - `Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`, - ); - return; - } - recentlyCreatedMessages.push(meta.args.message.id); + // Don't save the bot's own messages + if (meta.args.message.author.id === meta.pluginData.client.user?.id) { + return; + } - await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); - }); + if (recentlyCreatedMessages.includes(meta.args.message.id)) { + console.warn( + `Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`, + ); + return; + } + recentlyCreatedMessages.push(meta.args.message.id); + + await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, }); @@ -57,9 +60,7 @@ export const MessageUpdateEvt = messageSaverEvt({ return; } - meta.pluginData.state.queue.add(async () => { - await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); - }); + await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); }, }); @@ -74,9 +75,7 @@ export const MessageDeleteEvt = messageSaverEvt({ return; } - meta.pluginData.state.queue.add(async () => { - await meta.pluginData.state.savedMessages.markAsDeleted(msg.id); - }); + await meta.pluginData.state.savedMessages.markAsDeleted(msg.id); }, }); @@ -87,8 +86,6 @@ export const MessageDeleteBulkEvt = messageSaverEvt({ async listener(meta) { const ids = meta.args.messages.map(m => m.id); - meta.pluginData.state.queue.add(async () => { - await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); - }); + await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); }, }); diff --git a/backend/src/plugins/MessageSaver/types.ts b/backend/src/plugins/MessageSaver/types.ts index f8b481f7..28495da3 100644 --- a/backend/src/plugins/MessageSaver/types.ts +++ b/backend/src/plugins/MessageSaver/types.ts @@ -12,7 +12,6 @@ export interface MessageSaverPluginType extends BasePluginType { config: TConfigSchema; state: { savedMessages: GuildSavedMessages; - queue: Queue; }; } From b8b4e93dc14de58397f45a47c3097fd295c3c3ea Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 19:07:14 +0300 Subject: [PATCH 115/191] Fix user cache issue with welcome message pings --- .../plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index 750c6c29..772f27a9 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -68,7 +68,9 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ if (!channel || !(channel instanceof TextChannel)) return; try { - await createChunkedMessage(channel, formatted); + await createChunkedMessage(channel, formatted, { + parse: ["users"], + }); } catch { pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention( From 3385841702c2aa2e837b5eefc0b0a3e903c7cb3b Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:09:49 +0100 Subject: [PATCH 116/191] Persist fix (#263) --- backend/src/plugins/Persist/events/StoreDataEvt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Persist/events/StoreDataEvt.ts b/backend/src/plugins/Persist/events/StoreDataEvt.ts index aad0c48e..45dbda4b 100644 --- a/backend/src/plugins/Persist/events/StoreDataEvt.ts +++ b/backend/src/plugins/Persist/events/StoreDataEvt.ts @@ -16,7 +16,7 @@ export const StoreDataEvt = persistEvt({ const persistedRoles = config.persisted_roles; if (persistedRoles.length && member.roles) { - const rolesToPersist = intersection(persistedRoles, member.roles); + const rolesToPersist = intersection(persistedRoles, [...member.roles.cache.keys()]); if (rolesToPersist.length) { persist = true; persistData.roles = rolesToPersist; From f2f246ee84f60215c2cbd4e8f41d69c20f18c522 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:11:10 +0100 Subject: [PATCH 117/191] Custom Events fixes (#255) --- .../src/plugins/CustomEvents/CustomEventsPlugin.ts | 2 +- .../plugins/CustomEvents/actions/addRoleAction.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts index 009bf5a6..391ee191 100644 --- a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts +++ b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts @@ -51,7 +51,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin()( } const values = createTypedTemplateSafeValueContainer({ - ...args, + ...safeArgs, msg: messageToTemplateSafeMessage(message), }); diff --git a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts index 4fb21bf1..6b83770a 100644 --- a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts +++ b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts @@ -28,9 +28,11 @@ export async function addRoleAction( if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) { throw new ActionError("Missing permissions"); } - - const rolesToAdd = Array.isArray(action.role) ? action.role : [action.role]; - await target.edit({ - roles: Array.from(new Set([...target.roles.cache.values(), ...rolesToAdd])) as Snowflake[], - }); + const rolesToAdd = (Array.isArray(action.role) ? action.role : [action.role]).filter( + id => !target.roles.cache.has(id), + ); + if (rolesToAdd.length === 0) { + throw new ActionError("Target already has the role(s) specified"); + } + await target.roles.add(rolesToAdd); } From 6cddcb907404f9bd00e5decc88a45a0b33e8b323 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:12:37 +0100 Subject: [PATCH 118/191] Bot-specific for bot users in !userinfo (#260) --- .../plugins/Utility/functions/getUserInfoEmbed.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index dde541d7..d4e74637 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -36,7 +36,7 @@ export async function getUserInfoEmbed( const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); embed.author = { - name: `User: ${user.tag}`, + name: `${user.bot ? "Bot" : "User"}: ${user.tag}`, }; const avatarURL = user.displayAvatarURL(); @@ -54,7 +54,7 @@ export async function getUserInfoEmbed( if (compact) { embed.fields.push({ - name: preEmbedPadding + "User information", + name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`, value: trimLines(` Profile: <@!${user.id}> Created: **${accountAge} ago** (\`${prettyCreatedAt}\`) @@ -70,11 +70,12 @@ export async function getUserInfoEmbed( largest: 2, round: true, }); - embed.fields[0].value += `\nJoined: **${joinAge} ago** (\`${prettyJoinedAt}\`)`; + + embed.fields[0].value += `\n${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`)`; } else { embed.fields.push({ name: preEmbedPadding + "!! NOTE !!", - value: "User is not on the server", + value: `${user.bot ? "Bot" : "User"} is not on the server`, }); } @@ -82,7 +83,7 @@ export async function getUserInfoEmbed( } embed.fields.push({ - name: preEmbedPadding + "User information", + name: preEmbedPadding + `${user.bot ? "Bot" : "User"} information`, value: trimLines(` Name: **${user.tag}** ID: \`${user.id}\` @@ -107,7 +108,7 @@ export async function getUserInfoEmbed( embed.fields.push({ name: preEmbedPadding + "Member information", value: trimLines(` - Joined: **${joinAge} ago** (\`${prettyJoinedAt}\`) + ${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`) ${roles.length > 0 ? "Roles: " + roles.map(r => `<@&${r.id}>`).join(", ") : ""} `), }); @@ -126,7 +127,7 @@ export async function getUserInfoEmbed( } else { embed.fields.push({ name: preEmbedPadding + "Member information", - value: "⚠ User is not on the server", + value: `⚠ ${user.bot ? "Bot" : "User"} is not on the server`, }); } const cases = (await pluginData.state.cases.getByUserId(user.id)).filter(c => !c.is_hidden); From d5da50c0ed07a63506a9494d19079e8d90b8fa20 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:13:29 +0100 Subject: [PATCH 119/191] Fix customEvents channel perms overwrites (#253) --- .../actions/setChannelPermissionOverrides.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts index 6c345f05..6b9e9b1b 100644 --- a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts +++ b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts @@ -1,4 +1,4 @@ -import { Permissions, Snowflake, TextChannel } from "discord.js"; +import { Permissions, Snowflake, TextChannel, PermissionString } from "discord.js"; import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ActionError } from "../ActionError"; @@ -32,10 +32,17 @@ export async function setChannelPermissionOverridesAction( } for (const override of action.overrides) { - channel.permissionOverwrites.create( - override.id as Snowflake, - new Permissions(BigInt(override.allow)).remove(BigInt(override.deny)).serialize(), - ); + const allow = new Permissions(BigInt(override.allow)).serialize(); + const deny = new Permissions(BigInt(override.deny)).serialize(); + const perms: Partial> = {}; + for (const key in allow) { + if (allow[key]) { + perms[key] = true; + } else if (deny[key]) { + perms[key] = false; + } + } + channel.permissionOverwrites.create(override.id as Snowflake, perms); /* await channel.permissionOverwrites overwritePermissions( From e73d6d4e1c68d278397c2c9d812004b6da971d5b Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:14:33 +0100 Subject: [PATCH 120/191] Add template functions: strlen, round (#265) --- backend/src/templateFormatter.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/templateFormatter.ts b/backend/src/templateFormatter.ts index 7b61af1e..2ed902d9 100644 --- a/backend/src/templateFormatter.ts +++ b/backend/src/templateFormatter.ts @@ -388,6 +388,10 @@ const baseValues = { ucfirst(arg) { return baseValues.upperFirst(arg); }, + strlen(arg) { + if (typeof arg !== "string") return 0; + return [...arg].length; + }, rand(from, to, seed = null) { if (isNaN(from)) return 0; @@ -406,6 +410,10 @@ const baseValues = { return Math.round(randValue * (to - from) + from); }, + round(arg, decimals = 0) { + if (isNaN(arg)) return 0; + return decimals === 0 ? Math.round(arg) : arg.toFixed(decimals); + }, add(...args) { return args.reduce((result, arg) => { if (isNaN(arg)) return result; From 2d777a03dbed1e768a04c23a0cfa9a0d26adbee2 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:15:16 +0100 Subject: [PATCH 121/191] Logs - role change compare only role IDs instead of a collection (#264) --- .../plugins/Automod/events/RunAutomodOnMemberUpdate.ts | 4 ++-- backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts index a3636118..cf4563ca 100644 --- a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts +++ b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts @@ -14,8 +14,8 @@ export const RunAutomodOnMemberUpdate = typedGuildEventListener Date: Sat, 4 Sep 2021 17:16:44 +0100 Subject: [PATCH 122/191] Fix max stickers and premium tier in !server (#258) --- .../Utility/functions/getServerInfoEmbed.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index a0922399..91728e04 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -1,4 +1,4 @@ -import { MessageEmbedOptions, Snowflake } from "discord.js"; +import { MessageEmbedOptions, PremiumTier, Snowflake } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; @@ -19,6 +19,13 @@ import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { UtilityPluginType } from "../types"; import { getGuildPreview } from "./getGuildPreview"; +const PremiumTiers: Record = { + NONE: 0, + TIER_1: 1, + TIER_2: 2, + TIER_3: 3, +}; + export async function getServerInfoEmbed( pluginData: GuildPluginData, serverId: string, @@ -179,20 +186,22 @@ export async function getServerInfoEmbed( } if (restGuild) { + const premiumTierValue = PremiumTiers[restGuild.premiumTier]; + const maxEmojis = { 0: 50, 1: 100, 2: 150, 3: 250, - }[restGuild.premiumTier] || 50; + }[premiumTierValue] ?? 50; const maxStickers = { 0: 0, 1: 15, 2: 30, 3: 60, - }[restGuild.premiumTier] || 0; + }[premiumTierValue] ?? 0; otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`); otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`); @@ -202,7 +211,9 @@ export async function getServerInfoEmbed( } if (thisServer) { - otherStats.push(`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${thisServer.premiumTier})`); + otherStats.push( + `Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${PremiumTiers[thisServer.premiumTier]})`, + ); } embed.fields.push({ From 6a45ce67faf3bd599e9b2cbe528b7d7598b24556 Mon Sep 17 00:00:00 2001 From: Hiroyuki Date: Sat, 4 Sep 2021 12:18:33 -0400 Subject: [PATCH 123/191] feat(automod): MIME type trigger (#247) --- backend/src/plugins/Automod/AutomodPlugin.ts | 15 ++++ .../Automod/triggers/availableTriggers.ts | 3 + .../plugins/Automod/triggers/matchMimeType.ts | 79 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 backend/src/plugins/Automod/triggers/matchMimeType.ts diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 80d160e3..60f55296 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -114,6 +114,21 @@ const configPreprocessor: ConfigPreprocessorFn = options => { ]); } } + + if (triggerObj[triggerName].match_mime_type) { + const white = triggerObj[triggerName].match_mime_type.whitelist_enabled; + const black = triggerObj[triggerName].match_mime_type.blacklist_enabled; + + if (white && black) { + throw new StrictValidationError([ + `Cannot have both blacklist and whitelist enabled at rule <${rule.name}/match_mime_type>`, + ]); + } else if (!white && !black) { + throw new StrictValidationError([ + `Must have either blacklist or whitelist enabled at rule <${rule.name}/match_mime_type>`, + ]); + } + } } } } diff --git a/backend/src/plugins/Automod/triggers/availableTriggers.ts b/backend/src/plugins/Automod/triggers/availableTriggers.ts index e62e811d..22c63cd9 100644 --- a/backend/src/plugins/Automod/triggers/availableTriggers.ts +++ b/backend/src/plugins/Automod/triggers/availableTriggers.ts @@ -11,6 +11,7 @@ import { KickTrigger } from "./kick"; import { LineSpamTrigger } from "./lineSpam"; import { LinkSpamTrigger } from "./linkSpam"; import { MatchAttachmentTypeTrigger } from "./matchAttachmentType"; +import { MatchMimeTypeTrigger } from "./matchMimeType"; import { MatchInvitesTrigger } from "./matchInvites"; import { MatchLinksTrigger } from "./matchLinks"; import { MatchRegexTrigger } from "./matchRegex"; @@ -37,6 +38,7 @@ export const availableTriggers: Record match_invites: MatchInvitesTrigger, match_links: MatchLinksTrigger, match_attachment_type: MatchAttachmentTypeTrigger, + match_mime_type: MatchMimeTypeTrigger, member_join: MemberJoinTrigger, role_added: RoleAddedTrigger, role_removed: RoleRemovedTrigger, @@ -72,6 +74,7 @@ export const AvailableTriggers = t.type({ match_invites: MatchInvitesTrigger.configType, match_links: MatchLinksTrigger.configType, match_attachment_type: MatchAttachmentTypeTrigger.configType, + match_mime_type: MatchMimeTypeTrigger.configType, member_join: MemberJoinTrigger.configType, member_leave: MemberLeaveTrigger.configType, role_added: RoleAddedTrigger.configType, diff --git a/backend/src/plugins/Automod/triggers/matchMimeType.ts b/backend/src/plugins/Automod/triggers/matchMimeType.ts new file mode 100644 index 00000000..f38bb8fa --- /dev/null +++ b/backend/src/plugins/Automod/triggers/matchMimeType.ts @@ -0,0 +1,79 @@ +import { automodTrigger } from "../helpers"; +import * as t from "io-ts"; +import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils"; +import { GuildChannel, Util } from "discord.js"; + +interface MatchResultType { + matchedType: string; + mode: "blacklist" | "whitelist"; +} + +export const MatchMimeTypeTrigger = automodTrigger()({ + configType: t.type({ + mime_type_blacklist: t.array(t.string), + blacklist_enabled: t.boolean, + mime_type_whitelist: t.array(t.string), + whitelist_enabled: t.boolean, + }), + + defaultConfig: { + mime_type_blacklist: [], + blacklist_enabled: false, + mime_type_whitelist: [], + whitelist_enabled: false, + }, + + async match({ context, triggerConfig: trigger }) { + if (!context.message) return; + + const { attachments } = context.message.data; + if (!attachments) return null; + + for (const attachment of attachments) { + const { contentType } = attachment; + + const blacklist = trigger.blacklist_enabled + ? (trigger.mime_type_blacklist ?? []).map(_t => _t.toLowerCase()) + : null; + + if (contentType && blacklist?.includes(contentType)) { + return { + extra: { + matchedType: contentType, + mode: "blacklist", + }, + }; + } + + const whitelist = trigger.whitelist_enabled + ? (trigger.mime_type_whitelist ?? []).map(_t => _t.toLowerCase()) + : null; + + if (whitelist && (!contentType || !whitelist.includes(contentType))) { + return { + extra: { + matchedType: contentType || "", + mode: "whitelist", + }, + }; + } + + return null; + } + }, + + renderMatchInformation({ pluginData, contexts, matchResult }) { + const { message } = contexts[0]; + const channel = pluginData.guild.channels.resolve(message!.channel_id); + const prettyChannel = verboseChannelMention(channel as GuildChannel); + const { matchedType, mode } = matchResult.extra; + + return ( + asSingleLine(` + Matched MIME type \`${Util.escapeInlineCode(matchedType)}\` + (${mode === "blacklist" ? "blacklisted" : "not in whitelist"}) + in message (\`${message!.id}\`) in ${prettyChannel} + `) + messageSummary(message!) + ); + }, +}); From a19de26ff1e73ef0950612111ed26f633afeb098 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:20:50 +0100 Subject: [PATCH 124/191] Re-enable starboard + fixes (#262) --- .../Starboard/events/StarboardReactionAddEvt.ts | 3 --- .../events/StarboardReactionRemoveEvts.ts | 6 ------ .../util/removeMessageFromStarboard.ts | 17 +++++++++++++++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index 22c5761c..99dbbaa0 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -9,9 +9,6 @@ export const StarboardReactionAddEvt = starboardEvt({ event: "messageReactionAdd", async listener(meta) { - // FIXME: Temporarily disabled - return; - const pluginData = meta.pluginData; let msg = meta.args.reaction.message as Message; diff --git a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts index d2cc72da..203c4bde 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts @@ -5,9 +5,6 @@ export const StarboardReactionRemoveEvt = starboardEvt({ event: "messageReactionRemove", async listener(meta) { - // FIXME: Temporarily disabled - return; - const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteStarboardReaction( meta.args.reaction.message.id, @@ -21,9 +18,6 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({ event: "messageReactionRemoveAll", async listener(meta) { - // FIXME: Temporarily disabled - return; - const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id); boardLock.unlock(); diff --git a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts index 1a331358..f4635371 100644 --- a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts +++ b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts @@ -1,6 +1,19 @@ +import { GuildPluginData } from "knub"; import { StarboardMessage } from "../../../data/entities/StarboardMessage"; import { noop } from "../../../utils"; +import { StarboardPluginType } from "../types"; -export async function removeMessageFromStarboard(pluginData, msg: StarboardMessage) { - await pluginData.client.deleteMessage(msg.starboard_channel_id, msg.starboard_message_id).catch(noop); +export async function removeMessageFromStarboard( + pluginData: GuildPluginData, + msg: StarboardMessage, +) { + // fixes stuck entries on starboard_reactions table after messages being deleted, probably should add a cleanup script for this as well, i.e. DELETE FROM starboard_reactions WHERE message_id NOT IN (SELECT id FROM starboard_messages) + await pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(msg.message_id).catch(noop); + + // this code is now Almeida-certified and no longer ugly :ok_hand: :cake: + const channel = pluginData.client.channels.cache.find(c => c.id === msg.starboard_channel_id); + if (!channel?.isText()) return; + const message = await channel.messages.fetch(msg.starboard_message_id).catch(noop); + if (!message?.deletable) return; + await message.delete().catch(noop); } From 86143e7031ff609bdbcdaa280953aa7e64fe10ed Mon Sep 17 00:00:00 2001 From: Almeida Date: Sat, 4 Sep 2021 17:23:31 +0100 Subject: [PATCH 125/191] Don't match video embed descriptions (#256) --- .../Automod/functions/matchMultipleTextTypesOnMessage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts index 10188f7e..08492589 100644 --- a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts +++ b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts @@ -1,4 +1,4 @@ -import { Constants } from "discord.js"; +import { Constants, MessageEmbed } from "discord.js"; import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { resolveMember } from "../../../utils"; @@ -32,9 +32,9 @@ export async function* matchMultipleTextTypesOnMessage( yield ["message", msg.data.content]; } - if (trigger.match_embeds && msg.data.embeds && msg.data.embeds.length) { - const copiedEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0])); - if (copiedEmbed.type === "video") { + if (trigger.match_embeds && msg.data.embeds?.length) { + const copiedEmbed: MessageEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0])); + if (copiedEmbed.video) { copiedEmbed.description = ""; // The description is not rendered, hence it doesn't need to be matched } yield ["embed", JSON.stringify(copiedEmbed)]; From 60515ad7887809f0335a319662ed1f7adbcb0e1c Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 4 Sep 2021 17:36:15 +0100 Subject: [PATCH 126/191] Update invite detection regex (#259) --- backend/src/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 8a7f7c55..6e40d666 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -743,10 +743,11 @@ export function isNotNull(value): value is Exclude { // discordapp.com/invite/ // discord.gg/invite/ // discord.gg/ -const quickInviteDetection = /(?:discord.com|discordapp.com)\/invite\/([a-z0-9\-]+)|discord.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi; +// discord.com/friend-invite/ +const quickInviteDetection = /discord(?:app)?\.com\/(?:friend-)?invite\/([a-z0-9\-]+)|discord\.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi; const isInviteHostRegex = /(?:^|\.)(?:discord.gg|discord.com|discordapp.com)$/i; -const longInvitePathRegex = /^\/invite\/([a-z0-9\-]+)$/i; +const longInvitePathRegex = /^\/(?:friend-)?invite\/([a-z0-9\-]+)$/i; export function getInviteCodesInString(str: string): string[] { const inviteCodes: string[] = []; @@ -778,6 +779,8 @@ export function getInviteCodesInString(str: string): string[] { // discord.com/invite/[/anything] // discordapp.com/invite/[/anything] + // discord.com/friend-invite/[/anything] + // discordapp.com/friend-invite/[/anything] const longInviteMatch = url.pathname.match(longInvitePathRegex); if (longInviteMatch) { return longInviteMatch[1]; From 98f7c27dd4b8b857404aa578679d94900b4e1c1f Mon Sep 17 00:00:00 2001 From: Almeida Date: Sat, 4 Sep 2021 17:37:02 +0100 Subject: [PATCH 127/191] Add use_inline_reply option to the Automod reply action (#269) --- backend/src/plugins/Automod/actions/reply.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index d40681e3..70527f0b 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -1,7 +1,6 @@ import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js"; import * as t from "io-ts"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; -import { LogType } from "../../../data/LogType"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { convertDelayStringToMS, @@ -25,6 +24,7 @@ export const ReplyAction = automodAction({ t.type({ text: tMessageContent, auto_delete: tNullable(t.union([tDelayString, t.number])), + use_inline_reply: tNullable(t.boolean), }), ]), @@ -51,7 +51,7 @@ export const ReplyAction = automodAction({ const users = unique(Array.from(new Set(_contexts.map(c => c.user).filter(Boolean)))) as User[]; const user = users[0]; - const renderReplyText = async str => + const renderReplyText = async (str: string) => renderTemplate( str, new TemplateSafeValueContainer({ @@ -94,16 +94,26 @@ export const ReplyAction = automodAction({ } const messageContent = validateAndParseMessageContent(formatted); - const replyMsg = await channel.send({ + + const messageOpts: MessageOptions = { ...messageContent, allowedMentions: { users: [user.id], }, - }); + }; + + if (typeof actionConfig !== "string" && actionConfig.use_inline_reply) { + messageOpts.reply = { + failIfNotExists: false, + messageReference: _contexts[0].message!.id, + }; + } + + const replyMsg = await channel.send(messageOpts); if (typeof actionConfig === "object" && actionConfig.auto_delete) { const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!; - setTimeout(() => replyMsg.delete().catch(noop), delay); + setTimeout(() => !replyMsg.deleted && replyMsg.delete().catch(noop), delay); } } } From b3e2e0cffb90f2789ebed6ee0dff06f71436fa19 Mon Sep 17 00:00:00 2001 From: Almeida Date: Sat, 4 Sep 2021 17:37:49 +0100 Subject: [PATCH 128/191] Add archive_thread action to automod (#271) --- .../plugins/Automod/actions/archiveThread.ts | 20 +++++++++++++++++++ .../Automod/actions/availableActions.ts | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 backend/src/plugins/Automod/actions/archiveThread.ts diff --git a/backend/src/plugins/Automod/actions/archiveThread.ts b/backend/src/plugins/Automod/actions/archiveThread.ts new file mode 100644 index 00000000..7c7aa218 --- /dev/null +++ b/backend/src/plugins/Automod/actions/archiveThread.ts @@ -0,0 +1,20 @@ +import { ThreadChannel } from "discord.js"; +import * as t from "io-ts"; +import { noop } from "../../../utils"; +import { automodAction } from "../helpers"; + +export const ArchiveThreadAction = automodAction({ + configType: t.type({}), + defaultConfig: {}, + + async apply({ pluginData, contexts }) { + const threads = contexts + .filter(c => c.message?.channel_id) + .map(c => pluginData.guild.channels.cache.get(c.message!.channel_id)) + .filter((c): c is ThreadChannel => c?.isThread() ?? false); + + for (const thread of threads) { + await thread.setArchived().catch(noop); + } + }, +}); diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index fbdcf8f9..76b2a60e 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -3,6 +3,7 @@ import { AutomodActionBlueprint } from "../helpers"; import { AddRolesAction } from "./addRoles"; import { AddToCounterAction } from "./addToCounter"; import { AlertAction } from "./alert"; +import { ArchiveThreadAction } from "./archiveThread"; import { BanAction } from "./ban"; import { ChangeNicknameAction } from "./changeNickname"; import { CleanAction } from "./clean"; @@ -32,6 +33,7 @@ export const availableActions: Record> = { add_to_counter: AddToCounterAction, set_counter: SetCounterAction, set_slowmode: SetSlowmodeAction, + archive_thread: ArchiveThreadAction, }; export const AvailableActions = t.type({ @@ -50,4 +52,5 @@ export const AvailableActions = t.type({ add_to_counter: AddToCounterAction.configType, set_counter: SetCounterAction.configType, set_slowmode: SetSlowmodeAction.configType, + archive_thread: ArchiveThreadAction.configType, }); From 6e5dd2c31f13bf0f0a80681060d31372bb4464c3 Mon Sep 17 00:00:00 2001 From: Jonathan <54381371+axisiscool@users.noreply.github.com> Date: Sat, 4 Sep 2021 12:45:10 -0400 Subject: [PATCH 129/191] Fix typo in !removerole (#228) --- backend/src/plugins/Roles/commands/RemoveRoleCmd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index d6e36356..63a843c8 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -63,7 +63,7 @@ export const RemoveRoleCmd = rolesCmd({ sendSuccessMessage( pluginData, msg.channel, - `Removed role **${role.name}** removed from ${verboseUserMention(args.member.user)}!`, + `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`, ); }, }); From 200e8ba89d2f2d6e6d7d0310dafeb4d132f26297 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 19:49:04 +0300 Subject: [PATCH 130/191] Remove leftover piece of code --- backend/src/plugins/MessageSaver/MessageSaverPlugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 0f316ad2..c8a603e1 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -45,6 +45,5 @@ export const MessageSaverPlugin = zeppelinGuildPlugin()( beforeLoad(pluginData) { const { state, guild } = pluginData; state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); - state.queue = new Queue(); }, }); From 6486dd7ca817802cbe3327fedac5498037268319 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 19:57:59 +0300 Subject: [PATCH 131/191] Update update scripts and process files --- process-api.json | 3 ++- process-bot.json | 3 ++- update-backend-hotfix.sh | 16 ++++++++++++++++ update-backend.sh | 25 +++++++++++++++++++++++++ update-dashboard.sh | 15 +++++++++++++++ update.sh | 11 ++--------- 6 files changed, 62 insertions(+), 11 deletions(-) create mode 100755 update-backend-hotfix.sh create mode 100755 update-backend.sh create mode 100755 update-dashboard.sh diff --git a/process-api.json b/process-api.json index 99b5a8a3..2feaf75f 100644 --- a/process-api.json +++ b/process-api.json @@ -5,7 +5,8 @@ "cwd": "./backend", "script": "npm", "args": "run start-api-prod", - "log_date_format": "YYYY-MM-DD HH:mm:ss" + "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS", + "exp_backoff_restart_delay": 2500 } ] } diff --git a/process-bot.json b/process-bot.json index 9c552b90..b8667e2d 100644 --- a/process-bot.json +++ b/process-bot.json @@ -5,7 +5,8 @@ "cwd": "./backend", "script": "npm", "args": "run start-bot-prod", - "log_date_format": "YYYY-MM-DD HH:mm:ss" + "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS", + "exp_backoff_restart_delay": 2500 } ] } diff --git a/update-backend-hotfix.sh b/update-backend-hotfix.sh new file mode 100755 index 00000000..e692d7a9 --- /dev/null +++ b/update-backend-hotfix.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Load nvm +. ~/.nvm/nvm.sh + +# Run hotfix update +cd backend +nvm use +git pull +npm run build + +# Restart processes +cd .. +nvm use +pm2 restart process-bot.json +pm2 restart process-api.json diff --git a/update-backend.sh b/update-backend.sh new file mode 100755 index 00000000..4da51186 --- /dev/null +++ b/update-backend.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Load nvm +. ~/.nvm/nvm.sh + +# Stop current processes +nvm use +pm2 delete process-bot.json +pm2 delete process-api.json + +# Run update +nvm use +git pull +npm ci + +cd backend +npm ci +npm run build +npm run migrate-prod + +# Start processes again +cd .. +nvm use +pm2 start process-bot.json +pm2 start process-api.json diff --git a/update-dashboard.sh b/update-dashboard.sh new file mode 100755 index 00000000..7a336905 --- /dev/null +++ b/update-dashboard.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +TARGET_DIR=/var/www/zeppelin.gg + +# Load nvm +. ~/.nvm/nvm.sh + +# Update dashboard +cd dashboard +git pull +nvm use +npm ci +npm run build +rm -r "$TARGET_DIR/*" +cp -R dist/* "$TARGET_DIR" diff --git a/update.sh b/update.sh index 5e910c60..bf615aa5 100755 --- a/update.sh +++ b/update.sh @@ -1,11 +1,4 @@ #!/bin/bash -# Load nvm -. ~/.nvm/nvm.sh - -# Run update -nvm use -git pull -npm ci -npm run build -pm2 restart process.json +. ./update-backend.sh +. ./update-dashboard.sh From a396779cd5b0c7bd6d8bb153c8a99be696a1bafa Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:16:44 +0300 Subject: [PATCH 132/191] Add debug code for duplicate message saving --- .../MessageSaver/MessageSaverPlugin.ts | 3 +++ .../MessageSaver/events/SaveMessagesEvts.ts | 27 ++++++++++++------- backend/src/plugins/MessageSaver/types.ts | 1 + 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index c8a603e1..724f0a15 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -21,6 +21,8 @@ const defaultOptions: PluginOptions = { ], }; +let debugId = 0; + export const MessageSaverPlugin = zeppelinGuildPlugin()({ name: "message_saver", showInDocs: false, @@ -45,5 +47,6 @@ export const MessageSaverPlugin = zeppelinGuildPlugin()( beforeLoad(pluginData) { const { state, guild } = pluginData; state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); + state.debugId = ++debugId; }, }); diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 42a6b982..a2549820 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -1,14 +1,21 @@ import { Constants, Message, MessageType, Snowflake } from "discord.js"; import { messageSaverEvt } from "../types"; import { SECONDS } from "../../../utils"; +import moment from "moment-timezone"; -const recentlyCreatedMessages: Snowflake[] = []; +const recentlyCreatedMessages: Map = new Map(); const recentlyCreatedMessagesToKeep = 100; setInterval(() => { - const toDelete = recentlyCreatedMessages.length - recentlyCreatedMessagesToKeep; - if (toDelete > 0) { - recentlyCreatedMessages.splice(0, toDelete); + let toDelete = recentlyCreatedMessages.size - recentlyCreatedMessagesToKeep; + for (const key of recentlyCreatedMessages.keys()) { + if (toDelete === 0) { + break; + } + + recentlyCreatedMessages.delete(key); + + toDelete--; } }, 60 * SECONDS); @@ -34,13 +41,15 @@ export const MessageCreateEvt = messageSaverEvt({ return; } - if (recentlyCreatedMessages.includes(meta.args.message.id)) { - console.warn( - `Tried to save duplicate message from messageCreate event: ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`, - ); + // FIXME: Remove debug code + if (recentlyCreatedMessages.has(meta.args.message.id)) { + const context = `${meta.pluginData.state.debugId} / ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; + const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)!).format("HH:mm:ss.SSS"); + // tslint:disable-next-line:no-console + console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`); return; } - recentlyCreatedMessages.push(meta.args.message.id); + recentlyCreatedMessages.set(meta.args.message.id, Date.now()); await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, diff --git a/backend/src/plugins/MessageSaver/types.ts b/backend/src/plugins/MessageSaver/types.ts index 28495da3..694a0b0a 100644 --- a/backend/src/plugins/MessageSaver/types.ts +++ b/backend/src/plugins/MessageSaver/types.ts @@ -12,6 +12,7 @@ export interface MessageSaverPluginType extends BasePluginType { config: TConfigSchema; state: { savedMessages: GuildSavedMessages; + debugId: number; }; } From 633cf70f09144f870111ebd13f1f9fdadcee668e Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:24:30 +0300 Subject: [PATCH 133/191] Tweaks to debug code --- .../plugins/MessageSaver/events/SaveMessagesEvts.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index a2549820..219607d7 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -3,7 +3,7 @@ import { messageSaverEvt } from "../types"; import { SECONDS } from "../../../utils"; import moment from "moment-timezone"; -const recentlyCreatedMessages: Map = new Map(); +const recentlyCreatedMessages: Map = new Map(); const recentlyCreatedMessagesToKeep = 100; setInterval(() => { @@ -43,13 +43,15 @@ export const MessageCreateEvt = messageSaverEvt({ // FIXME: Remove debug code if (recentlyCreatedMessages.has(meta.args.message.id)) { - const context = `${meta.pluginData.state.debugId} / ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; - const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)!).format("HH:mm:ss.SSS"); + const ourDebugId = meta.pluginData.state.debugId; + const oldDebugId = recentlyCreatedMessages.get(meta.args.message.id)![0]; + const context = `${ourDebugId} : ${oldDebugId} / ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; + const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)![1]).format("HH:mm:ss.SSS"); // tslint:disable-next-line:no-console console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`); return; } - recentlyCreatedMessages.set(meta.args.message.id, Date.now()); + recentlyCreatedMessages.set(meta.args.message.id, [meta.pluginData.state.debugId, Date.now()]); await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, From 8d84e8cb14001aeafa1b6a38cc3d09569731e40b Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:29:30 +0300 Subject: [PATCH 134/191] One more debug code tweak --- backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 219607d7..56b7e680 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -45,7 +45,7 @@ export const MessageCreateEvt = messageSaverEvt({ if (recentlyCreatedMessages.has(meta.args.message.id)) { const ourDebugId = meta.pluginData.state.debugId; const oldDebugId = recentlyCreatedMessages.get(meta.args.message.id)![0]; - const context = `${ourDebugId} : ${oldDebugId} / ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; + const context = `${ourDebugId} : ${oldDebugId} / ${meta.pluginData.guild.id} : ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)![1]).format("HH:mm:ss.SSS"); // tslint:disable-next-line:no-console console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`); From e5fd91eac9afe2f900eb68389f8f35ffd70c2e60 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:49:05 +0300 Subject: [PATCH 135/191] More debug --- .../src/plugins/MessageSaver/MessageSaverPlugin.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 724f0a15..3d8bc9e4 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -22,6 +22,7 @@ const defaultOptions: PluginOptions = { }; let debugId = 0; +const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"]; export const MessageSaverPlugin = zeppelinGuildPlugin()({ name: "message_saver", @@ -48,5 +49,15 @@ export const MessageSaverPlugin = zeppelinGuildPlugin()( const { state, guild } = pluginData; state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.debugId = ++debugId; + + if (debugGuilds.includes(pluginData.guild.id)) { + console.log(`MessageSaverPlugin::beforeLoad (${state.debugId}): ${pluginData.guild.id}`); + } + }, + + beforeUnload(pluginData) { + if (debugGuilds.includes(pluginData.guild.id)) { + console.log(`MessageSaverPlugin::beforeUnload (${pluginData.state.debugId}): ${pluginData.guild.id}`); + } }, }); From 2190523866f8b067d136732169f3d299f9794f53 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:49:25 +0300 Subject: [PATCH 136/191] Fix missing await --- .../ReactionRoles/util/applyReactionRoleReactionsToMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 86c81a29..0c6334e9 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -25,7 +25,7 @@ export async function applyReactionRoleReactionsToMessage( let targetMessage; try { - targetMessage = channel.messages.fetch(messageId, { force: true }); + targetMessage = await channel.messages.fetch(messageId, { force: true }); } catch (e) { if (isDiscordAPIError(e)) { if (e.code === 10008) { From b550ce0c8e12db80d148c02566f719ea770de6d2 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:53:32 +0300 Subject: [PATCH 137/191] More+ debug --- .../src/plugins/MessageSaver/events/SaveMessagesEvts.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 56b7e680..3e1e91a6 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -3,7 +3,7 @@ import { messageSaverEvt } from "../types"; import { SECONDS } from "../../../utils"; import moment from "moment-timezone"; -const recentlyCreatedMessages: Map = new Map(); +const recentlyCreatedMessages: Map = new Map(); const recentlyCreatedMessagesToKeep = 100; setInterval(() => { @@ -45,13 +45,14 @@ export const MessageCreateEvt = messageSaverEvt({ if (recentlyCreatedMessages.has(meta.args.message.id)) { const ourDebugId = meta.pluginData.state.debugId; const oldDebugId = recentlyCreatedMessages.get(meta.args.message.id)![0]; - const context = `${ourDebugId} : ${oldDebugId} / ${meta.pluginData.guild.id} : ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; + const oldGuildId = recentlyCreatedMessages.get(meta.args.message.id)![2]; + const context = `${ourDebugId} : ${oldDebugId} / ${meta.pluginData.guild.id} : ${oldGuildId} : ${meta.args.message.guildId} / ${meta.args.message.channelId} / ${meta.args.message.id}`; const timestamp = moment(recentlyCreatedMessages.get(meta.args.message.id)![1]).format("HH:mm:ss.SSS"); // tslint:disable-next-line:no-console console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`); return; } - recentlyCreatedMessages.set(meta.args.message.id, [meta.pluginData.state.debugId, Date.now()]); + recentlyCreatedMessages.set(meta.args.message.id, [meta.pluginData.state.debugId, Date.now(), meta.pluginData.guild.id]); await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, From 5e13938f60a5b64c9234d7938d666c1345183688 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 21:02:55 +0300 Subject: [PATCH 138/191] More++ debug --- backend/src/index.ts | 16 ++++++++++++++++ .../plugins/MessageSaver/MessageSaverPlugin.ts | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 2ab3417c..108a3cbe 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -300,6 +300,22 @@ connect().then(async () => { startUptimeCounter(); }); + const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"]; + bot.on("guildLoaded", guildId => { + if (!debugGuilds.includes(guildId)) { + return; + } + + console.log(`[!! DEBUG !!] LOADED GUILD ${guildId}`); + }); + bot.on("guildUnloaded", guildId => { + if (!debugGuilds.includes(guildId)) { + return; + } + + console.log(`[!! DEBUG !!] UNLOADED GUILD ${guildId}`); + }); + bot.initialize(); logger.info("Bot Initialized"); logger.info("Logging in..."); diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 3d8bc9e4..202f03dd 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -51,13 +51,15 @@ export const MessageSaverPlugin = zeppelinGuildPlugin()( state.debugId = ++debugId; if (debugGuilds.includes(pluginData.guild.id)) { - console.log(`MessageSaverPlugin::beforeLoad (${state.debugId}): ${pluginData.guild.id}`); + console.log(`[!! DEBUG !!] MessageSaverPlugin::beforeLoad (${state.debugId}): ${pluginData.guild.id}`); } }, beforeUnload(pluginData) { if (debugGuilds.includes(pluginData.guild.id)) { - console.log(`MessageSaverPlugin::beforeUnload (${pluginData.state.debugId}): ${pluginData.guild.id}`); + console.log( + `[!! DEBUG !!] MessageSaverPlugin::beforeUnload (${pluginData.state.debugId}): ${pluginData.guild.id}`, + ); } }, }); From 0b6e61bf25db59c6341a40b307a17ff1d7783c22 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 21:23:53 +0300 Subject: [PATCH 139/191] Upgrade to Knub v30.0.0-beta.44 --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index e1970c3f..8ed1eff1 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.43", + "knub": "^30.0.0-beta.44", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3043,9 +3043,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.43", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.43.tgz", - "integrity": "sha512-Cmmy2+vnIWLoQhEGhXMykdJoBnB4ofli1HLWOeYCK4OuZrASh6hFvkbliLUuMet3yaHLFQkmK5Gw06ndw67OcA==", + "version": "30.0.0-beta.44", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.44.tgz", + "integrity": "sha512-VkddBzz43A2ZBEGI0V8YjPuRzzbMxZ9TAyswYm+lwsnMTVygvn2E/cWVz6BEWcbUj0rU/uCEUHsMD4M8NfXVqg==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8290,9 +8290,9 @@ } }, "knub": { - "version": "30.0.0-beta.43", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.43.tgz", - "integrity": "sha512-Cmmy2+vnIWLoQhEGhXMykdJoBnB4ofli1HLWOeYCK4OuZrASh6hFvkbliLUuMet3yaHLFQkmK5Gw06ndw67OcA==", + "version": "30.0.0-beta.44", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.44.tgz", + "integrity": "sha512-VkddBzz43A2ZBEGI0V8YjPuRzzbMxZ9TAyswYm+lwsnMTVygvn2E/cWVz6BEWcbUj0rU/uCEUHsMD4M8NfXVqg==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index 5ae1c006..74f02a5c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.43", + "knub": "^30.0.0-beta.44", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", From e95987a766a0bdb78555767982595b417543b605 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 22:11:08 +0300 Subject: [PATCH 140/191] Fix rare crash if isOwner() is called before the global config is loaded --- backend/src/pluginUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 14dcf85e..309914f1 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -232,7 +232,7 @@ export function getBaseUrl(pluginData: AnyPluginData) { export function isOwner(pluginData: AnyPluginData, userId: string) { const knub = pluginData.getKnubInstance() as TZeppelinKnub; - const owners = knub.getGlobalConfig().owners; + const owners = knub.getGlobalConfig()?.owners; if (!owners) { return false; } From 05c2434efcaa1772bbfa677f8ce3f5d6a2c807a6 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 22:11:38 +0300 Subject: [PATCH 141/191] Add command to monitor plugin load performance --- .../plugins/BotControl/BotControlPlugin.ts | 3 +++ .../BotControl/commands/PerformanceCmd.ts | 23 +++++++++++++++++++ backend/src/plugins/BotControl/types.ts | 1 + 3 files changed, 27 insertions(+) create mode 100644 backend/src/plugins/BotControl/commands/PerformanceCmd.ts diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index dd53d72c..6770b8ef 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -18,11 +18,13 @@ import { ReloadServerCmd } from "./commands/ReloadServerCmd"; import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; import { ServersCmd } from "./commands/ServersCmd"; import { BotControlPluginType, ConfigSchema } from "./types"; +import { PerformanceCmd } from "./commands/PerformanceCmd"; const defaultOptions = { config: { can_use: false, can_eligible: false, + can_performance: false, update_cmd: null, }, }; @@ -45,6 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ ListDashboardUsersCmd, ListDashboardPermsCmd, EligibleCmd, + PerformanceCmd, ], async afterLoad(pluginData) { diff --git a/backend/src/plugins/BotControl/commands/PerformanceCmd.ts b/backend/src/plugins/BotControl/commands/PerformanceCmd.ts new file mode 100644 index 00000000..6e4c857a --- /dev/null +++ b/backend/src/plugins/BotControl/commands/PerformanceCmd.ts @@ -0,0 +1,23 @@ +import { TextChannel } from "discord.js"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { createChunkedMessage, formatNumber, resolveInvite, sorter, verboseUserMention } from "../../../utils"; +import { botControlCmd } from "../types"; + +export const PerformanceCmd = botControlCmd({ + trigger: ["performance"], + permission: "can_performance", + + signature: {}, + + async run({ pluginData, message: msg, args }) { + const stats = pluginData.getKnubInstance().getPluginPerformanceStats(); + const averageLoadTimeEntries = Object.entries(stats.averageLoadTimes); + averageLoadTimeEntries.sort(sorter(v => v[1].time, "DESC")); + const lines = averageLoadTimeEntries.map( + ([pluginName, { time }]) => `${pluginName}: **${formatNumber(Math.round(time))}ms**`, + ); + const fullStats = `Average plugin load times:\n\n${lines.join("\n")}`; + createChunkedMessage(msg.channel as TextChannel, fullStats); + }, +}); diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index 1acac3a4..44e57cf8 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -9,6 +9,7 @@ import { tNullable } from "../../utils"; export const ConfigSchema = t.type({ can_use: t.boolean, can_eligible: t.boolean, + can_performance: t.boolean, update_cmd: tNullable(t.string), }); export type TConfigSchema = t.TypeOf; From 2b8f75b91b6cffe984d6b6309b07d15cf135eec0 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 4 Sep 2021 22:15:27 +0300 Subject: [PATCH 142/191] Upgrade to Knub v30.0.0-beta.45 --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 8ed1eff1..79e1da28 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.44", + "knub": "^30.0.0-beta.45", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", @@ -3043,9 +3043,9 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.44", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.44.tgz", - "integrity": "sha512-VkddBzz43A2ZBEGI0V8YjPuRzzbMxZ9TAyswYm+lwsnMTVygvn2E/cWVz6BEWcbUj0rU/uCEUHsMD4M8NfXVqg==", + "version": "30.0.0-beta.45", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz", + "integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==", "dependencies": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", @@ -8290,9 +8290,9 @@ } }, "knub": { - "version": "30.0.0-beta.44", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.44.tgz", - "integrity": "sha512-VkddBzz43A2ZBEGI0V8YjPuRzzbMxZ9TAyswYm+lwsnMTVygvn2E/cWVz6BEWcbUj0rU/uCEUHsMD4M8NfXVqg==", + "version": "30.0.0-beta.45", + "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.45.tgz", + "integrity": "sha512-r1jtHBYthOn8zjgyILh418/Qnw8f/cUMzz5aky7+T5HLFV0BAiBzeg5TOb0UFMkn8ewIPSy8GTG1x/CIAv3s8Q==", "requires": { "discord-api-types": "^0.22.0", "discord.js": "^13.0.1", diff --git a/backend/package.json b/backend/package.json index 74f02a5c..685637b7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,7 @@ "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.44", + "knub": "^30.0.0-beta.45", "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", From 947a49761e4b6ab2aba121814a7e749f268d3243 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 13:53:46 +0300 Subject: [PATCH 143/191] Add support for API permission expiry --- backend/src/api/start.ts | 3 +++ backend/src/api/tasks.ts | 10 ++++++++++ backend/src/data/ApiPermissionAssignments.ts | 8 ++++++++ .../data/entities/ApiPermissionAssignment.ts | 3 +++ ...30837386329-AddExpiresAtToApiPermissions.ts | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 backend/src/api/tasks.ts create mode 100644 backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts diff --git a/backend/src/api/start.ts b/backend/src/api/start.ts index 7d046c12..b4eed63b 100644 --- a/backend/src/api/start.ts +++ b/backend/src/api/start.ts @@ -6,6 +6,7 @@ import { initAuth } from "./auth"; import { initDocs } from "./docs"; import { initGuildsAPI } from "./guilds"; import { clientError, error, notFound } from "./responses"; +import { startBackgroundTasks } from "./tasks"; const app = express(); @@ -47,3 +48,5 @@ app.use((req, res, next) => { const port = (process.env.PORT && parseInt(process.env.PORT, 10)) || 3000; app.listen(port, "0.0.0.0", () => console.log(`API server listening on port ${port}`)); // tslint:disable-line + +startBackgroundTasks(); diff --git a/backend/src/api/tasks.ts b/backend/src/api/tasks.ts new file mode 100644 index 00000000..6edf218d --- /dev/null +++ b/backend/src/api/tasks.ts @@ -0,0 +1,10 @@ +import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; +import { MINUTES } from "../utils"; + +export function startBackgroundTasks() { + // Clear expired API permissions every minute + const apiPermissions = new ApiPermissionAssignments(); + setInterval(() => { + apiPermissions.clearExpiredPermissions(); + }, 1 * MINUTES); +} diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index 29686adc..fc04b61c 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -55,4 +55,12 @@ export class ApiPermissionAssignments extends BaseRepository { removeUser(guildId, userId) { return this.apiPermissions.delete({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId }); } + + async clearExpiredPermissions() { + await this.apiPermissions + .createQueryBuilder() + .where("expires_at IS NOT NULL") + .andWhere("expires_at <= NOW()") + .delete(); + } } diff --git a/backend/src/data/entities/ApiPermissionAssignment.ts b/backend/src/data/entities/ApiPermissionAssignment.ts index fcea7595..454fe17c 100644 --- a/backend/src/data/entities/ApiPermissionAssignment.ts +++ b/backend/src/data/entities/ApiPermissionAssignment.ts @@ -18,6 +18,9 @@ export class ApiPermissionAssignment { @Column("simple-array") permissions: string[]; + @Column() + expires_at: string; + @ManyToOne( type => ApiUserInfo, userInfo => userInfo.permissionAssignments, diff --git a/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts b/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts new file mode 100644 index 00000000..3eb6ea27 --- /dev/null +++ b/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddExpiresAtToApiPermissions1630837386329 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumns("api_permissions", [ + new TableColumn({ + name: "expires_at", + type: "boolean", + isNullable: true, + default: null, + }), + ]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("api_permissions", "expires_at"); + } +} From ff648e7071809e7c826f690e6aecf4fa0262b4a4 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 13:58:08 +0300 Subject: [PATCH 144/191] Start work on API audit logs --- backend/src/data/ApiAuditLog.ts | 28 +++++++++ backend/src/data/apiAuditLogTypes.ts | 35 +++++++++++ backend/src/data/entities/ApiAuditLogEntry.ts | 25 ++++++++ .../1630837718830-CreateApiAuditLogTable.ts | 58 +++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 backend/src/data/ApiAuditLog.ts create mode 100644 backend/src/data/apiAuditLogTypes.ts create mode 100644 backend/src/data/entities/ApiAuditLogEntry.ts create mode 100644 backend/src/migrations/1630837718830-CreateApiAuditLogTable.ts diff --git a/backend/src/data/ApiAuditLog.ts b/backend/src/data/ApiAuditLog.ts new file mode 100644 index 00000000..199747ef --- /dev/null +++ b/backend/src/data/ApiAuditLog.ts @@ -0,0 +1,28 @@ +import { BaseRepository } from "./BaseRepository"; +import { getRepository, Repository } from "typeorm/index"; +import { ApiAuditLogEntry } from "./entities/ApiAuditLogEntry"; +import { ApiLogin } from "./entities/ApiLogin"; +import { AuditLogEventData, AuditLogEventType } from "./apiAuditLogTypes"; + +export class ApiAuditLog extends BaseRepository { + private auditLog: Repository>; + + constructor() { + super(); + this.auditLog = getRepository(ApiAuditLogEntry); + } + + addEntry( + guildId: string, + authorId: string, + eventType: TEventType, + eventData: AuditLogEventData[TEventType], + ) { + this.auditLog.insert({ + guild_id: guildId, + author_id: authorId, + event_type: eventType as any, + event_data: eventData as any, + }); + } +} diff --git a/backend/src/data/apiAuditLogTypes.ts b/backend/src/data/apiAuditLogTypes.ts new file mode 100644 index 00000000..c50cc06d --- /dev/null +++ b/backend/src/data/apiAuditLogTypes.ts @@ -0,0 +1,35 @@ +export const AuditLogEventTypes = { + ADD_API_PERMISSION: "ADD_API_PERMISSION", + REMOVE_API_PERMISSION: "REMOVE_API_PERMISSION", + EDIT_CONFIG: "EDIT_CONFIG", +}; + +export type AuditLogEventType = keyof typeof AuditLogEventTypes; + +export type AddApiPermissionEventData = { + target_id: string; + permissions: string[]; + expires_at: string | null; +}; + +export type RemoveApiPermissionEventData = { + target_id: string; +}; + +export type EditConfigEventData = {}; + +export interface AuditLogEventData extends Record { + ADD_API_PERMISSION: { + target_id: string; + permissions: string[]; + expires_at: string | null; + }; + + REMOVE_API_PERMISSION: { + target_id: string; + }; + + EDIT_CONFIG: {}; +} + +export type AnyAuditLogEventData = AuditLogEventData[AuditLogEventType]; diff --git a/backend/src/data/entities/ApiAuditLogEntry.ts b/backend/src/data/entities/ApiAuditLogEntry.ts new file mode 100644 index 00000000..9c613353 --- /dev/null +++ b/backend/src/data/entities/ApiAuditLogEntry.ts @@ -0,0 +1,25 @@ +import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm"; +import { ApiUserInfo } from "./ApiUserInfo"; +import { AuditLogEventData, AuditLogEventType } from "../apiAuditLogTypes"; + +@Entity("api_audit_log") +export class ApiAuditLogEntry { + @Column() + @PrimaryColumn() + id: number; + + @Column() + guild_id: string; + + @Column() + author_id: string; + + @Column() + event_type: TEventType; + + @Column("simple-json") + event_data: AuditLogEventData[TEventType]; + + @Column() + created_at: string; +} diff --git a/backend/src/migrations/1630837718830-CreateApiAuditLogTable.ts b/backend/src/migrations/1630837718830-CreateApiAuditLogTable.ts new file mode 100644 index 00000000..a9f6faea --- /dev/null +++ b/backend/src/migrations/1630837718830-CreateApiAuditLogTable.ts @@ -0,0 +1,58 @@ +import { MigrationInterface, QueryRunner, Table, TableIndex } from "typeorm"; + +export class CreateApiAuditLogTable1630837718830 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "api_audit_log", + columns: [ + { + name: "id", + type: "int", + unsigned: true, + isPrimary: true, + isGenerated: true, + generationStrategy: "increment", + }, + { + name: "guild_id", + type: "bigint", + }, + { + name: "author_id", + type: "bigint", + }, + { + name: "event_type", + type: "varchar", + length: "255", + }, + { + name: "event_data", + type: "longtext", + }, + { + name: "created_at", + type: "datetime", + default: "(NOW())", + }, + ], + indices: [ + new TableIndex({ + columnNames: ["guild_id", "author_id"], + }), + new TableIndex({ + columnNames: ["guild_id", "event_type"], + }), + new TableIndex({ + columnNames: ["created_at"], + }), + ], + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("api_audit_log"); + } +} From 971ec0de6cdf2b1a4fec10ef208eed6a5907e746 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 13:58:27 +0300 Subject: [PATCH 145/191] Update permission names + code formatting --- shared/src/apiPermissions.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/shared/src/apiPermissions.ts b/shared/src/apiPermissions.ts index 1d8a71d0..d99952b3 100644 --- a/shared/src/apiPermissions.ts +++ b/shared/src/apiPermissions.ts @@ -13,27 +13,26 @@ const reverseApiPermissions = Object.entries(ApiPermissions).reduce((map, [key, export const permissionNames = { [ApiPermissions.Owner]: "Server owner", - [ApiPermissions.ManageAccess]: "Manage dashboard access", - [ApiPermissions.EditConfig]: "Edit config", + [ApiPermissions.ManageAccess]: "Bot manager", + [ApiPermissions.EditConfig]: "Bot operator", [ApiPermissions.ReadConfig]: "Read config", [ApiPermissions.ViewGuild]: "View server", }; export type TPermissionHierarchy = Array; -// prettier-ignore-start +// prettier-ignore export const permissionHierarchy: TPermissionHierarchy = [ - [ - ApiPermissions.Owner, - [ - [ - ApiPermissions.ManageAccess, - [[ApiPermissions.EditConfig, [[ApiPermissions.ReadConfig, [ApiPermissions.ViewGuild]]]]], - ], - ], - ], + [ApiPermissions.Owner, [ + [ApiPermissions.ManageAccess, [ + [ApiPermissions.EditConfig, [ + [ApiPermissions.ReadConfig, [ + ApiPermissions.ViewGuild, + ]], + ]], + ]], + ]], ]; -// prettier-ignore-end export function permissionArrToSet(permissions: string[]): Set { return new Set(permissions.filter(p => reverseApiPermissions[p])) as Set; From 48c4b3578d98250d0b7b5c6224b2c354d31070b6 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 14:34:06 +0300 Subject: [PATCH 146/191] Update server owner dashboard permissions automatically --- backend/src/data/AllowedGuilds.ts | 7 +- backend/src/data/ApiPermissionAssignments.ts | 67 +++++++++++++++++++ backend/src/data/apiAuditLogTypes.ts | 18 ++++- backend/src/data/entities/AllowedGuild.ts | 6 ++ backend/src/data/entities/ApiAuditLogEntry.ts | 2 +- .../data/entities/ApiPermissionAssignment.ts | 5 +- ...0840428694-AddTimestampsToAllowedGuilds.ts | 24 +++++++ .../GuildInfoSaver/GuildInfoSaverPlugin.ts | 35 ++++++---- backend/src/plugins/GuildInfoSaver/types.ts | 1 - 9 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 backend/src/migrations/1630840428694-AddTimestampsToAllowedGuilds.ts diff --git a/backend/src/data/AllowedGuilds.ts b/backend/src/data/AllowedGuilds.ts index e3399fea..747dbb87 100644 --- a/backend/src/data/AllowedGuilds.ts +++ b/backend/src/data/AllowedGuilds.ts @@ -2,6 +2,8 @@ import { getRepository, Repository } from "typeorm"; import { ApiPermissionTypes } from "./ApiPermissionAssignments"; import { BaseRepository } from "./BaseRepository"; import { AllowedGuild } from "./entities/AllowedGuild"; +import moment from "moment-timezone"; +import { DBDateFormat } from "../utils"; export class AllowedGuilds extends BaseRepository { private allowedGuilds: Repository; @@ -37,7 +39,10 @@ export class AllowedGuilds extends BaseRepository { } updateInfo(id, name, icon, ownerId) { - return this.allowedGuilds.update({ id }, { name, icon, owner_id: ownerId }); + return this.allowedGuilds.update( + { id }, + { name, icon, owner_id: ownerId, updated_at: moment.utc().format(DBDateFormat) }, + ); } add(id, data: Partial> = {}) { diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index fc04b61c..2497bf49 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -2,6 +2,9 @@ import { ApiPermissions } from "@shared/apiPermissions"; import { getRepository, Repository } from "typeorm"; import { BaseRepository } from "./BaseRepository"; import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment"; +import { Permissions } from "discord.js"; +import { ApiAuditLog } from "./ApiAuditLog"; +import { AuditLogEventTypes } from "./apiAuditLogTypes"; export enum ApiPermissionTypes { User = "USER", @@ -10,10 +13,12 @@ export enum ApiPermissionTypes { export class ApiPermissionAssignments extends BaseRepository { private apiPermissions: Repository; + private auditLogs: ApiAuditLog; constructor() { super(); this.apiPermissions = getRepository(ApiPermissionAssignment); + this.auditLogs = new ApiAuditLog(); } getByGuildId(guildId) { @@ -63,4 +68,66 @@ export class ApiPermissionAssignments extends BaseRepository { .andWhere("expires_at <= NOW()") .delete(); } + + async applyOwnerChange(guildId: string, newOwnerId: string) { + const existingPermissions = await this.getByGuildId(guildId); + let updatedOwner = false; + for (const perm of existingPermissions) { + let hasChanges = false; + + // Remove owner permission from anyone who currently has it + if (perm.permissions.includes(ApiPermissions.Owner)) { + perm.permissions.splice(perm.permissions.indexOf(ApiPermissions.Owner), 1); + hasChanges = true; + } + + // Add owner permission if we encounter the new owner + if (perm.type === ApiPermissionTypes.User && perm.target_id === newOwnerId) { + perm.permissions.push(ApiPermissions.Owner); + updatedOwner = true; + hasChanges = true; + } + + if (hasChanges) { + const criteria = { + guild_id: perm.guild_id, + type: perm.type, + target_id: perm.target_id, + }; + if (perm.permissions.length === 0) { + // No remaining permissions -> remove entry + this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.REMOVE_API_PERMISSION, { + type: perm.type, + target_id: perm.target_id, + }); + await this.apiPermissions.delete(criteria); + } else { + this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.EDIT_API_PERMISSION, { + type: perm.type, + target_id: perm.target_id, + permissions: perm.permissions, + expires_at: perm.expires_at, + }); + await this.apiPermissions.update(criteria, { + permissions: perm.permissions, + }); + } + } + } + + if (!updatedOwner) { + this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.ADD_API_PERMISSION, { + type: ApiPermissionTypes.User, + target_id: newOwnerId, + permissions: [ApiPermissions.Owner], + expires_at: null, + }); + await this.apiPermissions.insert({ + guild_id: guildId, + type: ApiPermissionTypes.User, + target_id: newOwnerId, + permissions: [ApiPermissions.Owner], + }); + } + } } diff --git a/backend/src/data/apiAuditLogTypes.ts b/backend/src/data/apiAuditLogTypes.ts index c50cc06d..8e9075af 100644 --- a/backend/src/data/apiAuditLogTypes.ts +++ b/backend/src/data/apiAuditLogTypes.ts @@ -1,7 +1,10 @@ +import { ApiPermissionTypes } from "./ApiPermissionAssignments"; + export const AuditLogEventTypes = { - ADD_API_PERMISSION: "ADD_API_PERMISSION", - REMOVE_API_PERMISSION: "REMOVE_API_PERMISSION", - EDIT_CONFIG: "EDIT_CONFIG", + ADD_API_PERMISSION: "ADD_API_PERMISSION" as const, + EDIT_API_PERMISSION: "EDIT_API_PERMISSION" as const, + REMOVE_API_PERMISSION: "REMOVE_API_PERMISSION" as const, + EDIT_CONFIG: "EDIT_CONFIG" as const, }; export type AuditLogEventType = keyof typeof AuditLogEventTypes; @@ -20,12 +23,21 @@ export type EditConfigEventData = {}; export interface AuditLogEventData extends Record { ADD_API_PERMISSION: { + type: ApiPermissionTypes; + target_id: string; + permissions: string[]; + expires_at: string | null; + }; + + EDIT_API_PERMISSION: { + type: ApiPermissionTypes; target_id: string; permissions: string[]; expires_at: string | null; }; REMOVE_API_PERMISSION: { + type: ApiPermissionTypes; target_id: string; }; diff --git a/backend/src/data/entities/AllowedGuild.ts b/backend/src/data/entities/AllowedGuild.ts index 129603ac..2ded9b44 100644 --- a/backend/src/data/entities/AllowedGuild.ts +++ b/backend/src/data/entities/AllowedGuild.ts @@ -14,4 +14,10 @@ export class AllowedGuild { @Column() owner_id: string; + + @Column() + created_at: string; + + @Column() + updated_at: string; } diff --git a/backend/src/data/entities/ApiAuditLogEntry.ts b/backend/src/data/entities/ApiAuditLogEntry.ts index 9c613353..0491c313 100644 --- a/backend/src/data/entities/ApiAuditLogEntry.ts +++ b/backend/src/data/entities/ApiAuditLogEntry.ts @@ -14,7 +14,7 @@ export class ApiAuditLogEntry { @Column() author_id: string; - @Column() + @Column({ type: String }) event_type: TEventType; @Column("simple-json") diff --git a/backend/src/data/entities/ApiPermissionAssignment.ts b/backend/src/data/entities/ApiPermissionAssignment.ts index 454fe17c..0171f940 100644 --- a/backend/src/data/entities/ApiPermissionAssignment.ts +++ b/backend/src/data/entities/ApiPermissionAssignment.ts @@ -1,5 +1,6 @@ import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm"; import { ApiUserInfo } from "./ApiUserInfo"; +import { ApiPermissionTypes } from "../ApiPermissionAssignments"; @Entity("api_permissions") export class ApiPermissionAssignment { @@ -7,9 +8,9 @@ export class ApiPermissionAssignment { @PrimaryColumn() guild_id: string; - @Column() + @Column({ type: "string" }) @PrimaryColumn() - type: string; + type: ApiPermissionTypes; @Column() @PrimaryColumn() diff --git a/backend/src/migrations/1630840428694-AddTimestampsToAllowedGuilds.ts b/backend/src/migrations/1630840428694-AddTimestampsToAllowedGuilds.ts new file mode 100644 index 00000000..8b29f669 --- /dev/null +++ b/backend/src/migrations/1630840428694-AddTimestampsToAllowedGuilds.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddTimestampsToAllowedGuilds1630840428694 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumns("allowed_guilds", [ + new TableColumn({ + name: "created_at", + type: "datetime", + default: "(NOW())", + }), + new TableColumn({ + name: "updated_at", + type: "datetime", + default: "(NOW())", + onUpdate: "CURRENT_TIMESTAMP", + }), + ]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("allowed_guilds", "updated_at"); + await queryRunner.dropColumn("allowed_guilds", "created_at"); + } +} diff --git a/backend/src/plugins/GuildInfoSaver/GuildInfoSaverPlugin.ts b/backend/src/plugins/GuildInfoSaver/GuildInfoSaverPlugin.ts index 3f241c65..880bd94e 100644 --- a/backend/src/plugins/GuildInfoSaver/GuildInfoSaverPlugin.ts +++ b/backend/src/plugins/GuildInfoSaver/GuildInfoSaverPlugin.ts @@ -1,9 +1,11 @@ import * as t from "io-ts"; -import { GuildPluginData } from "knub"; +import { GuildPluginData, typedGuildEventListener } from "knub"; import { AllowedGuilds } from "../../data/AllowedGuilds"; import { MINUTES } from "../../utils"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { GuildInfoSaverPluginType } from "./types"; +import { Guild } from "discord.js"; +import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; export const GuildInfoSaverPlugin = zeppelinGuildPlugin()({ name: "guild_info_saver", @@ -11,13 +13,18 @@ export const GuildInfoSaverPlugin = zeppelinGuildPlugin updateGuildInfo(pluginData), 60 * MINUTES); + void updateGuildInfo(pluginData.guild); + pluginData.state.updateInterval = setInterval(() => updateGuildInfo(pluginData.guild), 60 * MINUTES); }, beforeUnload(pluginData) { @@ -25,11 +32,13 @@ export const GuildInfoSaverPlugin = zeppelinGuildPlugin) { - pluginData.state.allowedGuilds.updateInfo( - pluginData.guild.id, - pluginData.guild.name, - pluginData.guild.iconURL(), - pluginData.guild.ownerId, - ); +async function updateGuildInfo(guild: Guild) { + const allowedGuilds = new AllowedGuilds(); + const existingData = (await allowedGuilds.find(guild.id))!; + allowedGuilds.updateInfo(guild.id, guild.name, guild.iconURL(), guild.ownerId); + + if (existingData.owner_id !== guild.ownerId || existingData.created_at === existingData.updated_at) { + const apiPermissions = new ApiPermissionAssignments(); + apiPermissions.applyOwnerChange(guild.id, guild.ownerId); + } } diff --git a/backend/src/plugins/GuildInfoSaver/types.ts b/backend/src/plugins/GuildInfoSaver/types.ts index 25926bff..29013186 100644 --- a/backend/src/plugins/GuildInfoSaver/types.ts +++ b/backend/src/plugins/GuildInfoSaver/types.ts @@ -3,7 +3,6 @@ import { AllowedGuilds } from "../../data/AllowedGuilds"; export interface GuildInfoSaverPluginType extends BasePluginType { state: { - allowedGuilds: AllowedGuilds; updateInterval: NodeJS.Timeout; }; } From 3b09d2d679017289a905fb99a164f6e3d76a50ae Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:42:35 +0300 Subject: [PATCH 147/191] Add rudimentary user management to dashboard --- backend/src/api/auth.ts | 2 +- backend/src/api/guilds.ts | 76 +++++- backend/src/data/ApiPermissionAssignments.ts | 16 +- .../data/entities/ApiPermissionAssignment.ts | 6 +- ...0837386329-AddExpiresAtToApiPermissions.ts | 2 +- dashboard/package-lock.json | 25 ++ dashboard/package.json | 2 + .../src/components/dashboard/GuildAccess.vue | 237 +++++++++++++++--- .../src/components/dashboard/GuildList.vue | 25 +- dashboard/src/store/auth.ts | 14 +- dashboard/src/store/guilds.ts | 51 ++-- dashboard/src/store/types.ts | 20 +- 12 files changed, 395 insertions(+), 81 deletions(-) diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index c891ac63..92e8e594 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -149,7 +149,7 @@ export function initAuth(app: express.Express) { return res.json({ valid: false }); } - res.json({ valid: true }); + res.json({ valid: true, userId }); }); app.post("/auth/logout", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => { await apiLogins.expireApiKey(req.user!.apiKey); diff --git a/backend/src/api/guilds.ts b/backend/src/api/guilds.ts index 6f6a7a85..412f526f 100644 --- a/backend/src/api/guilds.ts +++ b/backend/src/api/guilds.ts @@ -3,15 +3,21 @@ import express, { Request, Response } from "express"; import { YAMLException } from "js-yaml"; import { validateGuildConfig } from "../configValidator"; import { AllowedGuilds } from "../data/AllowedGuilds"; -import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; +import { ApiPermissionAssignments, ApiPermissionTypes } from "../data/ApiPermissionAssignments"; import { Configs } from "../data/Configs"; import { apiTokenAuthHandlers } from "./auth"; import { hasGuildPermission, requireGuildPermission } from "./permissions"; import { clientError, ok, serverError, unauthorized } from "./responses"; import { loadYamlSafely } from "../utils/loadYamlSafely"; import { ObjectAliasError } from "../utils/validateNoObjectAliases"; +import { isSnowflake } from "../utils"; +import moment from "moment-timezone"; +import { ApiAuditLog } from "../data/ApiAuditLog"; +import { AuditLogEventTypes } from "../data/apiAuditLogTypes"; +import { Queue } from "../Queue"; const apiPermissionAssignments = new ApiPermissionAssignments(); +const auditLog = new ApiAuditLog(); export function initGuildsAPI(app: express.Express) { const allowedGuilds = new AllowedGuilds(); @@ -25,6 +31,14 @@ export function initGuildsAPI(app: express.Express) { res.json(guilds); }); + guildRouter.get( + "/my-permissions", // a + async (req: Request, res: Response) => { + const permissions = await apiPermissionAssignments.getByUserId(req.user!.userId); + res.json(permissions); + }, + ); + guildRouter.get("/:guildId", async (req: Request, res: Response) => { if (!(await hasGuildPermission(req.user!.userId, req.params.guildId, ApiPermissions.ViewGuild))) { return unauthorized(res); @@ -101,5 +115,65 @@ export function initGuildsAPI(app: express.Express) { }, ); + const permissionManagementQueue = new Queue(); + guildRouter.post( + "/:guildId/set-target-permissions", + requireGuildPermission(ApiPermissions.ManageAccess), + async (req: Request, res: Response) => { + await permissionManagementQueue.add(async () => { + const { type, targetId, permissions, expiresAt } = req.body; + + if (type !== ApiPermissionTypes.User) { + return clientError(res, "Invalid type"); + } + if (!isSnowflake(targetId)) { + return clientError(res, "Invalid targetId"); + } + const validPermissions = new Set(Object.values(ApiPermissions)); + validPermissions.delete(ApiPermissions.Owner); + if (!Array.isArray(permissions) || permissions.some(p => !validPermissions.has(p))) { + return clientError(res, "Invalid permissions"); + } + if (expiresAt != null && !moment.utc(expiresAt).isValid()) { + return clientError(res, "Invalid expiresAt"); + } + + const existingAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId); + if (existingAssignment && existingAssignment.permissions.includes(ApiPermissions.Owner)) { + return clientError(res, "Can't change owner permissions"); + } + + if (permissions.length === 0) { + await apiPermissionAssignments.removeUser(req.params.guildId, targetId); + await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.REMOVE_API_PERMISSION, { + type: ApiPermissionTypes.User, + target_id: targetId, + }); + } else { + const existing = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId); + if (existing) { + await apiPermissionAssignments.updateUserPermissions(req.params.guildId, targetId, permissions); + await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.EDIT_API_PERMISSION, { + type: ApiPermissionTypes.User, + target_id: targetId, + permissions, + expires_at: existing.expires_at, + }); + } else { + await apiPermissionAssignments.addUser(req.params.guildId, targetId, permissions, expiresAt); + await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.ADD_API_PERMISSION, { + type: ApiPermissionTypes.User, + target_id: targetId, + permissions, + expires_at: expiresAt, + }); + } + } + + ok(res); + }); + }, + ); + app.use("/guilds", guildRouter); } diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index 2497bf49..d6cf11db 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -48,12 +48,13 @@ export class ApiPermissionAssignments extends BaseRepository { }); } - addUser(guildId, userId, permissions: ApiPermissions[]) { + addUser(guildId, userId, permissions: ApiPermissions[], expiresAt: string | null = null) { return this.apiPermissions.insert({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId, permissions, + expires_at: expiresAt, }); } @@ -61,6 +62,19 @@ export class ApiPermissionAssignments extends BaseRepository { return this.apiPermissions.delete({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId }); } + async updateUserPermissions(guildId: string, userId: string, permissions: ApiPermissions[]): Promise { + await this.apiPermissions.update( + { + guild_id: guildId, + type: ApiPermissionTypes.User, + target_id: userId, + }, + { + permissions, + }, + ); + } + async clearExpiredPermissions() { await this.apiPermissions .createQueryBuilder() diff --git a/backend/src/data/entities/ApiPermissionAssignment.ts b/backend/src/data/entities/ApiPermissionAssignment.ts index 0171f940..349544d5 100644 --- a/backend/src/data/entities/ApiPermissionAssignment.ts +++ b/backend/src/data/entities/ApiPermissionAssignment.ts @@ -8,7 +8,7 @@ export class ApiPermissionAssignment { @PrimaryColumn() guild_id: string; - @Column({ type: "string" }) + @Column({ type: String }) @PrimaryColumn() type: ApiPermissionTypes; @@ -19,8 +19,8 @@ export class ApiPermissionAssignment { @Column("simple-array") permissions: string[]; - @Column() - expires_at: string; + @Column({ type: String, nullable: true }) + expires_at: string | null; @ManyToOne( type => ApiUserInfo, diff --git a/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts b/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts index 3eb6ea27..812d35ed 100644 --- a/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts +++ b/backend/src/migrations/1630837386329-AddExpiresAtToApiPermissions.ts @@ -5,7 +5,7 @@ export class AddExpiresAtToApiPermissions1630837386329 implements MigrationInter await queryRunner.addColumns("api_permissions", [ new TableColumn({ name: "expires_at", - type: "boolean", + type: "datetime", isNullable: true, default: null, }), diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index f395a81f..7aeb3c64 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -9,9 +9,11 @@ "version": "1.0.0", "dependencies": { "highlight.js": "^9.15.10", + "humanize-duration": "^3.27.0", "js-yaml": "^3.13.1", "marked": "^0.7.0", "modern-css-reset": "^1.0.4", + "moment": "^2.29.1", "vue": "^2.6.10", "vue-highlightjs": "git://github.com/Dragory/vue-highlightjs.git#pass-hljs-instance", "vue-material-design-icons": "^4.1.0", @@ -6038,6 +6040,11 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "node_modules/humanize-duration": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", + "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7164,6 +7171,14 @@ "resolved": "https://registry.npmjs.org/modern-css-reset/-/modern-css-reset-1.4.0.tgz", "integrity": "sha512-0crZmSFmrxkI7159rvQWjpDhy0u4+Awg/iOycJdlVn0RSeft/a+6BrQHR3IqvmdK25sqt0o6Z5Ap7cWgUee2rw==" }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -17836,6 +17851,11 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "humanize-duration": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", + "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -18685,6 +18705,11 @@ "resolved": "https://registry.npmjs.org/modern-css-reset/-/modern-css-reset-1.4.0.tgz", "integrity": "sha512-0crZmSFmrxkI7159rvQWjpDhy0u4+Awg/iOycJdlVn0RSeft/a+6BrQHR3IqvmdK25sqt0o6Z5Ap7cWgUee2rw==" }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 01b2e226..2a3ae71f 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -39,9 +39,11 @@ }, "dependencies": { "highlight.js": "^9.15.10", + "humanize-duration": "^3.27.0", "js-yaml": "^3.13.1", "marked": "^0.7.0", "modern-css-reset": "^1.0.4", + "moment": "^2.29.1", "vue": "^2.6.10", "vue-highlightjs": "git://github.com/Dragory/vue-highlightjs.git#pass-hljs-instance", "vue-material-design-icons": "^4.1.0", diff --git a/dashboard/src/components/dashboard/GuildAccess.vue b/dashboard/src/components/dashboard/GuildAccess.vue index e19acde1..c4aa9b9d 100644 --- a/dashboard/src/components/dashboard/GuildAccess.vue +++ b/dashboard/src/components/dashboard/GuildAccess.vue @@ -1,70 +1,182 @@ diff --git a/dashboard/src/components/dashboard/GuildList.vue b/dashboard/src/components/dashboard/GuildList.vue index 3b126ce6..2842d7f3 100644 --- a/dashboard/src/components/dashboard/GuildList.vue +++ b/dashboard/src/components/dashboard/GuildList.vue @@ -17,9 +17,8 @@
{{ guild.id }}
- Info Config - Access + Access
@@ -28,12 +27,15 @@ - diff --git a/dashboard/src/store/auth.ts b/dashboard/src/store/auth.ts index 4e24658c..457284b3 100644 --- a/dashboard/src/store/auth.ts +++ b/dashboard/src/store/auth.ts @@ -12,6 +12,7 @@ export const AuthStore: Module = { apiKey: null, loadedInitialAuth: false, authRefreshInterval: null, + userId: null, }, actions: { @@ -23,7 +24,7 @@ export const AuthStore: Module = { try { const result = await post("auth/validate-key", { key: storedKey }); if (result.valid) { - await dispatch("setApiKey", storedKey); + await dispatch("setApiKey", { key: storedKey, userId: result.userId }); return; } } catch {} // tslint:disable-line @@ -35,9 +36,9 @@ export const AuthStore: Module = { commit("markInitialAuthLoaded"); }, - setApiKey({ commit, state, dispatch }, newKey: string) { - localStorage.setItem("apiKey", newKey); - commit("setApiKey", newKey); + setApiKey({ commit, state, dispatch }, { key, userId }) { + localStorage.setItem("apiKey", key); + commit("setApiKey", { key, userId }); dispatch("startAuthAutoRefresh"); }, @@ -64,7 +65,7 @@ export const AuthStore: Module = { await dispatch("endAuthAutoRefresh"); localStorage.removeItem("apiKey"); - commit("setApiKey", null); + commit("setApiKey", { key: null, userId: null }); }, async logout({ dispatch }) { @@ -79,8 +80,9 @@ export const AuthStore: Module = { }, mutations: { - setApiKey(state: AuthState, key) { + setApiKey(state: AuthState, { key, userId }) { state.apiKey = key; + state.userId = userId; }, setAuthRefreshInterval(state: AuthState, interval: IntervalType | null) { diff --git a/dashboard/src/store/guilds.ts b/dashboard/src/store/guilds.ts index ce9306dc..dfe6ac43 100644 --- a/dashboard/src/store/guilds.ts +++ b/dashboard/src/store/guilds.ts @@ -11,7 +11,6 @@ export const GuildStore: Module = { availableGuildsLoadStatus: LoadStatus.None, available: new Map(), configs: {}, - myPermissions: {}, guildPermissionAssignments: {}, }, @@ -48,9 +47,14 @@ export const GuildStore: Module = { await post(`guilds/${guildId}/config`, { config }); }, - async checkPermission({ commit }, { guildId, permission }) { - const result = await post(`guilds/${guildId}/check-permission`, { permission }); - commit("setMyPermission", { guildId, permission, value: result.result }); + async loadMyPermissionAssignments({ commit }) { + const myPermissionAssignments = await get(`guilds/my-permissions`); + for (const permissionAssignment of myPermissionAssignments) { + commit("setGuildPermissionAssignments", { + guildId: permissionAssignment.guild_id, + permissionAssignments: [permissionAssignment], + }); + } }, async loadGuildPermissionAssignments({ commit }, guildId) { @@ -58,8 +62,9 @@ export const GuildStore: Module = { commit("setGuildPermissionAssignments", { guildId, permissionAssignments }); }, - async setTargetPermissions({ commit }, { guildId, targetId, type, permissions }) { - commit("setTargetPermissions", { guildId, targetId, type, permissions }); + async setTargetPermissions({ commit }, { guildId, targetId, type, permissions, expiresAt }) { + await post(`guilds/${guildId}/set-target-permissions`, { guildId, targetId, type, permissions, expiresAt }); + commit("setTargetPermissions", { guildId, targetId, type, permissions, expiresAt }); }, }, @@ -77,12 +82,11 @@ export const GuildStore: Module = { Vue.set(state.configs, guildId, config); }, - setMyPermission(state: GuildState, { guildId, permission, value }) { - Vue.set(state.myPermissions, guildId, state.myPermissions[guildId] || {}); - Vue.set(state.myPermissions[guildId], permission, value); - }, - setGuildPermissionAssignments(state: GuildState, { guildId, permissionAssignments }) { + if (!state.guildPermissionAssignments) { + Vue.set(state, "guildPermissionAssignments", {}); + } + Vue.set( state.guildPermissionAssignments, guildId, @@ -93,12 +97,29 @@ export const GuildStore: Module = { ); }, - setTargetPermissions(state: GuildState, { guildId, targetId, type, permissions }) { + setTargetPermissions(state: GuildState, { guildId, targetId, type, permissions, expiresAt }) { const guildPermissionAssignments = state.guildPermissionAssignments[guildId] || []; - const itemToEdit = guildPermissionAssignments.find(p => p.target_id === targetId && p.type === type); - if (!itemToEdit) return; + if (permissions.length === 0) { + // No permissions -> remove permission assignment + guildPermissionAssignments.splice( + guildPermissionAssignments.findIndex(p => p.target_id === targetId && p.type === type), + 1, + ); + } else { + // Update/add permission assignment + const itemToEdit = guildPermissionAssignments.find(p => p.target_id === targetId && p.type === type); + if (itemToEdit) { + itemToEdit.permissions = new Set(permissions); + } else { + state.guildPermissionAssignments[guildId].push({ + type, + target_id: targetId, + permissions: new Set(permissions), + expires_at: expiresAt, + }); + } + } - itemToEdit.permissions = permissions; state.guildPermissionAssignments = { ...state.guildPermissionAssignments }; }, }, diff --git a/dashboard/src/store/types.ts b/dashboard/src/store/types.ts index b11193aa..c7e2987b 100644 --- a/dashboard/src/store/types.ts +++ b/dashboard/src/store/types.ts @@ -1,5 +1,4 @@ import { ApiPermissions } from "@shared/apiPermissions"; -import { ApiPermissionTypes } from "../../../backend/src/data/ApiPermissionAssignments"; export enum LoadStatus { None = 1, @@ -14,6 +13,14 @@ export interface AuthState { apiKey: string | null; loadedInitialAuth: boolean; authRefreshInterval: IntervalType | null; + userId: string | null; +} + +export interface GuildPermissionAssignment { + type: string; + target_id: string; + permissions: Set; + expires_at: string | null; } export interface GuildState { @@ -29,17 +36,8 @@ export interface GuildState { configs: { [key: string]: string; }; - myPermissions: { - [guildId: string]: { - [K in ApiPermissions]?: boolean; - }; - }; guildPermissionAssignments: { - [guildId: string]: Array<{ - target_id: string; - type: ApiPermissionTypes; - permissions: Set; - }>; + [guildId: string]: GuildPermissionAssignment[]; }; } From f13695c5243f10c5e912bd620ff325813834e902 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 17:07:50 +0300 Subject: [PATCH 148/191] Add command to add servers from invites with eligibility check --- backend/src/index.ts | 7 ++ .../plugins/BotControl/BotControlPlugin.ts | 3 + .../commands/AddServerFromInviteCmd.ts | 68 +++++++++++++++++++ .../BotControl/commands/AllowServerCmd.ts | 18 ++++- .../BotControl/commands/EligibleCmd.ts | 47 +++---------- .../BotControl/functions/isEligible.ts | 46 +++++++++++++ backend/src/plugins/BotControl/types.ts | 1 + 7 files changed, 151 insertions(+), 39 deletions(-) create mode 100644 backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts create mode 100644 backend/src/plugins/BotControl/functions/isEligible.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 108a3cbe..3022f7a7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -250,6 +250,13 @@ connect().then(async () => { async getConfig(id) { const key = id === "global" ? "global" : `guild-${id}`; + if (id !== "global") { + const allowedGuild = await allowedGuilds.find(id); + if (!allowedGuild) { + return {}; + } + } + const row = await guildConfigs.getActiveByKey(key); if (row) { try { diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index 6770b8ef..23ba5dcc 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -19,12 +19,14 @@ import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; import { ServersCmd } from "./commands/ServersCmd"; import { BotControlPluginType, ConfigSchema } from "./types"; import { PerformanceCmd } from "./commands/PerformanceCmd"; +import { AddServerFromInviteCmd } from "./commands/AddServerFromInviteCmd"; const defaultOptions = { config: { can_use: false, can_eligible: false, can_performance: false, + can_add_server_from_invite: false, update_cmd: null, }, }; @@ -48,6 +50,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ ListDashboardPermsCmd, EligibleCmd, PerformanceCmd, + AddServerFromInviteCmd, ], async afterLoad(pluginData) { diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts new file mode 100644 index 00000000..c7e2f28c --- /dev/null +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -0,0 +1,68 @@ +import { ApiPermissions } from "@shared/apiPermissions"; +import { TextChannel } from "discord.js"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { DBDateFormat, isGuildInvite, isSnowflake, resolveInvite } from "../../../utils"; +import { botControlCmd } from "../types"; +import moment from "moment-timezone"; +import { isEligible } from "../functions/isEligible"; + +export const AddServerFromInviteCmd = botControlCmd({ + trigger: ["add_server_from_invite", "allow_server_from_invite"], + permission: "can_add_server_from_invite", + + signature: { + user: ct.resolvedUser(), + inviteCode: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + const invite = await resolveInvite(pluginData.client, args.inviteCode, true); + if (!invite || !isGuildInvite(invite)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve invite"); // :D + return; + } + + const existing = await pluginData.state.allowedGuilds.find(invite.guild.id); + if (existing) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is already allowed!"); + return; + } + + const { result, explanation } = await isEligible(pluginData, args.user, invite); + if (!result) { + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + `Could not add server because it's not eligible: ${explanation}`, + ); + return; + } + + await pluginData.state.allowedGuilds.add(invite.guild.id, { name: invite.guild.name }); + await pluginData.state.configs.saveNewRevision(`guild-${invite.guild.id}`, "plugins: {}", msg.author.id); + + await pluginData.state.apiPermissionAssignments.addUser(invite.guild.id, args.user.id, [ + ApiPermissions.ManageAccess, + ]); + + if (args.user.id !== msg.author.id) { + // Add temporary access to user who added server + await pluginData.state.apiPermissionAssignments.addUser( + invite.guild.id, + msg.author.id, + [ApiPermissions.ManageAccess], + moment + .utc() + .add(1, "hour") + .format(DBDateFormat), + ); + } + + sendSuccessMessage( + pluginData, + msg.channel as TextChannel, + "Server was eligible and is now allowed to use Zeppelin!", + ); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index 2ff34f6d..304e7705 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -2,8 +2,9 @@ import { ApiPermissions } from "@shared/apiPermissions"; import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { isSnowflake } from "../../../utils"; +import { DBDateFormat, isSnowflake } from "../../../utils"; import { botControlCmd } from "../types"; +import moment from "moment-timezone"; export const AllowServerCmd = botControlCmd({ trigger: ["allow_server", "allowserver", "add_server", "addserver"], @@ -38,7 +39,20 @@ export const AllowServerCmd = botControlCmd({ await pluginData.state.configs.saveNewRevision(`guild-${args.guildId}`, "plugins: {}", msg.author.id); if (args.userId) { - await pluginData.state.apiPermissionAssignments.addUser(args.guildId, args.userId, [ApiPermissions.EditConfig]); + await pluginData.state.apiPermissionAssignments.addUser(args.guildId, args.userId, [ApiPermissions.ManageAccess]); + } + + if (args.userId !== msg.author.id) { + // Add temporary access to user who added server + await pluginData.state.apiPermissionAssignments.addUser( + args.guildId, + msg.author.id, + [ApiPermissions.ManageAccess], + moment + .utc() + .add(1, "hour") + .format(DBDateFormat), + ); } sendSuccessMessage(pluginData, msg.channel as TextChannel, "Server is now allowed to use Zeppelin!"); diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index 522cf3a0..0be29978 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -1,10 +1,9 @@ -import { TextChannel } from "discord.js"; +import { Guild, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveInvite, verboseUserMention } from "../../../utils"; +import { GuildInvite, isGuildInvite, resolveInvite, verboseUserMention } from "../../../utils"; import { botControlCmd } from "../types"; - -const REQUIRED_MEMBER_COUNT = 5000; +import { isEligible } from "../functions/isEligible"; export const EligibleCmd = botControlCmd({ trigger: ["eligible", "is_eligible", "iseligible"], @@ -16,45 +15,19 @@ export const EligibleCmd = botControlCmd({ }, async run({ pluginData, message: msg, args }) { - if ((await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id)).length) { - sendSuccessMessage( - pluginData, - msg.channel as TextChannel, - `${verboseUserMention(args.user)} is an existing bot operator. They are eligible!`, - ); - return; - } - const invite = await resolveInvite(pluginData.client, args.inviteCode, true); - if (!invite || !invite.guild) { - sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve server from invite"); + if (!invite || !isGuildInvite(invite)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve invite"); return; } - if (invite.guild.features.includes("PARTNERED")) { - sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is partnered. It is eligible!`); + const { result, explanation } = await isEligible(pluginData, args.user, invite); + + if (result) { + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is eligible: ${explanation}`); return; } - if (invite.guild.features.includes("VERIFIED")) { - sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is verified. It is eligible!`); - return; - } - - const memberCount = invite.memberCount || 0; - if (memberCount >= REQUIRED_MEMBER_COUNT) { - sendSuccessMessage( - pluginData, - msg.channel as TextChannel, - `Server has ${memberCount} members, which is equal or higher than the required ${REQUIRED_MEMBER_COUNT}. It is eligible!`, - ); - return; - } - - sendErrorMessage( - pluginData, - msg.channel as TextChannel, - `Server **${invite.guild.name}** (\`${invite.guild.id}\`) is not eligible`, - ); + sendErrorMessage(pluginData, msg.channel as TextChannel, `Server is **NOT** eligible: ${explanation}`); }, }); diff --git a/backend/src/plugins/BotControl/functions/isEligible.ts b/backend/src/plugins/BotControl/functions/isEligible.ts new file mode 100644 index 00000000..164430f3 --- /dev/null +++ b/backend/src/plugins/BotControl/functions/isEligible.ts @@ -0,0 +1,46 @@ +import { User } from "discord.js"; +import { BotControlPluginType } from "../types"; +import { GlobalPluginData } from "knub"; +import { GuildInvite } from "../../../utils"; + +const REQUIRED_MEMBER_COUNT = 5000; + +export async function isEligible( + pluginData: GlobalPluginData, + user: User, + invite: GuildInvite, +): Promise<{ result: boolean; explanation: string }> { + if ((await pluginData.state.apiPermissionAssignments.getByUserId(user.id)).length) { + return { + result: true, + explanation: "User is an existing bot operator", + }; + } + + if (invite.guild.features.includes("PARTNERED")) { + return { + result: true, + explanation: "Server is partnered", + }; + } + + if (invite.guild.features.includes("VERIFIED")) { + return { + result: true, + explanation: "Server is verified", + }; + } + + const memberCount = invite.memberCount || 0; + if (memberCount >= REQUIRED_MEMBER_COUNT) { + return { + result: true, + explanation: `Server has ${memberCount} members, which is equal or higher than the required ${REQUIRED_MEMBER_COUNT}`, + }; + } + + return { + result: false, + explanation: "Server does not meet requirements", + }; +} diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index 44e57cf8..b5c00657 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -10,6 +10,7 @@ export const ConfigSchema = t.type({ can_use: t.boolean, can_eligible: t.boolean, can_performance: t.boolean, + can_add_server_from_invite: t.boolean, update_cmd: tNullable(t.string), }); export type TConfigSchema = t.TypeOf; From cc4477a7eef81a4c7f246997df637bb3218c5321 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 17:30:34 +0300 Subject: [PATCH 149/191] Fix !jumbo for default emoji --- backend/src/plugins/Utility/commands/JumboCmd.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index e9ee63b5..d07855c1 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -31,7 +31,7 @@ function resizeBuffer(input: Buffer, width: number, height: number): Buffer { return photonImageToBuffer(photonImage); } -const CDN_URL = "https://twemoji.maxcdn.com/2/svg"; +const CDN_URL = "https://twemoji.maxcdn.com/"; export const JumboCmd = utilityCmd({ trigger: "jumbo", @@ -71,18 +71,19 @@ export const JumboCmd = utilityCmd({ file = new MessageAttachment(image, `emoji${extension}`); } } else { - let url = CDN_URL + `/${twemoji.convert.toCodePoint(args.emoji)}.svg`; + let url = `${twemoji.base}${twemoji.size}/${twemoji.convert.toCodePoint(args.emoji)}${twemoji.ext}`; let image: Buffer | undefined; try { + const downloadedBuffer = await getBufferFromUrl(url); image = resizeBuffer(await getBufferFromUrl(url), size, size); - } catch { - if (url.toLocaleLowerCase().endsWith("fe0f.svg")) { - url = url.slice(0, url.lastIndexOf("-fe0f")) + ".svg"; + } catch (err) { + if (url.toLocaleLowerCase().endsWith("fe0f.png")) { + url = url.slice(0, url.lastIndexOf("-fe0f")) + ".png"; image = await resizeBuffer(await getBufferFromUrl(url), size, size); } } if (!image) { - sendErrorMessage(pluginData, msg.channel, "Invalid emoji"); + sendErrorMessage(pluginData, msg.channel, "Error occurred while jumboing default emoji"); return; } From 70f2d7532749c31725b6d21d1955a3b845dd7783 Mon Sep 17 00:00:00 2001 From: Almeida Date: Sun, 5 Sep 2021 15:38:03 +0100 Subject: [PATCH 150/191] Rename use_inline_reply to inline (#278) --- backend/src/plugins/Automod/actions/reply.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index 70527f0b..bac6f105 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -24,7 +24,7 @@ export const ReplyAction = automodAction({ t.type({ text: tMessageContent, auto_delete: tNullable(t.union([tDelayString, t.number])), - use_inline_reply: tNullable(t.boolean), + inline: tNullable(t.boolean), }), ]), @@ -102,7 +102,7 @@ export const ReplyAction = automodAction({ }, }; - if (typeof actionConfig !== "string" && actionConfig.use_inline_reply) { + if (typeof actionConfig !== "string" && actionConfig.inline) { messageOpts.reply = { failIfNotExists: false, messageReference: _contexts[0].message!.id, From 7058d816313dea69896c763259e65a24f18f9152 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 17:38:52 +0300 Subject: [PATCH 151/191] Fix dist copy in update-dashboard.sh, return to original dir --- update-dashboard.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/update-dashboard.sh b/update-dashboard.sh index 7a336905..34a47fc6 100755 --- a/update-dashboard.sh +++ b/update-dashboard.sh @@ -11,5 +11,8 @@ git pull nvm use npm ci npm run build -rm -r "$TARGET_DIR/*" -cp -R dist/* "$TARGET_DIR" +rm -r $TARGET_DIR/* +cp -R dist/* $TARGET_DIR + +# Return +cd .. From bd851c4d5470cbd06a5b373336614324dbcf5304 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 18:11:55 +0300 Subject: [PATCH 152/191] Fix dashboard login redirect --- dashboard/src/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/src/auth.ts b/dashboard/src/auth.ts index 3dd1efb7..bb5c1b9e 100644 --- a/dashboard/src/auth.ts +++ b/dashboard/src/auth.ts @@ -16,8 +16,8 @@ export const authGuard: NavigationGuard = async (to, from, next) => { export const loginCallbackGuard: NavigationGuard = async (to, from, next) => { if (to.query.apiKey) { - await RootStore.dispatch("auth/setApiKey", to.query.apiKey); - next("/dashboard"); + await RootStore.dispatch("auth/setApiKey", { key: to.query.apiKey }); + window.location.href = "/dashboard"; } else { window.location.href = `/?error=noAccess`; } From 08e6e5a0617d4d3873c210396f2d955652af3797 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 18:32:46 +0300 Subject: [PATCH 153/191] presetup configurator: ignore build/ from git --- presetup-configurator/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 presetup-configurator/.gitignore diff --git a/presetup-configurator/.gitignore b/presetup-configurator/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/presetup-configurator/.gitignore @@ -0,0 +1 @@ +/build From 665fde0b7d538d7218be4425cf5804befe7998a8 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 18:33:26 +0300 Subject: [PATCH 154/191] presetup configurator: update package-lock.json to lockfile v2 --- presetup-configurator/package-lock.json | 436 +++++++++++++++++++++++- 1 file changed, 435 insertions(+), 1 deletion(-) diff --git a/presetup-configurator/package-lock.json b/presetup-configurator/package-lock.json index 37cd3f41..3d0c4d68 100644 --- a/presetup-configurator/package-lock.json +++ b/presetup-configurator/package-lock.json @@ -1,7 +1,441 @@ { "name": "zeppelin-presetup-configurator", + "lockfileVersion": 2, "requires": true, - "lockfileVersion": 1, + "packages": { + "": { + "name": "zeppelin-presetup-configurator", + "dependencies": { + "js-yaml": "^4.0.0", + "react": "^17.0.1", + "react-dom": "^17.0.1" + }, + "devDependencies": { + "@snowpack/plugin-typescript": "^1.2.1", + "@types/node": "^14.14.21", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "snowpack": "^3.0.11" + } + }, + "node_modules/@snowpack/plugin-typescript": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-typescript/-/plugin-typescript-1.2.1.tgz", + "integrity": "sha512-wU+JNaMVkqGsqTaUY7TnEMhGt/3URTgA9dpMCtZX6wn/ceA7Gwlmue/sOLynf0OTNLygHPvjiQECQYkEi3LTtg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "npm-run-path": "^4.0.1" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", + "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", + "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz", + "integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz", + "integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.8.32", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.32.tgz", + "integrity": "sha512-5IzQapMW/wFy5oxziHCJzawk26K3xeyrIAQPnPN3c0Q84hqRw6IfGDGfGWOdJNw5tAx77yvwqZ4r1QMpo6emJA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/execa": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fsevents": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.1.tgz", + "integrity": "sha512-f2wt9DCBKKjlFbjzGb8MOAW8LH8F0mrs1zc7KTjAJ9PZNQbfenzWbNP1VZJvw6ICMG9r14Ah6yfwPn7T7i646A==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", + "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.1" + }, + "peerDependencies": { + "react": "17.0.1" + } + }, + "node_modules/rollup": { + "version": "2.36.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.36.2.tgz", + "integrity": "sha512-qjjiuJKb+/8n0EZyQYVW+gFU4bNRBcZaXVzUgSVrGw0HlQBlK2aWyaOMMs1Ufic1jV69b9kW3u3i9B+hISDm3A==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", + "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/snowpack": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-3.0.11.tgz", + "integrity": "sha512-lBxgkvWTgdg0szE31JUt01wQkA9Lnmm+6lxqeV9rxDfflpx7ASnldVHFvu7Se70QJmPTQB0UJjfKI+xmYGwiiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.8.7", + "open": "^7.0.4", + "rollup": "^2.34.0" + }, + "bin": { + "snowpack": "index.bin.js", + "sp": "index.bin.js" + }, + "engines": { + "node": ">=10.19.0" + }, + "optionalDependencies": { + "fsevents": "^2.2.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + }, "dependencies": { "@snowpack/plugin-typescript": { "version": "1.2.1", From 57421a84393ef06de8ce640974e06ff1b56f6c16 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 18:33:42 +0300 Subject: [PATCH 155/191] presetup configurator: enable skipLibCheck in tsconfig --- presetup-configurator/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/presetup-configurator/tsconfig.json b/presetup-configurator/tsconfig.json index 75e45666..d426cfab 100644 --- a/presetup-configurator/tsconfig.json +++ b/presetup-configurator/tsconfig.json @@ -18,6 +18,7 @@ "resolveJsonModule": true, "esModuleInterop": true, "allowJs": true, - "jsx": "react" + "jsx": "react", + "skipLibCheck": true } } From 375837c31f025a73a5483c5b0183697d4992567a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 20:09:22 +0300 Subject: [PATCH 156/191] Use permissions for dashboard perm listing commands --- .../plugins/BotControl/BotControlPlugin.ts | 1 + .../commands/ListDashboardPermsCmd.ts | 7 ++----- .../commands/ListDashboardUsersCmd.ts | 19 ++++++++++++------- backend/src/plugins/BotControl/types.ts | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index 23ba5dcc..314e7420 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -27,6 +27,7 @@ const defaultOptions = { can_eligible: false, can_performance: false, can_add_server_from_invite: false, + can_list_dashboard_perms: false, update_cmd: null, }, }; diff --git a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts index 4a8a609c..dcfb166c 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts @@ -7,11 +7,8 @@ import { resolveUser } from "../../../utils"; import { botControlCmd } from "../types"; export const ListDashboardPermsCmd = botControlCmd({ - trigger: ["list_dashboard_permissions", "list_dashboard_perms", "list_dash_permissionss", "list_dash_perms"], - permission: null, - config: { - preFilters: [isOwnerPreFilter], - }, + trigger: ["list_dashboard_permissions", "list_dashboard_perms", "list_dash_permissions", "list_dash_perms"], + permission: "can_list_dashboard_perms", signature: { guildId: ct.string({ option: true, shortcut: "g" }), diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index 52c3083f..359a6e5b 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -6,10 +6,7 @@ import { botControlCmd } from "../types"; export const ListDashboardUsersCmd = botControlCmd({ trigger: ["list_dashboard_users"], - permission: null, - config: { - preFilters: [isOwnerPreFilter], - }, + permission: "can_list_dashboard_perms", signature: { guildId: ct.string(), @@ -23,13 +20,21 @@ export const ListDashboardUsersCmd = botControlCmd({ } const dashboardUsers = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); - const users = await Promise.all(dashboardUsers.map(perm => resolveUser(pluginData.client, perm.target_id))); - const userNameList = users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); + const users = await Promise.all( + dashboardUsers.map(async perm => ({ + user: await resolveUser(pluginData.client, perm.target_id), + permission: perm, + })), + ); + const userNameList = users.map( + ({ user, permission }) => + `<@!${user.id}> (**${user.tag}**, \`${user.id}\`): ${permission.permissions.join(", ")}`, + ); sendSuccessMessage( pluginData, msg.channel as TextChannel, - `The following users have dashboard access for **${guild.name}**:\n\n${userNameList}`, + `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`, {}, ); }, diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index b5c00657..f2da13ef 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -11,6 +11,7 @@ export const ConfigSchema = t.type({ can_eligible: t.boolean, can_performance: t.boolean, can_add_server_from_invite: t.boolean, + can_list_dashboard_perms: t.boolean, update_cmd: tNullable(t.string), }); export type TConfigSchema = t.TypeOf; From 9dea0e7d1754a8db947e75bf764251d047f2973b Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 20:58:26 +0300 Subject: [PATCH 157/191] Allow using thread/is_thread override criteria --- backend/src/pluginUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 309914f1..8e0cfaf6 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -62,6 +62,8 @@ const PluginOverrideCriteriaType: t.Type> = t.re const validTopLevelOverrideKeys = [ "channel", "category", + "thread", + "is_thread", "level", "user", "role", From 497d334b15534e3ef623052eebf0d69e20bf4de2 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 20:58:50 +0300 Subject: [PATCH 158/191] Fix channel/category/thread matching for threads in Automod overrides --- backend/src/plugins/Automod/functions/runAutomod.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/Automod/functions/runAutomod.ts b/backend/src/plugins/Automod/functions/runAutomod.ts index cba8f029..edc75c13 100644 --- a/backend/src/plugins/Automod/functions/runAutomod.ts +++ b/backend/src/plugins/Automod/functions/runAutomod.ts @@ -1,4 +1,4 @@ -import { Snowflake, TextChannel } from "discord.js"; +import { Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { availableActions } from "../actions/availableActions"; import { CleanAction } from "../actions/clean"; @@ -11,8 +11,14 @@ export async function runAutomod(pluginData: GuildPluginData, const userId = context.user?.id || context.member?.id || context.message?.user_id; const user = context.user || (userId && pluginData.client.users!.cache.get(userId as Snowflake)); const member = context.member || (userId && pluginData.guild.members.cache.get(userId as Snowflake)) || null; - const channelId = context.message?.channel_id; - const channel = channelId ? (pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel) : null; + + const channelIdOrThreadId = context.message?.channel_id; + const channelOrThread = channelIdOrThreadId + ? (pluginData.guild.channels.cache.get(channelIdOrThreadId as Snowflake) as TextChannel | ThreadChannel) + : null; + const channelId = channelOrThread?.isThread() ? channelOrThread.parent?.id : channelIdOrThreadId; + const threadId = channelOrThread?.isThread() ? channelOrThread.id : null; + const channel = channelOrThread?.isThread() ? channelOrThread.parent : channelOrThread; const categoryId = channel?.parentId; // Don't apply Automod on Zeppelin itself @@ -23,6 +29,7 @@ export async function runAutomod(pluginData: GuildPluginData, const config = await pluginData.config.getMatchingConfig({ channelId, categoryId, + threadId, userId, member, }); From 70fb0b5baa7b35d1529ba794d88b3bbb4359bd3e Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 21:01:13 +0300 Subject: [PATCH 159/191] Ignore mime type charset and other extra details in match_mime_type --- backend/src/plugins/Automod/triggers/matchMimeType.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Automod/triggers/matchMimeType.ts b/backend/src/plugins/Automod/triggers/matchMimeType.ts index f38bb8fa..5e00d4b6 100644 --- a/backend/src/plugins/Automod/triggers/matchMimeType.ts +++ b/backend/src/plugins/Automod/triggers/matchMimeType.ts @@ -30,7 +30,8 @@ export const MatchMimeTypeTrigger = automodTrigger()({ if (!attachments) return null; for (const attachment of attachments) { - const { contentType } = attachment; + const { contentType: rawContentType } = attachment; + const contentType = (rawContentType || "").split(";")[0]; // Remove "; charset=utf8" and similar from the end const blacklist = trigger.blacklist_enabled ? (trigger.mime_type_blacklist ?? []).map(_t => _t.toLowerCase()) From 82ca94e8c9143cac8fdefb7f1e3507b213b8a404 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 23:46:40 +0300 Subject: [PATCH 160/191] Limit which changed props are included in update logs --- .../Logs/events/LogsChannelModifyEvts.ts | 15 ++++++++++++++- .../events/LogsEmojiAndStickerModifyEvts.ts | 14 ++++++++++++-- .../plugins/Logs/events/LogsRoleModifyEvts.ts | 8 +++++++- .../Logs/events/LogsStageInstanceModifyEvts.ts | 17 +++++++++++++++-- .../plugins/Logs/events/LogsThreadModifyEvts.ts | 8 +++++++- backend/src/utils/filterObject.ts | 15 +++++++++------ 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts index f3a29c2c..33bd7df9 100644 --- a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts @@ -5,6 +5,8 @@ import { logsEvt } from "../types"; import { logChannelCreate } from "../logFunctions/logChannelCreate"; import { logChannelDelete } from "../logFunctions/logChannelDelete"; import { logChannelUpdate } from "../logFunctions/logChannelUpdate"; +import { TextChannel, VoiceChannel } from "discord.js"; +import { filterObject } from "../../../utils/filterObject"; export const LogsChannelCreateEvt = logsEvt({ event: "channelCreate", @@ -26,6 +28,15 @@ export const LogsChannelDeleteEvt = logsEvt({ }, }); +const validChannelDiffProps: Set = new Set([ + "name", + "parentId", + "nsfw", + "rateLimitPerUser", + "topic", + "bitrate", +]); + export const LogsChannelUpdateEvt = logsEvt({ event: "channelUpdate", @@ -34,7 +45,9 @@ export const LogsChannelUpdateEvt = logsEvt({ return; } - const diff = getScalarDifference(meta.args.oldChannel, meta.args.newChannel); + const oldChannelDiffProps = filterObject(meta.args.oldChannel || {}, (v, k) => validChannelDiffProps.has(k)); + const newChannelDiffProps = filterObject(meta.args.newChannel, (v, k) => validChannelDiffProps.has(k)); + const diff = getScalarDifference(oldChannelDiffProps, newChannelDiffProps); const differenceString = differenceToString(diff); if (differenceString.trim() === "") { diff --git a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts index 9832ab41..29b8eb61 100644 --- a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts @@ -6,6 +6,8 @@ import { logEmojiUpdate } from "../logFunctions/logEmojiUpdate"; import { logStickerCreate } from "../logFunctions/logStickerCreate"; import { logStickerDelete } from "../logFunctions/logStickerDelete"; import { logStickerUpdate } from "../logFunctions/logStickerUpdate"; +import { Emoji, GuildEmoji, Sticker, ThreadChannel } from "discord.js"; +import { filterObject } from "../../../utils/filterObject"; export const LogsEmojiCreateEvt = logsEvt({ event: "emojiCreate", @@ -27,11 +29,15 @@ export const LogsEmojiDeleteEvt = logsEvt({ }, }); +const validEmojiDiffProps: Set = new Set(["name"]); + export const LogsEmojiUpdateEvt = logsEvt({ event: "emojiUpdate", async listener(meta) { - const diff = getScalarDifference(meta.args.oldEmoji, meta.args.newEmoji); + const oldEmojiDiffProps = filterObject(meta.args.oldEmoji || {}, (v, k) => validEmojiDiffProps.has(k)); + const newEmojiDiffProps = filterObject(meta.args.newEmoji, (v, k) => validEmojiDiffProps.has(k)); + const diff = getScalarDifference(oldEmojiDiffProps, newEmojiDiffProps); const differenceString = differenceToString(diff); if (differenceString === "") { @@ -66,11 +72,15 @@ export const LogsStickerDeleteEvt = logsEvt({ }, }); +const validStickerDiffProps: Set = new Set(["name"]); + export const LogsStickerUpdateEvt = logsEvt({ event: "stickerUpdate", async listener(meta) { - const diff = getScalarDifference(meta.args.oldSticker, meta.args.newSticker); + const oldStickerDiffProps = filterObject(meta.args.oldSticker || {}, (v, k) => validStickerDiffProps.has(k)); + const newStickerDiffProps = filterObject(meta.args.newSticker, (v, k) => validStickerDiffProps.has(k)); + const diff = getScalarDifference(oldStickerDiffProps, newStickerDiffProps); const differenceString = differenceToString(diff); if (differenceString === "") { diff --git a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts index 0fea7c3f..81d6e2bf 100644 --- a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts @@ -5,6 +5,8 @@ import { logsEvt } from "../types"; import { logRoleCreate } from "../logFunctions/logRoleCreate"; import { logRoleDelete } from "../logFunctions/logRoleDelete"; import { logRoleUpdate } from "../logFunctions/logRoleUpdate"; +import { GuildEmoji, Role } from "discord.js"; +import { filterObject } from "../../../utils/filterObject"; export const LogsRoleCreateEvt = logsEvt({ event: "roleCreate", @@ -26,11 +28,15 @@ export const LogsRoleDeleteEvt = logsEvt({ }, }); +const validRoleDiffProps: Set = new Set(["name", "hoist", "color", "mentionable"]); + export const LogsRoleUpdateEvt = logsEvt({ event: "roleUpdate", async listener(meta) { - const diff = getScalarDifference(meta.args.oldRole, meta.args.newRole); + const oldRoleDiffProps = filterObject(meta.args.oldRole || {}, (v, k) => validRoleDiffProps.has(k)); + const newRoleDiffProps = filterObject(meta.args.newRole, (v, k) => validRoleDiffProps.has(k)); + const diff = getScalarDifference(oldRoleDiffProps, newRoleDiffProps); const differenceString = differenceToString(diff); logRoleUpdate(meta.pluginData, { diff --git a/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts index d964c672..f9ddb54a 100644 --- a/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts @@ -3,9 +3,10 @@ import { differenceToString, getScalarDifference } from "../../../utils"; import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; import { logsEvt } from "../types"; import { logStageInstanceCreate } from "../logFunctions/logStageInstanceCreate"; -import { StageChannel } from "discord.js"; +import { Role, StageChannel, StageInstance } from "discord.js"; import { logStageInstanceDelete } from "../logFunctions/logStageInstanceDelete"; import { logStageInstanceUpdate } from "../logFunctions/logStageInstanceUpdate"; +import { filterObject } from "../../../utils/filterObject"; export const LogsStageInstanceCreateEvt = logsEvt({ event: "stageInstanceCreate", @@ -37,6 +38,12 @@ export const LogsStageInstanceDeleteEvt = logsEvt({ }, }); +const validStageInstanceDiffProps: Set = new Set([ + "topic", + "privacyLevel", + "discoverableDisabled", +]); + export const LogsStageInstanceUpdateEvt = logsEvt({ event: "stageInstanceUpdate", @@ -45,7 +52,13 @@ export const LogsStageInstanceUpdateEvt = logsEvt({ meta.args.newStageInstance.channel ?? ((await meta.pluginData.guild.channels.fetch(meta.args.newStageInstance.channelId)) as StageChannel); - const diff = getScalarDifference(meta.args.oldStageInstance, meta.args.newStageInstance); + const oldStageInstanceDiffProps = filterObject(meta.args.oldStageInstance || {}, (v, k) => + validStageInstanceDiffProps.has(k), + ); + const newStageInstanceDiffProps = filterObject(meta.args.newStageInstance, (v, k) => + validStageInstanceDiffProps.has(k), + ); + const diff = getScalarDifference(oldStageInstanceDiffProps, newStageInstanceDiffProps); const differenceString = differenceToString(diff); logStageInstanceUpdate(meta.pluginData, { diff --git a/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts index 7fb3dcf6..b760d21d 100644 --- a/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts @@ -5,6 +5,8 @@ import { logsEvt } from "../types"; import { logThreadCreate } from "../logFunctions/logThreadCreate"; import { logThreadDelete } from "../logFunctions/logThreadDelete"; import { logThreadUpdate } from "../logFunctions/logThreadUpdate"; +import { TextChannel, ThreadChannel, VoiceChannel } from "discord.js"; +import { filterObject } from "../../../utils/filterObject"; export const LogsThreadCreateEvt = logsEvt({ event: "threadCreate", @@ -26,11 +28,15 @@ export const LogsThreadDeleteEvt = logsEvt({ }, }); +const validThreadDiffProps: Set = new Set(["name", "autoArchiveDuration", "rateLimitPerUser"]); + export const LogsThreadUpdateEvt = logsEvt({ event: "threadUpdate", async listener(meta) { - const diff = getScalarDifference(meta.args.oldThread, meta.args.newThread, ["messageCount", "archiveTimestamp"]); + const oldThreadDiffProps = filterObject(meta.args.oldThread || {}, (v, k) => validThreadDiffProps.has(k)); + const newThreadDiffProps = filterObject(meta.args.newThread, (v, k) => validThreadDiffProps.has(k)); + const diff = getScalarDifference(oldThreadDiffProps, newThreadDiffProps); const differenceString = differenceToString(diff); logThreadUpdate(meta.pluginData, { diff --git a/backend/src/utils/filterObject.ts b/backend/src/utils/filterObject.ts index f5fd6de0..0456ebcb 100644 --- a/backend/src/utils/filterObject.ts +++ b/backend/src/utils/filterObject.ts @@ -1,13 +1,16 @@ +type FilterResult = { + [K in keyof T]?: T[K]; +}; + /** * Filter an object's properties based on its values and keys * @return New object with filtered properties */ -export function filterObject>( +export function filterObject( object: T, filterFn: (value: T[K], key: K) => boolean, -): Record { - return Object.fromEntries(Object.entries(object).filter(([key, value]) => filterFn(value as any, key))) as Record< - keyof T, - T[keyof T] - >; +): FilterResult { + return Object.fromEntries( + Object.entries(object).filter(([key, value]) => filterFn(value as any, key as keyof T)), + ) as FilterResult; } From c33f1449444ce61b0ed886adbae62f4379de3434 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 5 Sep 2021 23:49:35 +0300 Subject: [PATCH 161/191] Fix starboard reactions within threads --- .../src/plugins/Starboard/events/StarboardReactionAddEvt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index 99dbbaa0..992713bf 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -29,9 +29,9 @@ export const StarboardReactionAddEvt = starboardEvt({ if (!member || member!.user.bot) return; const config = await pluginData.config.getMatchingConfig({ + userId, member, - channelId: msg.channel.id, - categoryId: (msg.channel as TextChannel).parentId, + message: msg, }); const boardLock = await pluginData.locks.acquire(allStarboardsLock()); From 8dfa9aec2a9f1ccf9c9598209f3ebf7406ceda61 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Mon, 6 Sep 2021 00:55:35 +0300 Subject: [PATCH 162/191] Fix !nickname crash --- backend/src/plugins/Utility/commands/NicknameCmd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Utility/commands/NicknameCmd.ts b/backend/src/plugins/Utility/commands/NicknameCmd.ts index 75f6699d..f689a6af 100644 --- a/backend/src/plugins/Utility/commands/NicknameCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameCmd.ts @@ -20,7 +20,7 @@ export const NicknameCmd = utilityCmd({ if (!args.member.nickname) { msg.channel.send(`<@!${args.member.id}> does not have a nickname`); } else { - msg.channel.send(`The nickname of <@!${args.member.id}> is **${Util.escapeBold(args.nickname)}**`); + msg.channel.send(`The nickname of <@!${args.member.id}> is **${Util.escapeBold(args.member.nickname)}**`); } return; } From a3669a2eeb3c995add979e77b3b1e6730702b97c Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:46:28 +0300 Subject: [PATCH 163/191] Fix !clean performance --- .../src/plugins/Utility/commands/CleanCmd.ts | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 185f2036..7a852721 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -12,10 +12,12 @@ import { DAYS, getInviteCodesInString, noop, SECONDS } from "../../../utils"; import { utilityCmd, UtilityPluginType } from "../types"; import { boolean, number } from "io-ts"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { humanizeDurationShort } from "../../../humanizeDurationShort"; const MAX_CLEAN_COUNT = 150; const MAX_CLEAN_TIME = 1 * DAYS; -const CLEAN_COMMAND_DELETE_DELAY = 5 * SECONDS; +const MAX_CLEAN_API_REQUESTS = 20; +const CLEAN_COMMAND_DELETE_DELAY = 10 * SECONDS; export async function cleanMessages( pluginData: GuildPluginData, @@ -102,47 +104,41 @@ export async function cleanCmd(pluginData: GuildPluginData, a const cleaningMessage = msg.channel.send("Cleaning..."); - const messagesToClean: SavedMessage[] = []; + const messagesToClean: Message[] = []; let beforeId = msg.id; const timeCutoff = msg.createdTimestamp - MAX_CLEAN_TIME; const upToMsgId = args["to-id"]; let foundId = false; const deletePins = args["delete-pins"] != null ? args["delete-pins"] : false; - let pins: Message[] = []; + let pinIds: Set = new Set(); if (!deletePins) { - pins = [...(await msg.channel.messages.fetchPinned().catch(() => [])).values()]; + pinIds = new Set((await msg.channel.messages.fetchPinned()).keys()); } + let note: string | null = null; + let requests = 0; while (messagesToClean.length < args.count) { const potentialMessages = await targetChannel.messages.fetch({ before: beforeId, - limit: Math.min(args.count, 100), + limit: 100, }); if (potentialMessages.size === 0) break; - const existingStored = await pluginData.state.savedMessages.getMultiple([...potentialMessages.keys()]); - const alreadyStored = existingStored.map(stored => stored.id); - const messagesToStore = [ - ...potentialMessages.filter(potentialMsg => !alreadyStored.includes(potentialMsg.id)).values(), - ]; - await pluginData.state.savedMessages.createFromMessages(messagesToStore); + requests++; - const potentialMessagesToClean = await pluginData.state.savedMessages.getMultiple([...potentialMessages.keys()]); - if (potentialMessagesToClean.length === 0) break; - - const filtered: SavedMessage[] = []; - for (const message of potentialMessagesToClean) { - const contentString = message.data.content || ""; - if (args.user && message.user_id !== args.user) continue; - if (args.bots && !message.is_bot) continue; - if (!deletePins && pins.find(x => x.id === message.id) != null) continue; + const filtered: Message[] = []; + for (const message of potentialMessages.values()) { + const contentString = message.content || ""; + if (args.user && message.author.id !== args.user) continue; + if (args.bots && !message.author.bot) continue; + if (!deletePins && pinIds.has(message.id)) continue; if (args["has-invites"] && getInviteCodesInString(contentString).length === 0) continue; if (upToMsgId != null && message.id < upToMsgId) { foundId = true; break; } - if (moment.utc(message.posted_at).valueOf() < timeCutoff) continue; + if (message.createdTimestamp < timeCutoff) continue; if (args.match && !(await pluginData.state.regexRunner.exec(args.match, contentString).catch(allowTimeout))) { continue; } @@ -155,16 +151,38 @@ export async function cleanCmd(pluginData: GuildPluginData, a beforeId = potentialMessages.lastKey()!; - if (foundId || moment.utc(potentialMessages.last()!.createdTimestamp).valueOf() < timeCutoff) { + if (foundId) { break; } + + if (messagesToClean.length < args.count) { + if (potentialMessages.last()!.createdTimestamp < timeCutoff) { + note = `stopped looking after reaching ${humanizeDurationShort(MAX_CLEAN_TIME)} old messages`; + break; + } + + if (requests >= MAX_CLEAN_API_REQUESTS) { + note = `stopped looking after ${requests * 100} messages`; + break; + } + } } let responseMsg: Message | undefined; if (messagesToClean.length > 0) { - const cleanResult = await cleanMessages(pluginData, targetChannel, messagesToClean, msg.author); + // Save to-be-deleted messages that were missing from the database + const existingStored = await pluginData.state.savedMessages.getMultiple(messagesToClean.map(m => m.id)); + const alreadyStored = existingStored.map(stored => stored.id); + const messagesToStore = messagesToClean.filter(potentialMsg => !alreadyStored.includes(potentialMsg.id)); + await pluginData.state.savedMessages.createFromMessages(messagesToStore); + + const savedMessagesToClean = await pluginData.state.savedMessages.getMultiple(messagesToClean.map(m => m.id)); + const cleanResult = await cleanMessages(pluginData, targetChannel, savedMessagesToClean, msg.author); let responseText = `Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`; + if (note) { + responseText += ` (${note})`; + } if (targetChannel.id !== msg.channel.id) { responseText += ` in <#${targetChannel.id}>: ${cleanResult.archiveUrl}`; } @@ -184,7 +202,8 @@ export async function cleanCmd(pluginData: GuildPluginData, a responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText); } else { - responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`); + const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; + responseMsg = await sendErrorMessage(pluginData, msg.channel, responseText); } await (await cleaningMessage).delete(); From 0b7a5dbfbc36ba090bb2731e48639de8d6ac473a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:46:48 +0300 Subject: [PATCH 164/191] Enable source maps in backend --- backend/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index 685637b7..3ecd8bbc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,11 +7,11 @@ "watch": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node start-dev.js\"", "watch-yaml-parse-test": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node dist/backend/src/yamlParseTest.js\"", "build": "rimraf dist && tsc", - "start-bot-dev": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --inspect=0.0.0.0:9229 dist/backend/src/index.js", - "start-bot-prod": "cross-env NODE_ENV=production node -r ./register-tsconfig-paths.js --unhandled-rejections=strict dist/backend/src/index.js", + "start-bot-dev": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps --inspect=0.0.0.0:9229 dist/backend/src/index.js", + "start-bot-prod": "cross-env NODE_ENV=production node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/index.js", "watch-bot": "cross-env NODE_ENV=development tsc-watch --onSuccess \"npm run start-bot-dev\"", - "start-api-dev": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --inspect=0.0.0.0:9239 dist/backend/src/api/index.js", - "start-api-prod": "cross-env NODE_ENV=production node -r ./register-tsconfig-paths.js --unhandled-rejections=strict dist/backend/src/api/index.js", + "start-api-dev": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps --inspect=0.0.0.0:9239 dist/backend/src/api/index.js", + "start-api-prod": "cross-env NODE_ENV=production node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/api/index.js", "watch-api": "cross-env NODE_ENV=development tsc-watch --onSuccess \"npm run start-api-dev\"", "typeorm": "node -r ./register-tsconfig-paths.js ./node_modules/typeorm/cli.js", "migrate-prod": "npm run typeorm -- migration:run", From 001b6d00ea4fb86e2d5cbae45005a40f2c7bafbc Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:57:06 +0300 Subject: [PATCH 165/191] Batch embed logs. Always use log batching. --- backend/src/plugins/Logs/LogsPlugin.ts | 2 +- backend/src/plugins/Logs/types.ts | 5 +- backend/src/plugins/Logs/util/log.ts | 173 +++++++++++++------------ backend/src/utils/MessageBuffer.ts | 131 +++++++++++++++++++ 4 files changed, 225 insertions(+), 86 deletions(-) create mode 100644 backend/src/utils/MessageBuffer.ts diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index 03423404..a6eb8443 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -266,7 +266,7 @@ export const LogsPlugin = zeppelinGuildPlugin()({ state.archives = GuildArchives.getGuildInstance(guild.id); state.cases = GuildCases.getGuildInstance(guild.id); - state.batches = new Map(); + state.buffers = new Map(); state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`); }, diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index c952349d..39d9d440 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -6,7 +6,7 @@ import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { RegExpRunner } from "../../RegExpRunner"; -import { tMessageContent, tNullable } from "../../utils"; +import { StrictMessageContent, tMessageContent, tNullable } from "../../utils"; import { TRegex } from "../../validatorUtils"; import { LogType } from "../../data/LogType"; import { GuildMember } from "discord.js"; @@ -28,6 +28,7 @@ import { TemplateSafeValueContainer, TypedTemplateSafeValueContainer, } from "../../templateFormatter"; +import { MessageBuffer } from "../../utils/MessageBuffer"; export const tLogFormats = t.record(t.string, t.union([t.string, tMessageContent])); export type TLogFormats = t.TypeOf; @@ -85,7 +86,7 @@ export interface LogsPluginType extends BasePluginType { logListener; - batches: Map; + buffers: Map; onMessageDeleteFn; onMessageDeleteBulkFn; diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index 45205882..0afc1e1d 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -1,12 +1,11 @@ -import { MessageMentionTypes, Snowflake, TextChannel } from "discord.js"; +import { MessageEmbedOptions, MessageMentionTypes, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; import { allowTimeout } from "../../../RegExpRunner"; -import { createChunkedMessage, get, noop } from "../../../utils"; -import { ILogTypeData, LogsPluginType, LogTypeData, TLogChannelMap } from "../types"; +import { ILogTypeData, LogsPluginType, TLogChannel, TLogChannelMap } from "../types"; import { getLogMessage } from "./getLogMessage"; -import { TemplateSafeValueContainer, TypedTemplateSafeValueContainer } from "../../../templateFormatter"; +import { TypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { LogType } from "../../../data/LogType"; +import { MessageBuffer } from "../../../utils/MessageBuffer"; const excludedUserProps = ["user", "member", "mod"]; const excludedRoleProps = ["message.member.roles", "member.roles"]; @@ -24,6 +23,53 @@ interface ExclusionData { messageTextContent?: string | null; } +const DEFAULT_BATCH_TIME = 1000; +const MIN_BATCH_TIME = 250; +const MAX_BATCH_TIME = 5000; + +async function shouldExclude( + pluginData: GuildPluginData, + opts: TLogChannel, + exclusionData: ExclusionData, +): Promise { + if (opts.excluded_users && exclusionData.userId && opts.excluded_users.includes(exclusionData.userId)) { + return true; + } + + if (opts.exclude_bots && exclusionData.bot) { + return true; + } + + if (opts.excluded_roles && exclusionData.roles) { + for (const role of exclusionData.roles) { + if (opts.excluded_roles.includes(role)) { + return true; + } + } + } + + if (opts.excluded_channels && exclusionData.channel && opts.excluded_channels.includes(exclusionData.channel)) { + return true; + } + + if (opts.excluded_categories && exclusionData.category && opts.excluded_categories.includes(exclusionData.category)) { + return true; + } + + if (opts.excluded_message_regexes && exclusionData.messageTextContent) { + for (const regex of opts.excluded_message_regexes) { + const matches = await pluginData.state.regexRunner + .exec(regex, exclusionData.messageTextContent) + .catch(allowTimeout); + if (matches) { + return true; + } + } + } + + return false; +} + export async function log( pluginData: GuildPluginData, type: TLogType, @@ -36,86 +82,47 @@ export async function log( logChannelLoop: for (const [channelId, opts] of Object.entries(logChannels)) { const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) continue; + if (opts.include?.length && !opts.include.includes(typeStr)) continue; + if (opts.exclude && opts.exclude.includes(typeStr)) continue; + if (await shouldExclude(pluginData, opts, exclusionData)) continue; - 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 && exclusionData.userId && opts.excluded_users.includes(exclusionData.userId)) { - continue; - } + const message = await getLogMessage(pluginData, type, data, { + format: opts.format, + include_embed_timestamp: opts.include_embed_timestamp, + timestamp_format: opts.timestamp_format, + }); + if (!message) return; - // If we're excluding bots and the logged user is a bot, skip it - 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_channels && exclusionData.channel && opts.excluded_channels.includes(exclusionData.channel)) { - continue; - } - - if ( - opts.excluded_categories && - exclusionData.category && - opts.excluded_categories.includes(exclusionData.category) - ) { - continue; - } - - if (opts.excluded_message_regexes && exclusionData.messageTextContent) { - for (const regex of opts.excluded_message_regexes) { - const matches = await pluginData.state.regexRunner - .exec(regex, exclusionData.messageTextContent) - .catch(allowTimeout); - if (matches) { - continue logChannelLoop; - } - } - } - - const message = await getLogMessage(pluginData, type, data, { - format: opts.format, - include_embed_timestamp: opts.include_embed_timestamp, - timestamp_format: opts.timestamp_format, - }); - - if (message) { - // For non-string log messages (i.e. embeds) batching or chunking is not possible, so send them immediately - if (typeof message !== "string") { - await channel.send(message).catch(noop); - return; - } - - // Default to batched unless explicitly disabled - const batched = opts.batched ?? true; - const batchTime = opts.batch_time ?? 1000; - const cfg = pluginData.config.get(); - const parse: MessageMentionTypes[] = cfg.allow_user_mentions ? ["users"] : []; - - if (batched) { - // If we're batching log messages, gather all log messages within the set batch_time into a single message - if (!pluginData.state.batches.has(channel.id)) { - pluginData.state.batches.set(channel.id, []); - setTimeout(async () => { - const batchedMessage = pluginData.state.batches.get(channel.id)!.join("\n"); - pluginData.state.batches.delete(channel.id); - createChunkedMessage(channel, batchedMessage, { parse }).catch(noop); - }, batchTime); - } - - pluginData.state.batches.get(channel.id)!.push(message); - } else { - // If we're not batching log messages, just send them immediately - await createChunkedMessage(channel, message, { parse }).catch(noop); - } - } + // Initialize message buffer for this channel + if (!pluginData.state.buffers.has(channelId)) { + const batchTime = Math.min(Math.max(opts.batch_time ?? DEFAULT_BATCH_TIME, MIN_BATCH_TIME), MAX_BATCH_TIME); + pluginData.state.buffers.set( + channelId, + new MessageBuffer({ + timeout: batchTime, + consume: part => { + const parse: MessageMentionTypes[] = pluginData.config.get().allow_user_mentions ? ["users"] : []; + channel + .send({ + ...part, + allowedMentions: { parse }, + }) + .catch(err => { + // tslint:disable-next-line:no-console + console.warn( + `Error while sending ${typeStr} log to ${pluginData.guild.id}/${channelId}: ${err.message}`, + ); + }); + }, + }), + ); } + + // Add log message to buffer + const buffer = pluginData.state.buffers.get(channelId)!; + buffer.push({ + content: typeof message === "string" ? message : message.content || "", + embeds: typeof message === "string" ? [] : ((message.embeds || []) as MessageEmbedOptions[]), + }); } } diff --git a/backend/src/utils/MessageBuffer.ts b/backend/src/utils/MessageBuffer.ts new file mode 100644 index 00000000..06000d87 --- /dev/null +++ b/backend/src/utils/MessageBuffer.ts @@ -0,0 +1,131 @@ +import { StrictMessageContent } from "../utils"; +import Timeout = NodeJS.Timeout; + +type ConsumeFn = (part: StrictMessageContent) => void; + +type ContentType = "mixed" | "plain" | "embeds"; + +export type MessageBufferContent = Pick; + +type Chunk = { + type: ContentType; + content: MessageBufferContent; +}; + +export interface MessageBufferOpts { + consume?: ConsumeFn; + timeout?: number; +} + +const MAX_CHARS_PER_MESSAGE = 2000; +const MAX_EMBEDS_PER_MESSAGE = 10; + +/** + * Allows buffering and automatic partitioning of message contents. Useful for e.g. high volume log channels, message chunking, etc. + */ +export class MessageBuffer { + protected autoConsumeFn: ConsumeFn | null = null; + + protected timeoutMs: number | null = null; + + protected chunk: Chunk | null = null; + + protected chunkTimeout: Timeout | null = null; + + protected finalizedChunks: MessageBufferContent[] = []; + + constructor(opts: MessageBufferOpts = {}) { + if (opts.consume) { + this.autoConsumeFn = opts.consume; + } + + if (opts.timeout) { + this.timeoutMs = opts.timeout; + } + } + + push(content: MessageBufferContent): void { + let contentType: ContentType; + if (content.content && !content.embeds?.length) { + contentType = "plain"; + } else if (content.embeds?.length && !content.content) { + contentType = "embeds"; + } else { + contentType = "mixed"; + } + + // Plain text can't be merged with mixed or embeds + if (contentType === "plain" && this.chunk && this.chunk.type !== "plain") { + this.startNewChunk(contentType); + } + // Mixed can't be merged at all + if (contentType === "mixed" && this.chunk) { + this.startNewChunk(contentType); + } + + if (!this.chunk) this.startNewChunk(contentType); + const chunk = this.chunk!; + + if (content.content) { + if (chunk.content.content && chunk.content.content.length + content.content.length > MAX_CHARS_PER_MESSAGE) { + this.startNewChunk(contentType); + } + + if (chunk.content.content == null) chunk.content.content = ""; + chunk.content.content += content.content; + } + + if (content.embeds) { + if (chunk.content.embeds && chunk.content.embeds.length + content.embeds.length > MAX_EMBEDS_PER_MESSAGE) { + this.startNewChunk(contentType); + } + + if (chunk.content.embeds == null) chunk.content.embeds = []; + chunk.content.embeds.push(...content.embeds); + } + } + + protected startNewChunk(type: ContentType): void { + if (this.chunk) { + this.finalizeChunk(); + } + this.chunk = { + type, + content: {}, + }; + if (this.timeoutMs) { + this.chunkTimeout = setTimeout(() => this.finalizeChunk(), this.timeoutMs); + } + } + + protected finalizeChunk(): void { + if (!this.chunk) return; + const chunk = this.chunk; + this.chunk = null; + + if (this.chunkTimeout) { + clearTimeout(this.chunkTimeout); + this.chunkTimeout = null; + } + + // Discard empty chunks + if (!chunk.content.content && !chunk.content.embeds?.length) return; + + if (this.autoConsumeFn) { + this.autoConsumeFn(chunk.content); + return; + } + + this.finalizedChunks.push(chunk.content); + } + + consume(): StrictMessageContent[] { + return Array.from(this.finalizedChunks); + this.finalizedChunks = []; + } + + finalizeAndConsume(): StrictMessageContent[] { + this.finalizeChunk(); + return this.consume(); + } +} From ff2a7b38f3a192ba4cf2c42cf643a52cf280f931 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:57:20 +0300 Subject: [PATCH 166/191] Fix duplicate entries in !help --- backend/src/plugins/Utility/commands/HelpCmd.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/plugins/Utility/commands/HelpCmd.ts b/backend/src/plugins/Utility/commands/HelpCmd.ts index 112e932e..5966cbc9 100644 --- a/backend/src/plugins/Utility/commands/HelpCmd.ts +++ b/backend/src/plugins/Utility/commands/HelpCmd.ts @@ -34,6 +34,7 @@ export const HelpCmd = utilityCmd({ plugin, command: registeredCommand, }); + break; } } } From 9d9f1d02de2e9326241124c3b04ecb43df57561d Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 19:01:55 +0300 Subject: [PATCH 167/191] Upgrade to Prettier v2 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd9fbc39..c0f6c73e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "devDependencies": { "husky": "^3.0.9", "lint-staged": "^9.4.2", - "prettier": "^1.19.1", + "prettier": "^2.4.0", "tslint": "^5.13.1", "tslint-config-prettier": "^1.18.0", "typescript": "^4.3.4" @@ -1794,15 +1794,15 @@ } }, "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, "node_modules/pump": { @@ -3747,9 +3747,9 @@ } }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", "dev": true }, "pump": { diff --git a/package.json b/package.json index 2178161f..6657015e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "devDependencies": { "husky": "^3.0.9", "lint-staged": "^9.4.2", - "prettier": "^1.19.1", + "prettier": "^2.4.0", "tslint": "^5.13.1", "tslint-config-prettier": "^1.18.0", "typescript": "^4.3.4" From 419d7f6ab44ca80b263a00c113664db338ff8d90 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 19:04:01 +0300 Subject: [PATCH 168/191] Update prettier target files, use .prettierignore --- .prettierignore | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7572a504 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.github +.idea +dist +node_modules +assets +debug diff --git a/package.json b/package.json index 6657015e..1511e040 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "", "private": true, "scripts": { - "format": "prettier --write \"./{backend,dashboard}/{,!(node_modules)/**/}/*.ts\"", + "format": "prettier --write './**/*.{css,html,js,json,ts,tsx}'", "lint": "tslint \"./{backend,dashboard}/{,!(node_modules)/**/}/*.ts\"", - "codestyle-check": "prettier --check \"./{backend,dashboard}/{,!(node_modules)/**/}/*.ts\"" + "codestyle-check": "prettier --check './**/*.{css,html,js,json,ts,tsx}'" }, "devDependencies": { "husky": "^3.0.9", From 0cde0d46d2f0f3a39f6d320fd5a3707074ea5e51 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 19:06:01 +0300 Subject: [PATCH 169/191] Update .prettierignore, use subproject specific ignore files --- .prettierignore | 5 ++--- backend/.prettierignore | 1 + dashboard/.prettierignore | 1 + presetup-configurator/.prettierignore | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 backend/.prettierignore create mode 100644 dashboard/.prettierignore create mode 100644 presetup-configurator/.prettierignore diff --git a/.prettierignore b/.prettierignore index 7572a504..f0db6e8f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ .github .idea -dist node_modules -assets -debug +/assets +/debug diff --git a/backend/.prettierignore b/backend/.prettierignore new file mode 100644 index 00000000..9b1c8b13 --- /dev/null +++ b/backend/.prettierignore @@ -0,0 +1 @@ +/dist diff --git a/dashboard/.prettierignore b/dashboard/.prettierignore new file mode 100644 index 00000000..9b1c8b13 --- /dev/null +++ b/dashboard/.prettierignore @@ -0,0 +1 @@ +/dist diff --git a/presetup-configurator/.prettierignore b/presetup-configurator/.prettierignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/presetup-configurator/.prettierignore @@ -0,0 +1 @@ +/build From ac79eb09f5025141d9c060a9db26253f99259c5f Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 11 Sep 2021 19:06:51 +0300 Subject: [PATCH 170/191] Reformat all files with Prettier --- backend/ormconfig.js | 30 +-- backend/register-tsconfig-paths.js | 6 +- backend/src/Queue.ts | 4 +- backend/src/QueuedEventEmitter.ts | 2 +- backend/src/RegExpRunner.ts | 2 +- backend/src/api/auth.ts | 6 +- backend/src/api/docs.ts | 10 +- backend/src/api/guilds.ts | 2 +- backend/src/configValidator.ts | 2 +- backend/src/data/ApiLogins.ts | 10 +- backend/src/data/ApiUserInfo.ts | 2 +- backend/src/data/Configs.ts | 8 +- backend/src/data/GuildArchives.ts | 4 +- backend/src/data/GuildCases.ts | 4 +- backend/src/data/GuildCounters.ts | 54 ++--- backend/src/data/GuildLogs.ts | 4 +- backend/src/data/GuildMemberTimezones.ts | 2 +- backend/src/data/GuildMutes.ts | 16 +- backend/src/data/GuildNicknameHistory.ts | 2 +- backend/src/data/GuildSavedMessages.ts | 8 +- backend/src/data/GuildScheduledPosts.ts | 5 +- backend/src/data/GuildSlowmodes.ts | 5 +- backend/src/data/GuildTempbans.ts | 10 +- backend/src/data/GuildVCAlerts.ts | 5 +- backend/src/data/UsernameHistory.ts | 2 +- backend/src/data/cleanup/configs.ts | 14 +- backend/src/data/cleanup/messages.ts | 17 +- backend/src/data/cleanup/nicknames.ts | 7 +- backend/src/data/cleanup/usernames.ts | 7 +- backend/src/data/db.ts | 4 +- backend/src/data/entities/ApiLogin.ts | 5 +- .../data/entities/ApiPermissionAssignment.ts | 5 +- backend/src/data/entities/ApiUserInfo.ts | 10 +- backend/src/data/entities/Case.ts | 5 +- backend/src/data/entities/CaseNote.ts | 5 +- backend/src/data/entities/Config.ts | 2 +- backend/src/data/entities/StarboardMessage.ts | 2 +- .../src/data/entities/StarboardReaction.ts | 2 +- backend/src/index.ts | 18 +- ...12501-MigrateUsernamesToNewHistoryTable.ts | 4 +- backend/src/pluginUtils.ts | 8 +- .../plugins/AutoDelete/AutoDeletePlugin.ts | 6 +- .../plugins/AutoDelete/util/deleteNextItem.ts | 2 +- .../AutoDelete/util/onMessageDelete.ts | 2 +- backend/src/plugins/Automod/AutomodPlugin.ts | 6 +- .../src/plugins/Automod/actions/addRoles.ts | 6 +- backend/src/plugins/Automod/actions/alert.ts | 2 +- .../plugins/Automod/actions/archiveThread.ts | 4 +- backend/src/plugins/Automod/actions/ban.ts | 2 +- .../plugins/Automod/actions/changeNickname.ts | 4 +- backend/src/plugins/Automod/actions/kick.ts | 4 +- backend/src/plugins/Automod/actions/log.ts | 2 +- backend/src/plugins/Automod/actions/mute.ts | 2 +- .../plugins/Automod/actions/removeRoles.ts | 6 +- backend/src/plugins/Automod/actions/reply.ts | 6 +- backend/src/plugins/Automod/actions/warn.ts | 4 +- .../functions/clearOldRecentActions.ts | 2 +- .../Automod/functions/clearOldRecentSpam.ts | 2 +- .../functions/clearRecentActionsForMessage.ts | 2 +- .../functions/createMessageSpamTrigger.ts | 6 +- .../Automod/functions/findRecentSpam.ts | 2 +- .../functions/getMatchingRecentActions.ts | 2 +- .../functions/getTextMatchPartialSummary.ts | 2 +- .../Automod/triggers/exampleTrigger.ts | 2 +- .../Automod/triggers/matchAttachmentType.ts | 9 +- .../plugins/Automod/triggers/matchMimeType.ts | 4 +- .../plugins/Automod/triggers/matchWords.ts | 2 +- .../Automod/triggers/memberJoinSpam.ts | 2 +- .../commands/AddDashboardUserCmd.ts | 2 +- .../commands/AddServerFromInviteCmd.ts | 5 +- .../BotControl/commands/AllowServerCmd.ts | 5 +- .../commands/ListDashboardUsersCmd.ts | 2 +- .../BotControl/commands/PerformanceCmd.ts | 2 +- .../commands/RemoveDashboardUserCmd.ts | 2 +- .../plugins/BotControl/commands/ServersCmd.ts | 14 +- .../plugins/Cases/functions/createCaseNote.ts | 2 +- .../functions/getCaseTypeAmountForUserId.ts | 4 +- backend/src/plugins/Censor/CensorPlugin.ts | 4 +- .../plugins/Censor/util/applyFiltersToMsg.ts | 4 +- .../ChannelArchiver/ChannelArchiverPlugin.ts | 2 +- .../commands/ArchiveChannelCmd.ts | 6 +- ...etCompanionChannelOptsForVoiceChannelId.ts | 4 +- .../functions/handleCompanionPermissions.ts | 2 +- .../src/plugins/ContextMenus/actions/mute.ts | 5 +- .../ContextMenus/utils/loadAllCommands.ts | 2 +- .../src/plugins/Counters/CountersPlugin.ts | 2 +- .../Counters/commands/CountersListCmd.ts | 4 +- .../Counters/functions/changeCounterValue.ts | 4 +- .../Counters/functions/decayCounter.ts | 4 +- .../Counters/functions/setCounterValue.ts | 4 +- .../CustomEvents/actions/addRoleAction.ts | 2 +- .../LocateUser/events/BanRemoveAlertsEvt.ts | 2 +- .../LocateUser/events/SendAlertsEvts.ts | 2 +- .../LocateUser/utils/fillAlertsList.ts | 2 +- .../plugins/LocateUser/utils/sendAlerts.ts | 2 +- backend/src/plugins/Logs/LogsPlugin.ts | 6 +- .../plugins/Logs/events/LogsUserUpdateEvts.ts | 8 +- .../Logs/logFunctions/logAutomodAction.ts | 2 +- .../Logs/logFunctions/logMemberRoleAdd.ts | 2 +- .../Logs/logFunctions/logMemberRoleChanges.ts | 4 +- .../Logs/logFunctions/logMemberRoleRemove.ts | 2 +- backend/src/plugins/Logs/types.ts | 5 +- .../src/plugins/Logs/util/getLogMessage.ts | 10 +- backend/src/plugins/Logs/util/log.ts | 4 +- .../plugins/Logs/util/onMessageDeleteBulk.ts | 2 +- .../src/plugins/Logs/util/onMessageUpdate.ts | 8 +- .../MessageSaver/events/SaveMessagesEvts.ts | 8 +- .../ModActions/commands/CasesModCmd.ts | 4 +- .../ModActions/commands/CasesUserCmd.ts | 10 +- .../ModActions/commands/MassUnbanCmd.ts | 10 +- .../ModActions/commands/MassmuteCmd.ts | 2 +- .../functions/clearIgnoredEvents.ts | 2 +- .../functions/formatReasonWithAttachments.ts | 2 +- .../ModActions/functions/isEventIgnored.ts | 2 +- .../Mutes/commands/ClearBannedMutesCmd.ts | 2 +- .../src/plugins/Mutes/commands/MutesCmd.ts | 37 ++-- .../Mutes/functions/clearExpiredMutes.ts | 2 +- .../src/plugins/Mutes/functions/muteUser.ts | 12 +- .../src/plugins/Mutes/functions/unmuteUser.ts | 2 +- .../plugins/NameHistory/commands/NamesCmd.ts | 4 +- .../Post/commands/ScheduledPostsListCmd.ts | 2 +- .../src/plugins/Post/util/actualPostCmd.ts | 12 +- .../ReactionRoles/ReactionRolesPlugin.ts | 2 +- .../commands/InitReactionRolesCmd.ts | 20 +- .../commands/PostButtonRolesCmd.ts | 4 +- .../events/AddReactionRoleEvt.ts | 2 +- .../applyReactionRoleReactionsToMessage.ts | 2 +- .../ReactionRoles/util/runAutoRefresh.ts | 2 +- .../plugins/Reminders/commands/RemindCmd.ts | 5 +- .../plugins/Roles/commands/MassAddRoleCmd.ts | 2 +- .../Roles/commands/MassRemoveRoleCmd.ts | 2 +- .../SelfGrantableRolesPlugin.ts | 4 +- .../SelfGrantableRoles/commands/RoleAddCmd.ts | 8 +- .../commands/RoleRemoveCmd.ts | 8 +- .../util/findMatchingRoles.ts | 2 +- .../util/getApplyingEntries.ts | 2 +- .../util/normalizeRoleNames.ts | 2 +- .../SelfGrantableRoles/util/splitRoleNames.ts | 2 +- .../src/plugins/Slowmode/SlowmodePlugin.ts | 2 +- .../Slowmode/commands/SlowmodeListCmd.ts | 2 +- backend/src/plugins/Spam/SpamPlugin.ts | 2 +- .../Spam/util/clearOldRecentActions.ts | 4 +- .../Spam/util/clearRecentUserActions.ts | 2 +- .../src/plugins/Spam/util/getRecentActions.ts | 2 +- .../Spam/util/logAndDetectMessageSpam.ts | 12 +- .../src/plugins/Starboard/StarboardPlugin.ts | 2 +- .../events/StarboardReactionAddEvt.ts | 6 +- .../util/createStarboardEmbedFromMessage.ts | 5 +- .../util/removeMessageFromStarboard.ts | 2 +- backend/src/plugins/Tags/TagsPlugin.ts | 18 +- .../src/plugins/Tags/commands/TagListCmd.ts | 2 +- .../src/plugins/Tags/util/onMessageCreate.ts | 2 +- .../src/plugins/Tags/util/renderTagBody.ts | 2 +- .../plugins/Tags/util/renderTagFromString.ts | 2 +- .../src/plugins/Utility/commands/AboutCmd.ts | 11 +- .../src/plugins/Utility/commands/CleanCmd.ts | 12 +- .../src/plugins/Utility/commands/HelpCmd.ts | 5 +- .../src/plugins/Utility/commands/RolesCmd.ts | 6 +- .../src/plugins/Utility/commands/VcmoveCmd.ts | 4 +- .../Utility/functions/getChannelInfoEmbed.ts | 8 +- .../Utility/functions/getEmojiInfoEmbed.ts | 2 +- .../Utility/functions/getInviteInfoEmbed.ts | 4 +- .../Utility/functions/getRoleInfoEmbed.ts | 9 +- .../Utility/functions/getServerInfoEmbed.ts | 8 +- .../Utility/functions/getUserInfoEmbed.ts | 8 +- backend/src/plugins/Utility/search.ts | 37 ++-- .../src/plugins/ZeppelinPluginBlueprint.ts | 12 +- backend/src/templateFormatter.test.ts | 18 +- backend/src/templateFormatter.ts | 4 +- backend/src/utils.test.ts | 18 +- backend/src/utils.ts | 71 +++---- backend/src/utils/crypt.test.ts | 2 +- backend/src/utils/normalizeText.test.ts | 8 +- backend/src/utils/parseFuzzyTimezone.ts | 2 +- backend/src/utils/tColor.ts | 4 +- backend/src/utils/tValidTimezone.ts | 4 +- backend/src/utils/templateSafeObjects.ts | 12 +- backend/src/utils/typeUtils.ts | 9 +- .../src/utils/validateNoObjectAliases.test.ts | 6 +- backend/src/utils/waitForInteraction.ts | 2 +- backend/src/validation.test.ts | 2 +- backend/src/validatorUtils.ts | 20 +- backend/start-dev.js | 4 +- backend/tsconfig.json | 12 +- dashboard/.htmlnanorc.js | 2 +- dashboard/src/api.ts | 4 +- dashboard/src/directives/trim-indents.ts | 2 +- dashboard/src/index.html | 32 +-- dashboard/src/splash.html | 2 +- dashboard/src/store/guilds.ts | 6 +- dashboard/tailwind.config.js | 26 +-- dashboard/tsconfig.json | 5 +- dashboard/webpack.config.js | 81 ++++---- presetup-configurator/snowpack.config.js | 6 +- presetup-configurator/src/App.tsx | 10 +- presetup-configurator/src/Configurator.css | 1 - presetup-configurator/src/Configurator.tsx | 47 +++-- presetup-configurator/src/Levels.tsx | 12 +- presetup-configurator/src/LogChannels.tsx | 191 +++++++++--------- presetup-configurator/src/index.html | 26 +-- presetup-configurator/src/index.tsx | 2 +- presetup-configurator/tsconfig.json | 5 +- shared/src/apiPermissions.test.ts | 4 +- shared/src/apiPermissions.ts | 2 +- shared/tsconfig.json | 8 +- tslint.json | 5 +- 206 files changed, 727 insertions(+), 888 deletions(-) diff --git a/backend/ormconfig.js b/backend/ormconfig.js index 9b8b311e..161346a4 100644 --- a/backend/ormconfig.js +++ b/backend/ormconfig.js @@ -1,24 +1,24 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); try { - fs.accessSync(path.resolve(__dirname, 'bot.env')); - require('dotenv').config({ path: path.resolve(__dirname, 'bot.env') }); + fs.accessSync(path.resolve(__dirname, "bot.env")); + require("dotenv").config({ path: path.resolve(__dirname, "bot.env") }); } catch { try { - fs.accessSync(path.resolve(__dirname, 'api.env')); - require('dotenv').config({ path: path.resolve(__dirname, 'api.env') }); + fs.accessSync(path.resolve(__dirname, "api.env")); + require("dotenv").config({ path: path.resolve(__dirname, "api.env") }); } catch { throw new Error("bot.env or api.env required"); } } -const moment = require('moment-timezone'); -moment.tz.setDefault('UTC'); +const moment = require("moment-timezone"); +moment.tz.setDefault("UTC"); -const entities = path.relative(process.cwd(), path.resolve(__dirname, 'dist/backend/src/data/entities/*.js')); -const migrations = path.relative(process.cwd(), path.resolve(__dirname, 'dist/backend/src/migrations/*.js')); -const migrationsDir = path.relative(process.cwd(), path.resolve(__dirname, 'src/migrations')); +const entities = path.relative(process.cwd(), path.resolve(__dirname, "dist/backend/src/data/entities/*.js")); +const migrations = path.relative(process.cwd(), path.resolve(__dirname, "dist/backend/src/migrations/*.js")); +const migrationsDir = path.relative(process.cwd(), path.resolve(__dirname, "src/migrations")); module.exports = { type: "mysql", @@ -26,7 +26,7 @@ module.exports = { username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - charset: 'utf8mb4', + charset: "utf8mb4", supportBigNumbers: true, bigNumberStrings: true, dateStrings: true, @@ -39,13 +39,13 @@ module.exports = { // Pool options extra: { typeCast(field, next) { - if (field.type === 'DATETIME') { + if (field.type === "DATETIME") { const val = field.string(); - return val != null ? moment.utc(val).format('YYYY-MM-DD HH:mm:ss') : null; + return val != null ? moment.utc(val).format("YYYY-MM-DD HH:mm:ss") : null; } return next(); - } + }, }, // Migrations diff --git a/backend/register-tsconfig-paths.js b/backend/register-tsconfig-paths.js index 2cc33ff1..9f182acf 100644 --- a/backend/register-tsconfig-paths.js +++ b/backend/register-tsconfig-paths.js @@ -6,9 +6,9 @@ * https://github.com/TypeStrong/ts-node/pull/254 */ -const path = require('path'); -const tsconfig = require('./tsconfig.json'); -const tsconfigPaths = require('tsconfig-paths'); +const path = require("path"); +const tsconfig = require("./tsconfig.json"); +const tsconfigPaths = require("tsconfig-paths"); // E.g. ./dist/backend const baseUrl = path.resolve(tsconfig.compilerOptions.outDir, path.basename(__dirname)); diff --git a/backend/src/Queue.ts b/backend/src/Queue.ts index ddedc56a..2ca63d2f 100644 --- a/backend/src/Queue.ts +++ b/backend/src/Queue.ts @@ -29,7 +29,7 @@ export class Queue { } public add(fn: TQueueFunction): Promise { - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { this.queue.push(async () => { const result = await fn(); resolve(result); @@ -50,7 +50,7 @@ export class Queue { } const fn = this.queue.shift()!; - new Promise(resolve => { + new Promise((resolve) => { // Either fn() completes or the timeout is reached void fn().then(resolve); setTimeout(resolve, this._timeout); diff --git a/backend/src/QueuedEventEmitter.ts b/backend/src/QueuedEventEmitter.ts index 3da957d3..2402c491 100644 --- a/backend/src/QueuedEventEmitter.ts +++ b/backend/src/QueuedEventEmitter.ts @@ -42,7 +42,7 @@ export class QueuedEventEmitter { const listeners = [...(this.listeners.get(eventName) || []), ...(this.listeners.get("*") || [])]; let promise: Promise = Promise.resolve(); - listeners.forEach(listener => { + listeners.forEach((listener) => { promise = this.queue.add(listener.bind(null, ...args)); }); diff --git a/backend/src/RegExpRunner.ts b/backend/src/RegExpRunner.ts index 96d39be3..1ac5f47c 100644 --- a/backend/src/RegExpRunner.ts +++ b/backend/src/RegExpRunner.ts @@ -27,7 +27,7 @@ const INITIAL_REGEX_TIMEOUT = 5 * SECONDS; const INITIAL_REGEX_TIMEOUT_DURATION = 30 * SECONDS; const FINAL_REGEX_TIMEOUT = 5 * SECONDS; -const regexTimeoutUpgradePromise = new Promise(resolve => setTimeout(resolve, INITIAL_REGEX_TIMEOUT_DURATION)); +const regexTimeoutUpgradePromise = new Promise((resolve) => setTimeout(resolve, INITIAL_REGEX_TIMEOUT_DURATION)); let newWorkerTimeout = INITIAL_REGEX_TIMEOUT; regexTimeoutUpgradePromise.then(() => (newWorkerTimeout = FINAL_REGEX_TIMEOUT)); diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index 92e8e594..b59da332 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -33,21 +33,21 @@ function simpleDiscordAPIRequest(bearerToken, path): Promise { Authorization: `Bearer ${bearerToken}`, }, }, - res => { + (res) => { if (res.statusCode !== 200) { reject(new Error(`Discord API error ${res.statusCode}`)); return; } let rawData = ""; - res.on("data", data => (rawData += data)); + res.on("data", (data) => (rawData += data)); res.on("end", () => { resolve(JSON.parse(rawData)); }); }, ); - request.on("error", err => reject(err)); + request.on("error", (err) => reject(err)); }); } diff --git a/backend/src/api/docs.ts b/backend/src/api/docs.ts index 8dbeb116..29a3dba9 100644 --- a/backend/src/api/docs.ts +++ b/backend/src/api/docs.ts @@ -22,21 +22,21 @@ function formatConfigSchema(schema) { } else if (schema.name.startsWith("Optional<")) { return `Optional<${formatConfigSchema(schema.types[0])}>`; } else { - return schema.types.map(t => formatConfigSchema(t)).join(" | "); + return schema.types.map((t) => formatConfigSchema(t)).join(" | "); } } else if (schema._tag === "IntersectionType") { - return schema.types.map(t => formatConfigSchema(t)).join(" & "); + return schema.types.map((t) => formatConfigSchema(t)).join(" & "); } else { return schema.name; } } export function initDocs(app: express.Express) { - const docsPlugins = guildPlugins.filter(plugin => plugin.showInDocs); + const docsPlugins = guildPlugins.filter((plugin) => plugin.showInDocs); app.get("/docs/plugins", (req: express.Request, res: express.Response) => { res.json( - docsPlugins.map(plugin => { + docsPlugins.map((plugin) => { const thinInfo = plugin.info ? { prettyName: plugin.info.prettyName, legacy: plugin.info.legacy ?? false } : {}; return { name: plugin.name, @@ -56,7 +56,7 @@ export function initDocs(app: express.Express) { const name = plugin.name; const info = plugin.info || {}; - const commands = (plugin.commands || []).map(cmd => ({ + const commands = (plugin.commands || []).map((cmd) => ({ trigger: cmd.trigger, permission: cmd.permission, signature: cmd.signature, diff --git a/backend/src/api/guilds.ts b/backend/src/api/guilds.ts index 412f526f..0706b4be 100644 --- a/backend/src/api/guilds.ts +++ b/backend/src/api/guilds.ts @@ -131,7 +131,7 @@ export function initGuildsAPI(app: express.Express) { } const validPermissions = new Set(Object.values(ApiPermissions)); validPermissions.delete(ApiPermissions.Owner); - if (!Array.isArray(permissions) || permissions.some(p => !validPermissions.has(p))) { + if (!Array.isArray(permissions) || permissions.some((p) => !validPermissions.has(p))) { return clientError(res, "Invalid permissions"); } if (expiresAt != null && !moment.utc(expiresAt).isValid()) { diff --git a/backend/src/configValidator.ts b/backend/src/configValidator.ts index 0c45c611..b5543dc4 100644 --- a/backend/src/configValidator.ts +++ b/backend/src/configValidator.ts @@ -36,7 +36,7 @@ export async function validateGuildConfig(config: any): Promise { const plugin = pluginNameToPlugin.get(pluginName)!; try { const mergedOptions = configUtils.mergeConfig(plugin.defaultOptions || {}, pluginOptions); - await plugin.configPreprocessor?.((mergedOptions as unknown) as PluginOptions, true); + await plugin.configPreprocessor?.(mergedOptions as unknown as PluginOptions, true); } catch (err) { if (err instanceof ConfigValidationError || err instanceof StrictValidationError) { return `${pluginName}: ${err.message}`; diff --git a/backend/src/data/ApiLogins.ts b/backend/src/data/ApiLogins.ts index 23bb7e49..ab8779d0 100644 --- a/backend/src/data/ApiLogins.ts +++ b/backend/src/data/ApiLogins.ts @@ -68,10 +68,7 @@ export class ApiLogins extends BaseRepository { token: hashedToken, user_id: userId, logged_in_at: moment.utc().format(DBDateFormat), - expires_at: moment - .utc() - .add(LOGIN_EXPIRY_TIME, "ms") - .format(DBDateFormat), + expires_at: moment.utc().add(LOGIN_EXPIRY_TIME, "ms").format(DBDateFormat), }); return `${loginId}.${token}`; @@ -96,10 +93,7 @@ export class ApiLogins extends BaseRepository { await this.apiLogins.update( { id: loginId }, { - expires_at: moment() - .utc() - .add(LOGIN_EXPIRY_TIME, "ms") - .format(DBDateFormat), + expires_at: moment().utc().add(LOGIN_EXPIRY_TIME, "ms").format(DBDateFormat), }, ); } diff --git a/backend/src/data/ApiUserInfo.ts b/backend/src/data/ApiUserInfo.ts index 73633d01..b7cd34ae 100644 --- a/backend/src/data/ApiUserInfo.ts +++ b/backend/src/data/ApiUserInfo.ts @@ -22,7 +22,7 @@ export class ApiUserInfo extends BaseRepository { } update(id, data: ApiUserInfoData) { - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const repo = entityManager.getRepository(ApiUserInfoEntity); const existingInfo = await repo.findOne({ where: { id } }); diff --git a/backend/src/data/Configs.ts b/backend/src/data/Configs.ts index 3e0882f0..2b3e4089 100644 --- a/backend/src/data/Configs.ts +++ b/backend/src/data/Configs.ts @@ -41,11 +41,7 @@ export class Configs extends BaseRepository { } getActiveLargerThanId(id) { - return this.configs - .createQueryBuilder() - .where("id > :id", { id }) - .andWhere("is_active = 1") - .getMany(); + return this.configs.createQueryBuilder().where("id > :id", { id }).andWhere("is_active = 1").getMany(); } async hasConfig(key) { @@ -65,7 +61,7 @@ export class Configs extends BaseRepository { } async saveNewRevision(key, config, editedBy) { - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const repo = entityManager.getRepository(Config); // Mark all old revisions inactive await repo.update({ key }, { is_active: false }); diff --git a/backend/src/data/GuildArchives.ts b/backend/src/data/GuildArchives.ts index 4fae975b..a84817ae 100644 --- a/backend/src/data/GuildArchives.ts +++ b/backend/src/data/GuildArchives.ts @@ -88,10 +88,10 @@ export class GuildArchives extends BaseGuildRepository { id: msg.id, timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), content: msg.data.content, - attachments: msg.data.attachments?.map(att => { + attachments: msg.data.attachments?.map((att) => { return JSON.stringify({ name: att.name, url: att.url, type: att.contentType }); }), - stickers: msg.data.stickers?.map(sti => { + stickers: msg.data.stickers?.map((sti) => { return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) }); }), user: partialUser, diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index f0e9de43..b13b7c4a 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -123,7 +123,7 @@ export class GuildCases extends BaseGuildRepository { guild_id: this.guildId, case_number: () => `(SELECT IFNULL(MAX(case_number)+1, 1) FROM cases AS ma2 WHERE guild_id = ${this.guildId})`, }) - .catch(err => { + .catch((err) => { if (err?.code === "ER_DUP_ENTRY") { if (data.audit_log_id) { console.trace(`Tried to insert case with duplicate audit_log_id`); @@ -148,7 +148,7 @@ export class GuildCases extends BaseGuildRepository { } async softDelete(id: number, deletedById: string, deletedByName: string, deletedByText: string) { - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const cases = entityManager.getRepository(Case); const caseNotes = entityManager.getRepository(CaseNote); diff --git a/backend/src/data/GuildCounters.ts b/backend/src/data/GuildCounters.ts index 0a1ff57e..52f20293 100644 --- a/backend/src/data/GuildCounters.ts +++ b/backend/src/data/GuildCounters.ts @@ -17,19 +17,11 @@ const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT const decayQueue = new Queue(); async function deleteCountersMarkedToBeDeleted(): Promise { - await getRepository(Counter) - .createQueryBuilder() - .where("delete_at <= NOW()") - .delete() - .execute(); + await getRepository(Counter).createQueryBuilder().where("delete_at <= NOW()").delete().execute(); } async function deleteTriggersMarkedToBeDeleted(): Promise { - await getRepository(CounterTrigger) - .createQueryBuilder() - .where("delete_at <= NOW()") - .delete() - .execute(); + await getRepository(CounterTrigger).createQueryBuilder().where("delete_at <= NOW()").delete().execute(); } setInterval(deleteCountersMarkedToBeDeleted, 1 * HOURS); @@ -97,10 +89,7 @@ export class GuildCounters extends BaseGuildRepository { criteria.id = Not(In(idsToKeep)); } - const deleteAt = moment - .utc() - .add(DELETE_UNUSED_COUNTERS_AFTER, "ms") - .format(DBDateFormat); + const deleteAt = moment.utc().add(DELETE_UNUSED_COUNTERS_AFTER, "ms").format(DBDateFormat); await this.counters.update(criteria, { delete_at: deleteAt, @@ -108,11 +97,7 @@ export class GuildCounters extends BaseGuildRepository { } async deleteCountersMarkedToBeDeleted(): Promise { - await this.counters - .createQueryBuilder() - .where("delete_at <= NOW()") - .delete() - .execute(); + await this.counters.createQueryBuilder().where("delete_at <= NOW()").delete().execute(); } async changeCounterValue( @@ -230,14 +215,11 @@ export class GuildCounters extends BaseGuildRepository { const triggersToMark = await triggersToMarkQuery.getMany(); if (triggersToMark.length) { - const deleteAt = moment - .utc() - .add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms") - .format(DBDateFormat); + const deleteAt = moment.utc().add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms").format(DBDateFormat); await this.counterTriggers.update( { - id: In(triggersToMark.map(t => t.id)), + id: In(triggersToMark.map((t) => t.id)), }, { delete_at: deleteAt, @@ -247,11 +229,7 @@ export class GuildCounters extends BaseGuildRepository { } async deleteTriggersMarkedToBeDeleted(): Promise { - await this.counterTriggers - .createQueryBuilder() - .where("delete_at <= NOW()") - .delete() - .execute(); + await this.counterTriggers.createQueryBuilder().where("delete_at <= NOW()").delete().execute(); } async initCounterTrigger( @@ -278,7 +256,7 @@ export class GuildCounters extends BaseGuildRepository { throw new Error(`Invalid comparison value: ${reverseComparisonValue}`); } - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const existing = await entityManager.findOne(CounterTrigger, { counter_id: counterId, name: triggerName, @@ -330,7 +308,7 @@ export class GuildCounters extends BaseGuildRepository { channelId = channelId || "0"; userId = userId || "0"; - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const previouslyTriggered = await entityManager.findOne(CounterTriggerState, { trigger_id: counterTrigger.id, user_id: userId!, @@ -378,7 +356,7 @@ export class GuildCounters extends BaseGuildRepository { async checkAllValuesForTrigger( counterTrigger: CounterTrigger, ): Promise> { - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const matchingValues = await entityManager .createQueryBuilder(CounterValue, "cv") .leftJoin( @@ -395,7 +373,7 @@ export class GuildCounters extends BaseGuildRepository { if (matchingValues.length) { await entityManager.insert( CounterTriggerState, - matchingValues.map(row => ({ + matchingValues.map((row) => ({ trigger_id: counterTrigger.id, channel_id: row.channel_id, user_id: row.user_id, @@ -403,7 +381,7 @@ export class GuildCounters extends BaseGuildRepository { ); } - return matchingValues.map(row => ({ + return matchingValues.map((row) => ({ channelId: row.channel_id, userId: row.user_id, })); @@ -429,7 +407,7 @@ export class GuildCounters extends BaseGuildRepository { channelId = channelId || "0"; userId = userId || "0"; - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const matchingValue = await entityManager .createQueryBuilder(CounterValue, "cv") .innerJoin( @@ -468,7 +446,7 @@ export class GuildCounters extends BaseGuildRepository { async checkAllValuesForReverseTrigger( counterTrigger: CounterTrigger, ): Promise> { - return connection.transaction(async entityManager => { + return connection.transaction(async (entityManager) => { const matchingValues: Array<{ id: string; triggerStateId: string; @@ -496,11 +474,11 @@ export class GuildCounters extends BaseGuildRepository { if (matchingValues.length) { await entityManager.delete(CounterTriggerState, { - id: In(matchingValues.map(v => v.triggerStateId)), + id: In(matchingValues.map((v) => v.triggerStateId)), }); } - return matchingValues.map(row => ({ + return matchingValues.map((row) => ({ channelId: row.channel_id, userId: row.user_id, })); diff --git a/backend/src/data/GuildLogs.ts b/backend/src/data/GuildLogs.ts index 457beec6..b324984e 100644 --- a/backend/src/data/GuildLogs.ts +++ b/backend/src/data/GuildLogs.ts @@ -46,12 +46,12 @@ export class GuildLogs extends events.EventEmitter { } isLogIgnored(type: LogType, ignoreId: any) { - return this.ignoredLogs.some(info => type === info.type && ignoreId === info.ignoreId); + return this.ignoredLogs.some((info) => type === info.type && ignoreId === info.ignoreId); } clearIgnoredLog(type: LogType, ignoreId: any) { this.ignoredLogs.splice( - this.ignoredLogs.findIndex(info => type === info.type && ignoreId === info.ignoreId), + this.ignoredLogs.findIndex((info) => type === info.type && ignoreId === info.ignoreId), 1, ); } diff --git a/backend/src/data/GuildMemberTimezones.ts b/backend/src/data/GuildMemberTimezones.ts index 6c4ef82b..3f98b9ef 100644 --- a/backend/src/data/GuildMemberTimezones.ts +++ b/backend/src/data/GuildMemberTimezones.ts @@ -19,7 +19,7 @@ export class GuildMemberTimezones extends BaseGuildRepository { } async set(memberId, timezone: string) { - await connection.transaction(async entityManager => { + await connection.transaction(async (entityManager) => { const repo = entityManager.getRepository(MemberTimezone); const existingRow = await repo.findOne({ guild_id: this.guildId, diff --git a/backend/src/data/GuildMutes.ts b/backend/src/data/GuildMutes.ts index 35f86fc0..f95571a0 100644 --- a/backend/src/data/GuildMutes.ts +++ b/backend/src/data/GuildMutes.ts @@ -35,12 +35,7 @@ export class GuildMutes extends BaseGuildRepository { } async addMute(userId, expiryTime, rolesToRestore?: string[]): Promise { - const expiresAt = expiryTime - ? moment - .utc() - .add(expiryTime, "ms") - .format("YYYY-MM-DD HH:mm:ss") - : null; + const expiresAt = expiryTime ? moment.utc().add(expiryTime, "ms").format("YYYY-MM-DD HH:mm:ss") : null; const result = await this.mutes.insert({ guild_id: this.guildId, @@ -53,12 +48,7 @@ export class GuildMutes extends BaseGuildRepository { } async updateExpiryTime(userId, newExpiryTime, rolesToRestore?: string[]) { - const expiresAt = newExpiryTime - ? moment - .utc() - .add(newExpiryTime, "ms") - .format("YYYY-MM-DD HH:mm:ss") - : null; + const expiresAt = newExpiryTime ? moment.utc().add(newExpiryTime, "ms").format("YYYY-MM-DD HH:mm:ss") : null; if (rolesToRestore && rolesToRestore.length) { return this.mutes.update( @@ -89,7 +79,7 @@ export class GuildMutes extends BaseGuildRepository { .createQueryBuilder("mutes") .where("guild_id = :guild_id", { guild_id: this.guildId }) .andWhere( - new Brackets(qb => { + new Brackets((qb) => { qb.where("expires_at > NOW()").orWhere("expires_at IS NULL"); }), ) diff --git a/backend/src/data/GuildNicknameHistory.ts b/backend/src/data/GuildNicknameHistory.ts index 51ed863c..9f75d24e 100644 --- a/backend/src/data/GuildNicknameHistory.ts +++ b/backend/src/data/GuildNicknameHistory.ts @@ -70,7 +70,7 @@ export class GuildNicknameHistory extends BaseGuildRepository { if (toDelete.length > 0) { await this.nicknameHistory.delete({ - id: In(toDelete.map(v => v.id)), + id: In(toDelete.map((v) => v.id)), }); } } diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index 462df642..6d1e29c5 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -46,7 +46,7 @@ export class GuildSavedMessages extends BaseGuildRepository { }; if (msg.attachments.size) { - data.attachments = Array.from(msg.attachments.values()).map(att => ({ + data.attachments = Array.from(msg.attachments.values()).map((att) => ({ id: att.id, contentType: att.contentType, name: att.name, @@ -59,14 +59,14 @@ export class GuildSavedMessages extends BaseGuildRepository { } if (msg.embeds.length) { - data.embeds = msg.embeds.map(embed => ({ + 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 => ({ + fields: embed.fields.map((field) => ({ name: field.name, value: field.value, inline: field.inline, @@ -119,7 +119,7 @@ export class GuildSavedMessages extends BaseGuildRepository { } if (msg.stickers?.size) { - data.stickers = Array.from(msg.stickers.values()).map(sticker => ({ + data.stickers = Array.from(msg.stickers.values()).map((sticker) => ({ format: sticker.format, guildId: sticker.guildId, id: sticker.id, diff --git a/backend/src/data/GuildScheduledPosts.ts b/backend/src/data/GuildScheduledPosts.ts index c302b81e..b2f32d54 100644 --- a/backend/src/data/GuildScheduledPosts.ts +++ b/backend/src/data/GuildScheduledPosts.ts @@ -11,10 +11,7 @@ export class GuildScheduledPosts extends BaseGuildRepository { } all(): Promise { - return this.scheduledPosts - .createQueryBuilder() - .where("guild_id = :guildId", { guildId: this.guildId }) - .getMany(); + return this.scheduledPosts.createQueryBuilder().where("guild_id = :guildId", { guildId: this.guildId }).getMany(); } getDueScheduledPosts(): Promise { diff --git a/backend/src/data/GuildSlowmodes.ts b/backend/src/data/GuildSlowmodes.ts index b6452490..dac92ead 100644 --- a/backend/src/data/GuildSlowmodes.ts +++ b/backend/src/data/GuildSlowmodes.ts @@ -67,10 +67,7 @@ export class GuildSlowmodes extends BaseGuildRepository { const slowmode = await this.getChannelSlowmode(channelId); if (!slowmode) return; - const expiresAt = moment - .utc() - .add(slowmode.slowmode_seconds, "seconds") - .format("YYYY-MM-DD HH:mm:ss"); + const expiresAt = moment.utc().add(slowmode.slowmode_seconds, "seconds").format("YYYY-MM-DD HH:mm:ss"); if (await this.userHasSlowmode(channelId, userId)) { // Update existing diff --git a/backend/src/data/GuildTempbans.ts b/backend/src/data/GuildTempbans.ts index f5f52143..6085a193 100644 --- a/backend/src/data/GuildTempbans.ts +++ b/backend/src/data/GuildTempbans.ts @@ -30,10 +30,7 @@ export class GuildTempbans extends BaseGuildRepository { } async addTempban(userId, expiryTime, modId): Promise { - const expiresAt = moment - .utc() - .add(expiryTime, "ms") - .format("YYYY-MM-DD HH:mm:ss"); + const expiresAt = moment.utc().add(expiryTime, "ms").format("YYYY-MM-DD HH:mm:ss"); const result = await this.tempbans.insert({ guild_id: this.guildId, @@ -47,10 +44,7 @@ export class GuildTempbans extends BaseGuildRepository { } async updateExpiryTime(userId, newExpiryTime, modId) { - const expiresAt = moment - .utc() - .add(newExpiryTime, "ms") - .format("YYYY-MM-DD HH:mm:ss"); + const expiresAt = moment.utc().add(newExpiryTime, "ms").format("YYYY-MM-DD HH:mm:ss"); return this.tempbans.update( { diff --git a/backend/src/data/GuildVCAlerts.ts b/backend/src/data/GuildVCAlerts.ts index 4c64c197..4736204a 100644 --- a/backend/src/data/GuildVCAlerts.ts +++ b/backend/src/data/GuildVCAlerts.ts @@ -19,10 +19,7 @@ export class GuildVCAlerts extends BaseGuildRepository { } async getAllGuildAlerts(): Promise { - return this.allAlerts - .createQueryBuilder() - .where("guild_id = :guildId", { guildId: this.guildId }) - .getMany(); + return this.allAlerts.createQueryBuilder().where("guild_id = :guildId", { guildId: this.guildId }).getMany(); } async getAlertsByUserId(userId: string): Promise { diff --git a/backend/src/data/UsernameHistory.ts b/backend/src/data/UsernameHistory.ts index c17a747c..7aa4331a 100644 --- a/backend/src/data/UsernameHistory.ts +++ b/backend/src/data/UsernameHistory.ts @@ -67,7 +67,7 @@ export class UsernameHistory extends BaseRepository { if (toDelete.length > 0) { await this.usernameHistory.delete({ - id: In(toDelete.map(v => v.id)), + id: In(toDelete.map((v) => v.id)), }); } } diff --git a/backend/src/data/cleanup/configs.ts b/backend/src/data/cleanup/configs.ts index 252b6c6d..1d22ea52 100644 --- a/backend/src/data/cleanup/configs.ts +++ b/backend/src/data/cleanup/configs.ts @@ -13,10 +13,7 @@ export async function cleanupConfigs() { let rows; // >1 month old: 1 config retained per month - const oneMonthCutoff = moment - .utc() - .subtract(30, "days") - .format(DBDateFormat); + const oneMonthCutoff = moment.utc().subtract(30, "days").format(DBDateFormat); do { rows = await connection.query( ` @@ -46,7 +43,7 @@ export async function cleanupConfigs() { if (rows.length > 0) { await configRepository.delete({ - id: In(rows.map(r => r.id)), + id: In(rows.map((r) => r.id)), }); } @@ -54,10 +51,7 @@ export async function cleanupConfigs() { } while (rows.length === CLEAN_PER_LOOP); // >2 weeks old: 1 config retained per day - const twoWeekCutoff = moment - .utc() - .subtract(2, "weeks") - .format(DBDateFormat); + const twoWeekCutoff = moment.utc().subtract(2, "weeks").format(DBDateFormat); do { rows = await connection.query( ` @@ -87,7 +81,7 @@ export async function cleanupConfigs() { if (rows.length > 0) { await configRepository.delete({ - id: In(rows.map(r => r.id)), + id: In(rows.map((r) => r.id)), }); } diff --git a/backend/src/data/cleanup/messages.ts b/backend/src/data/cleanup/messages.ts index 06d55c99..0a5d038b 100644 --- a/backend/src/data/cleanup/messages.ts +++ b/backend/src/data/cleanup/messages.ts @@ -18,18 +18,9 @@ export async function cleanupMessages(): Promise { const messagesRepository = getRepository(SavedMessage); - const deletedAtThreshold = moment - .utc() - .subtract(DELETED_MESSAGE_RETENTION_PERIOD, "ms") - .format(DBDateFormat); - const postedAtThreshold = moment - .utc() - .subtract(RETENTION_PERIOD, "ms") - .format(DBDateFormat); - const botPostedAtThreshold = moment - .utc() - .subtract(BOT_MESSAGE_RETENTION_PERIOD, "ms") - .format(DBDateFormat); + const deletedAtThreshold = moment.utc().subtract(DELETED_MESSAGE_RETENTION_PERIOD, "ms").format(DBDateFormat); + const postedAtThreshold = moment.utc().subtract(RETENTION_PERIOD, "ms").format(DBDateFormat); + const botPostedAtThreshold = moment.utc().subtract(BOT_MESSAGE_RETENTION_PERIOD, "ms").format(DBDateFormat); // SELECT + DELETE messages in batches // This is to avoid deadlocks that happened frequently when deleting with the same criteria as the select below @@ -60,7 +51,7 @@ export async function cleanupMessages(): Promise { if (rows.length > 0) { await messagesRepository.delete({ - id: In(rows.map(r => r.id)), + id: In(rows.map((r) => r.id)), }); } diff --git a/backend/src/data/cleanup/nicknames.ts b/backend/src/data/cleanup/nicknames.ts index 4907e004..addaa347 100644 --- a/backend/src/data/cleanup/nicknames.ts +++ b/backend/src/data/cleanup/nicknames.ts @@ -11,10 +11,7 @@ export async function cleanupNicknames(): Promise { let cleaned = 0; const nicknameHistoryRepository = getRepository(NicknameHistoryEntry); - const dateThreshold = moment - .utc() - .subtract(NICKNAME_RETENTION_PERIOD, "ms") - .format(DBDateFormat); + const dateThreshold = moment.utc().subtract(NICKNAME_RETENTION_PERIOD, "ms").format(DBDateFormat); // Clean old nicknames (NICKNAME_RETENTION_PERIOD) let rows; @@ -31,7 +28,7 @@ export async function cleanupNicknames(): Promise { if (rows.length > 0) { await nicknameHistoryRepository.delete({ - id: In(rows.map(r => r.id)), + id: In(rows.map((r) => r.id)), }); } diff --git a/backend/src/data/cleanup/usernames.ts b/backend/src/data/cleanup/usernames.ts index eea441d5..63ac79f8 100644 --- a/backend/src/data/cleanup/usernames.ts +++ b/backend/src/data/cleanup/usernames.ts @@ -11,10 +11,7 @@ export async function cleanupUsernames(): Promise { let cleaned = 0; const usernameHistoryRepository = getRepository(UsernameHistoryEntry); - const dateThreshold = moment - .utc() - .subtract(USERNAME_RETENTION_PERIOD, "ms") - .format(DBDateFormat); + const dateThreshold = moment.utc().subtract(USERNAME_RETENTION_PERIOD, "ms").format(DBDateFormat); // Clean old usernames (USERNAME_RETENTION_PERIOD) let rows; @@ -31,7 +28,7 @@ export async function cleanupUsernames(): Promise { if (rows.length > 0) { await usernameHistoryRepository.delete({ - id: In(rows.map(r => r.id)), + id: In(rows.map((r) => r.id)), }); } diff --git a/backend/src/data/db.ts b/backend/src/data/db.ts index 9888d0b5..42ac5d38 100644 --- a/backend/src/data/db.ts +++ b/backend/src/data/db.ts @@ -7,9 +7,9 @@ export let connection: Connection; export function connect() { if (!connectionPromise) { - connectionPromise = createConnection().then(newConnection => { + connectionPromise = createConnection().then((newConnection) => { // Verify the DB timezone is set to UTC - return newConnection.query("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP) AS tz").then(r => { + return newConnection.query("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP) AS tz").then((r) => { if (r[0].tz !== "00:00:00") { throw new SimpleError(`Database timezone must be UTC (detected ${r[0].tz})`); } diff --git a/backend/src/data/entities/ApiLogin.ts b/backend/src/data/entities/ApiLogin.ts index 25f2405a..0cc35f39 100644 --- a/backend/src/data/entities/ApiLogin.ts +++ b/backend/src/data/entities/ApiLogin.ts @@ -19,10 +19,7 @@ export class ApiLogin { @Column() expires_at: string; - @ManyToOne( - type => ApiUserInfo, - userInfo => userInfo.logins, - ) + @ManyToOne((type) => ApiUserInfo, (userInfo) => userInfo.logins) @JoinColumn({ name: "user_id" }) userInfo: ApiUserInfo; } diff --git a/backend/src/data/entities/ApiPermissionAssignment.ts b/backend/src/data/entities/ApiPermissionAssignment.ts index 349544d5..0160241f 100644 --- a/backend/src/data/entities/ApiPermissionAssignment.ts +++ b/backend/src/data/entities/ApiPermissionAssignment.ts @@ -22,10 +22,7 @@ export class ApiPermissionAssignment { @Column({ type: String, nullable: true }) expires_at: string | null; - @ManyToOne( - type => ApiUserInfo, - userInfo => userInfo.permissionAssignments, - ) + @ManyToOne((type) => ApiUserInfo, (userInfo) => userInfo.permissionAssignments) @JoinColumn({ name: "target_id" }) userInfo: ApiUserInfo; } diff --git a/backend/src/data/entities/ApiUserInfo.ts b/backend/src/data/entities/ApiUserInfo.ts index 32c3a1b0..b6146b23 100644 --- a/backend/src/data/entities/ApiUserInfo.ts +++ b/backend/src/data/entities/ApiUserInfo.ts @@ -20,15 +20,9 @@ export class ApiUserInfo { @Column() updated_at: string; - @OneToMany( - type => ApiLogin, - login => login.userInfo, - ) + @OneToMany((type) => ApiLogin, (login) => login.userInfo) logins: ApiLogin[]; - @OneToMany( - type => ApiPermissionAssignment, - p => p.userInfo, - ) + @OneToMany((type) => ApiPermissionAssignment, (p) => p.userInfo) permissionAssignments: ApiPermissionAssignment[]; } diff --git a/backend/src/data/entities/Case.ts b/backend/src/data/entities/Case.ts index f8980b33..84d24618 100644 --- a/backend/src/data/entities/Case.ts +++ b/backend/src/data/entities/Case.ts @@ -35,9 +35,6 @@ export class Case { */ @Column({ type: String, nullable: true }) log_message_id: string | null; - @OneToMany( - type => CaseNote, - note => note.case, - ) + @OneToMany((type) => CaseNote, (note) => note.case) notes: CaseNote[]; } diff --git a/backend/src/data/entities/CaseNote.ts b/backend/src/data/entities/CaseNote.ts index 3f2e8125..f883d79f 100644 --- a/backend/src/data/entities/CaseNote.ts +++ b/backend/src/data/entities/CaseNote.ts @@ -15,10 +15,7 @@ export class CaseNote { @Column() created_at: string; - @ManyToOne( - type => Case, - theCase => theCase.notes, - ) + @ManyToOne((type) => Case, (theCase) => theCase.notes) @JoinColumn({ name: "case_id" }) case: Case; } diff --git a/backend/src/data/entities/Config.ts b/backend/src/data/entities/Config.ts index 8e16514e..d5e391b4 100644 --- a/backend/src/data/entities/Config.ts +++ b/backend/src/data/entities/Config.ts @@ -22,7 +22,7 @@ export class Config { @Column() edited_at: string; - @ManyToOne(type => ApiUserInfo) + @ManyToOne((type) => ApiUserInfo) @JoinColumn({ name: "edited_by" }) userInfo: ApiUserInfo; } diff --git a/backend/src/data/entities/StarboardMessage.ts b/backend/src/data/entities/StarboardMessage.ts index 405222c1..2d8839e6 100644 --- a/backend/src/data/entities/StarboardMessage.ts +++ b/backend/src/data/entities/StarboardMessage.ts @@ -16,7 +16,7 @@ export class StarboardMessage { @Column() guild_id: string; - @OneToOne(type => SavedMessage) + @OneToOne((type) => SavedMessage) @JoinColumn({ name: "message_id" }) message: SavedMessage; } diff --git a/backend/src/data/entities/StarboardReaction.ts b/backend/src/data/entities/StarboardReaction.ts index 8e04a8fb..c2cb0258 100644 --- a/backend/src/data/entities/StarboardReaction.ts +++ b/backend/src/data/entities/StarboardReaction.ts @@ -16,7 +16,7 @@ export class StarboardReaction { @Column() reactor_id: string; - @OneToOne(type => SavedMessage) + @OneToOne((type) => SavedMessage) @JoinColumn({ name: "message_id" }) message: SavedMessage; } diff --git a/backend/src/index.ts b/backend/src/index.ts index 3022f7a7..3b7e1e17 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -144,8 +144,8 @@ if (process.env.NODE_ENV === "production") { // Verify required Node.js version const REQUIRED_NODE_VERSION = "14.0.0"; -const requiredParts = REQUIRED_NODE_VERSION.split(".").map(v => parseInt(v, 10)); -const actualVersionParts = process.versions.node.split(".").map(v => parseInt(v, 10)); +const requiredParts = REQUIRED_NODE_VERSION.split(".").map((v) => parseInt(v, 10)); +const actualVersionParts = process.versions.node.split(".").map((v) => parseInt(v, 10)); for (const [i, part] of actualVersionParts.entries()) { if (part > requiredParts[i]) break; if (part === requiredParts[i]) continue; @@ -188,7 +188,7 @@ connect().then(async () => { }); client.setMaxListeners(200); - client.on(Constants.Events.RATE_LIMIT, data => { + client.on(Constants.Events.RATE_LIMIT, (data) => { // tslint:disable-next-line:no-console // console.log(`[DEBUG] [RATE_LIMIT] ${JSON.stringify(data)}`); }); @@ -196,7 +196,7 @@ connect().then(async () => { const safe429DecayInterval = 5 * SECONDS; const safe429MaxCount = 5; const safe429Counter = new DecayingCounter(safe429DecayInterval); - client.on(Constants.Events.DEBUG, errorText => { + client.on(Constants.Events.DEBUG, (errorText) => { if (!errorText.includes("429")) { return; } @@ -212,7 +212,7 @@ connect().then(async () => { } }); - client.on("error", err => { + client.on("error", (err) => { errorHandler(new DiscordJSError(err.message, (err as any).code, 0)); }); @@ -240,9 +240,9 @@ connect().then(async () => { } const configuredPlugins = ctx.config.plugins; - const basePluginNames = baseGuildPlugins.map(p => p.name); + const basePluginNames = baseGuildPlugins.map((p) => p.name); - return Array.from(plugins.keys()).filter(pluginName => { + return Array.from(plugins.keys()).filter((pluginName) => { if (basePluginNames.includes(pluginName)) return true; return configuredPlugins[pluginName] && configuredPlugins[pluginName].enabled !== false; }); @@ -308,14 +308,14 @@ connect().then(async () => { }); const debugGuilds = ["877581055920603238", "348468156597010432", "134286179121102848"]; - bot.on("guildLoaded", guildId => { + bot.on("guildLoaded", (guildId) => { if (!debugGuilds.includes(guildId)) { return; } console.log(`[!! DEBUG !!] LOADED GUILD ${guildId}`); }); - bot.on("guildUnloaded", guildId => { + bot.on("guildUnloaded", (guildId) => { if (!debugGuilds.includes(guildId)) { return; } diff --git a/backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts b/backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts index f15417ef..a42c0c7f 100644 --- a/backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts +++ b/backend/src/migrations/1556909512501-MigrateUsernamesToNewHistoryTable.ts @@ -9,7 +9,7 @@ export class MigrateUsernamesToNewHistoryTable1556909512501 implements Migration const migratedUsernames = new Set(); - await new Promise(async resolve => { + await new Promise(async (resolve) => { const stream = await queryRunner.stream("SELECT CONCAT(user_id, '-', username) AS `key` FROM username_history"); stream.on("data", (row: any) => { migratedUsernames.add(row.key); @@ -18,7 +18,7 @@ export class MigrateUsernamesToNewHistoryTable1556909512501 implements Migration }); const migrateNextBatch = (): Promise<{ finished: boolean; migrated?: number }> => { - return new Promise(async resolve => { + return new Promise(async (resolve) => { const toInsert: any[][] = []; const toDelete: number[] = []; diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 8e0cfaf6..f9093ad1 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -85,7 +85,7 @@ export function strictValidationErrorToConfigValidationError(err: StrictValidati return new ConfigValidationError( err .getErrors() - .map(e => e.toString()) + .map((e) => e.toString()) .join("\n"), ); } @@ -199,7 +199,7 @@ export async function sendSuccessMessage( return channel .send({ ...content }) // Force line break - .catch(err => { + .catch((err) => { const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id; logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); return undefined; @@ -220,7 +220,7 @@ export async function sendErrorMessage( return channel .send({ ...content }) // Force line break - .catch(err => { + .catch((err) => { const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id; logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); return undefined; @@ -252,7 +252,7 @@ type AnyFn = (...args: any[]) => any; * Creates a public plugin function out of a function with pluginData as the first parameter */ export function mapToPublicFn(inputFn: T) { - return pluginData => { + return (pluginData) => { return (...args: Tail>): ReturnType => { return inputFn(pluginData, ...args); }; diff --git a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts index 593ffb31..6c8251b8 100644 --- a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts +++ b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts @@ -45,13 +45,13 @@ export const AutoDeletePlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state, guild } = pluginData; - state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); + state.onMessageCreateFn = (msg) => onMessageCreate(pluginData, msg); state.guildSavedMessages.events.on("create", state.onMessageCreateFn); - state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg); + state.onMessageDeleteFn = (msg) => onMessageDelete(pluginData, msg); state.guildSavedMessages.events.on("delete", state.onMessageDeleteFn); - state.onMessageDeleteBulkFn = msgs => onMessageDeleteBulk(pluginData, msgs); + state.onMessageDeleteBulkFn = (msgs) => onMessageDeleteBulk(pluginData, msgs); state.guildSavedMessages.events.on("deleteBulk", state.onMessageDeleteBulkFn); }, diff --git a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts index 07ffec69..9ff6a8f0 100644 --- a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts +++ b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts @@ -45,7 +45,7 @@ export async function deleteNextItem(pluginData: GuildPluginData { + (channel as TextChannel).messages.delete(itemToDelete.message.id as Snowflake).catch((err) => { if (err.code === 10008) { // "Unknown Message", probably already deleted by automod or another bot, ignore return; diff --git a/backend/src/plugins/AutoDelete/util/onMessageDelete.ts b/backend/src/plugins/AutoDelete/util/onMessageDelete.ts index 48b7bb72..984a7850 100644 --- a/backend/src/plugins/AutoDelete/util/onMessageDelete.ts +++ b/backend/src/plugins/AutoDelete/util/onMessageDelete.ts @@ -4,7 +4,7 @@ import { AutoDeletePluginType } from "../types"; import { scheduleNextDeletion } from "./scheduleNextDeletion"; export function onMessageDelete(pluginData: GuildPluginData, msg: SavedMessage) { - const indexToDelete = pluginData.state.deletionQueue.findIndex(item => item.message.id === msg.id); + const indexToDelete = pluginData.state.deletionQueue.findIndex((item) => item.message.id === msg.id); if (indexToDelete > -1) { pluginData.state.deletionQueue.splice(indexToDelete, 1); scheduleNextDeletion(pluginData); diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 60f55296..9a9cbc76 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -57,7 +57,7 @@ const defaultOptions = { /** * Config preprocessor to set default values for triggers and perform extra validation */ -const configPreprocessor: ConfigPreprocessorFn = options => { +const configPreprocessor: ConfigPreprocessorFn = (options) => { if (options.config?.rules) { // Loop through each rule for (const [name, rule] of Object.entries(options.config.rules)) { @@ -232,10 +232,10 @@ export const AutomodPlugin = zeppelinGuildPlugin()({ 30 * SECONDS, ); - pluginData.state.onMessageCreateFn = message => runAutomodOnMessage(pluginData, message, false); + pluginData.state.onMessageCreateFn = (message) => runAutomodOnMessage(pluginData, message, false); pluginData.state.savedMessages.events.on("create", pluginData.state.onMessageCreateFn); - pluginData.state.onMessageUpdateFn = message => runAutomodOnMessage(pluginData, message, true); + pluginData.state.onMessageUpdateFn = (message) => runAutomodOnMessage(pluginData, message, true); pluginData.state.savedMessages.events.on("update", pluginData.state.onMessageUpdateFn); const countersPlugin = pluginData.getPlugin(CountersPlugin); diff --git a/backend/src/plugins/Automod/actions/addRoles.ts b/backend/src/plugins/Automod/actions/addRoles.ts index c39cc5e7..ee577ae9 100644 --- a/backend/src/plugins/Automod/actions/addRoles.ts +++ b/backend/src/plugins/Automod/actions/addRoles.ts @@ -17,7 +17,7 @@ export const AddRolesAction = automodAction({ defaultConfig: [], async apply({ pluginData, contexts, actionConfig, ruleName }) { - const members = unique(contexts.map(c => c.member).filter(nonNullish)); + const members = unique(contexts.map((c) => c.member).filter(nonNullish)); const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); @@ -41,7 +41,7 @@ export const AddRolesAction = automodAction({ if (rolesWeCannotAssign.length) { const roleNamesWeCannotAssign = rolesWeCannotAssign.map( - roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, + (roleId) => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); logs.logBotAlert({ @@ -52,7 +52,7 @@ export const AddRolesAction = automodAction({ } await Promise.all( - members.map(async member => { + members.map(async (member) => { const memberRoles = new Set(member.roles.cache.keys()); for (const roleId of rolesToAssign) { memberRoles.add(roleId as Snowflake); diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index 314bb552..b765811e 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -41,7 +41,7 @@ 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 ? userToTemplateSafeUser(c.user) : null)).filter(isTruthy); + 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(", "); diff --git a/backend/src/plugins/Automod/actions/archiveThread.ts b/backend/src/plugins/Automod/actions/archiveThread.ts index 7c7aa218..6fe871a5 100644 --- a/backend/src/plugins/Automod/actions/archiveThread.ts +++ b/backend/src/plugins/Automod/actions/archiveThread.ts @@ -9,8 +9,8 @@ export const ArchiveThreadAction = automodAction({ async apply({ pluginData, contexts }) { const threads = contexts - .filter(c => c.message?.channel_id) - .map(c => pluginData.guild.channels.cache.get(c.message!.channel_id)) + .filter((c) => c.message?.channel_id) + .map((c) => pluginData.guild.channels.cache.get(c.message!.channel_id)) .filter((c): c is ThreadChannel => c?.isThread() ?? false); for (const thread of threads) { diff --git a/backend/src/plugins/Automod/actions/ban.ts b/backend/src/plugins/Automod/actions/ban.ts index ff92be3a..e32279a7 100644 --- a/backend/src/plugins/Automod/actions/ban.ts +++ b/backend/src/plugins/Automod/actions/ban.ts @@ -35,7 +35,7 @@ export const BanAction = automodAction({ hide: Boolean(actionConfig.hide_case), }; - const userIdsToBan = unique(contexts.map(c => c.user?.id).filter(nonNullish)); + const userIdsToBan = unique(contexts.map((c) => c.user?.id).filter(nonNullish)); const modActions = pluginData.getPlugin(ModActionsPlugin); for (const userId of userIdsToBan) { diff --git a/backend/src/plugins/Automod/actions/changeNickname.ts b/backend/src/plugins/Automod/actions/changeNickname.ts index ed2db27c..0a368355 100644 --- a/backend/src/plugins/Automod/actions/changeNickname.ts +++ b/backend/src/plugins/Automod/actions/changeNickname.ts @@ -15,13 +15,13 @@ export const ChangeNicknameAction = automodAction({ defaultConfig: {}, async apply({ pluginData, contexts, actionConfig }) { - const members = unique(contexts.map(c => c.member).filter(nonNullish)); + const members = unique(contexts.map((c) => c.member).filter(nonNullish)); for (const member of members) { if (pluginData.state.recentNicknameChanges.has(member.id)) continue; const newName = typeof actionConfig === "string" ? actionConfig : actionConfig.name; - member.edit({ nick: newName }).catch(err => { + member.edit({ nick: newName }).catch((err) => { pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Failed to change the nickname of \`${member.id}\``, }); diff --git a/backend/src/plugins/Automod/actions/kick.ts b/backend/src/plugins/Automod/actions/kick.ts index 98408da3..9e6a792d 100644 --- a/backend/src/plugins/Automod/actions/kick.ts +++ b/backend/src/plugins/Automod/actions/kick.ts @@ -31,8 +31,8 @@ export const KickAction = automodAction({ hide: Boolean(actionConfig.hide_case), }; - const userIdsToKick = unique(contexts.map(c => c.user?.id).filter(nonNullish)); - const membersToKick = await asyncMap(userIdsToKick, id => resolveMember(pluginData.client, pluginData.guild, id)); + const userIdsToKick = unique(contexts.map((c) => c.user?.id).filter(nonNullish)); + const membersToKick = await asyncMap(userIdsToKick, (id) => resolveMember(pluginData.client, pluginData.guild, id)); const modActions = pluginData.getPlugin(ModActionsPlugin); for (const member of membersToKick) { diff --git a/backend/src/plugins/Automod/actions/log.ts b/backend/src/plugins/Automod/actions/log.ts index 4a78442d..62f57a20 100644 --- a/backend/src/plugins/Automod/actions/log.ts +++ b/backend/src/plugins/Automod/actions/log.ts @@ -10,7 +10,7 @@ export const LogAction = automodAction({ defaultConfig: true, async apply({ pluginData, contexts, ruleName, matchResult }) { - const users = unique(contexts.map(c => c.user)).filter(isTruthy); + 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(", "); diff --git a/backend/src/plugins/Automod/actions/mute.ts b/backend/src/plugins/Automod/actions/mute.ts index 8d4aee6a..a1bb9299 100644 --- a/backend/src/plugins/Automod/actions/mute.ts +++ b/backend/src/plugins/Automod/actions/mute.ts @@ -40,7 +40,7 @@ export const MuteAction = automodAction({ hide: Boolean(actionConfig.hide_case), }; - const userIdsToMute = unique(contexts.map(c => c.user?.id).filter(nonNullish)); + const userIdsToMute = unique(contexts.map((c) => c.user?.id).filter(nonNullish)); const mutes = pluginData.getPlugin(MutesPlugin); for (const userId of userIdsToMute) { diff --git a/backend/src/plugins/Automod/actions/removeRoles.ts b/backend/src/plugins/Automod/actions/removeRoles.ts index 690af257..62b71480 100644 --- a/backend/src/plugins/Automod/actions/removeRoles.ts +++ b/backend/src/plugins/Automod/actions/removeRoles.ts @@ -18,7 +18,7 @@ export const RemoveRolesAction = automodAction({ defaultConfig: [], async apply({ pluginData, contexts, actionConfig, ruleName }) { - const members = unique(contexts.map(c => c.member).filter(nonNullish)); + const members = unique(contexts.map((c) => c.member).filter(nonNullish)); const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); @@ -42,7 +42,7 @@ export const RemoveRolesAction = automodAction({ if (rolesWeCannotRemove.length) { const roleNamesWeCannotRemove = rolesWeCannotRemove.map( - roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, + (roleId) => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); logs.logBotAlert({ @@ -53,7 +53,7 @@ export const RemoveRolesAction = automodAction({ } await Promise.all( - members.map(async member => { + members.map(async (member) => { const memberRoles = new Set(member.roles.cache.keys()); for (const roleId of rolesToRemove) { memberRoles.delete(roleId as Snowflake); diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index bac6f105..efe833cb 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -32,8 +32,8 @@ export const ReplyAction = automodAction({ async apply({ pluginData, contexts, actionConfig, ruleName }) { const contextsWithTextChannels = contexts - .filter(c => c.message?.channel_id) - .filter(c => { + .filter((c) => c.message?.channel_id) + .filter((c) => { const channel = pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake); return channel instanceof TextChannel || channel instanceof ThreadChannel; }); @@ -48,7 +48,7 @@ export const ReplyAction = automodAction({ }, new Map()); for (const [channelId, _contexts] of contextsByChannelId.entries()) { - const users = unique(Array.from(new Set(_contexts.map(c => c.user).filter(Boolean)))) as User[]; + const users = unique(Array.from(new Set(_contexts.map((c) => c.user).filter(Boolean)))) as User[]; const user = users[0]; const renderReplyText = async (str: string) => diff --git a/backend/src/plugins/Automod/actions/warn.ts b/backend/src/plugins/Automod/actions/warn.ts index 29dd4955..59135cb2 100644 --- a/backend/src/plugins/Automod/actions/warn.ts +++ b/backend/src/plugins/Automod/actions/warn.ts @@ -31,8 +31,8 @@ export const WarnAction = automodAction({ hide: Boolean(actionConfig.hide_case), }; - const userIdsToWarn = unique(contexts.map(c => c.user?.id).filter(nonNullish)); - const membersToWarn = await asyncMap(userIdsToWarn, id => resolveMember(pluginData.client, pluginData.guild, id)); + const userIdsToWarn = unique(contexts.map((c) => c.user?.id).filter(nonNullish)); + const membersToWarn = await asyncMap(userIdsToWarn, (id) => resolveMember(pluginData.client, pluginData.guild, id)); const modActions = pluginData.getPlugin(ModActionsPlugin); for (const member of membersToWarn) { diff --git a/backend/src/plugins/Automod/functions/clearOldRecentActions.ts b/backend/src/plugins/Automod/functions/clearOldRecentActions.ts index 67a79718..9293e9c3 100644 --- a/backend/src/plugins/Automod/functions/clearOldRecentActions.ts +++ b/backend/src/plugins/Automod/functions/clearOldRecentActions.ts @@ -4,7 +4,7 @@ import { AutomodPluginType } from "../types"; export function clearOldRecentActions(pluginData: GuildPluginData) { const now = Date.now(); - pluginData.state.recentActions = pluginData.state.recentActions.filter(info => { + pluginData.state.recentActions = pluginData.state.recentActions.filter((info) => { return info.context.timestamp + RECENT_ACTION_EXPIRY_TIME > now; }); } diff --git a/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts b/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts index a0f34c8b..7cda8329 100644 --- a/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts +++ b/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts @@ -4,7 +4,7 @@ import { AutomodPluginType } from "../types"; export function clearOldRecentSpam(pluginData: GuildPluginData) { const now = Date.now(); - pluginData.state.recentSpam = pluginData.state.recentSpam.filter(spam => { + pluginData.state.recentSpam = pluginData.state.recentSpam.filter((spam) => { return spam.timestamp + RECENT_SPAM_EXPIRY_TIME > now; }); } diff --git a/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts b/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts index 7aa9750a..e1cadc58 100644 --- a/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts +++ b/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts @@ -6,7 +6,7 @@ export function clearRecentActionsForMessage(pluginData: GuildPluginData { + pluginData.state.recentActions = pluginData.state.recentActions.filter((act) => { return act.identifier !== globalIdentifier && act.identifier !== perChannelIdentifier; }); } diff --git a/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts b/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts index 009a1f52..1dbd233d 100644 --- a/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts +++ b/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts @@ -60,7 +60,7 @@ export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: if (matchedSpam) { const messages = matchedSpam.recentActions - .map(action => action.context.message) + .map((action) => action.context.message) .filter(Boolean) .sort(sorter("posted_at")) as SavedMessage[]; @@ -75,8 +75,8 @@ export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: return { extraContexts: matchedSpam.recentActions - .map(action => action.context) - .filter(_context => _context !== context), + .map((action) => action.context) + .filter((_context) => _context !== context), extra: { archiveId, diff --git a/backend/src/plugins/Automod/functions/findRecentSpam.ts b/backend/src/plugins/Automod/functions/findRecentSpam.ts index 0e042ed5..07e68d77 100644 --- a/backend/src/plugins/Automod/functions/findRecentSpam.ts +++ b/backend/src/plugins/Automod/functions/findRecentSpam.ts @@ -7,7 +7,7 @@ export function findRecentSpam( type: RecentActionType, identifier?: string, ) { - return pluginData.state.recentSpam.find(spam => { + return pluginData.state.recentSpam.find((spam) => { return spam.type === type && (!identifier || spam.identifiers.includes(identifier)); }); } diff --git a/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts b/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts index 416c050b..75107e45 100644 --- a/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts +++ b/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts @@ -11,7 +11,7 @@ export function getMatchingRecentActions( ) { to = to || Date.now(); - return pluginData.state.recentActions.filter(action => { + return pluginData.state.recentActions.filter((action) => { return ( action.type === type && (!identifier || action.identifier === identifier) && diff --git a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts index fe0466e4..31ece554 100644 --- a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts +++ b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts @@ -29,6 +29,6 @@ export function getTextMatchPartialSummary( const visibleName = context.member?.nickname || context.user!.username; return `visible name: ${visibleName}`; } else if (type === "customstatus") { - return `custom status: ${context.member!.presence?.activities.find(a => a.type === "CUSTOM")?.name}`; + return `custom status: ${context.member!.presence?.activities.find((a) => a.type === "CUSTOM")?.name}`; } } diff --git a/backend/src/plugins/Automod/triggers/exampleTrigger.ts b/backend/src/plugins/Automod/triggers/exampleTrigger.ts index 7098e713..bf0880a9 100644 --- a/backend/src/plugins/Automod/triggers/exampleTrigger.ts +++ b/backend/src/plugins/Automod/triggers/exampleTrigger.ts @@ -15,7 +15,7 @@ export const ExampleTrigger = automodTrigger()({ }, async match({ triggerConfig, context }) { - const foundFruit = triggerConfig.allowedFruits.find(fruit => context.message?.data.content === fruit); + const foundFruit = triggerConfig.allowedFruits.find((fruit) => context.message?.data.content === fruit); if (foundFruit) { return { extra: { diff --git a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts index 0eb7e764..d905fbc0 100644 --- a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts +++ b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts @@ -33,13 +33,10 @@ export const MatchAttachmentTypeTrigger = automodTrigger()({ } for (const attachment of context.message.data.attachments) { - const attachmentType = attachment.url - .split(".") - .pop()! - .toLowerCase(); + const attachmentType = attachment.url.split(".").pop()!.toLowerCase(); const blacklist = trigger.blacklist_enabled - ? (trigger.filetype_blacklist || []).map(_t => _t.toLowerCase()) + ? (trigger.filetype_blacklist || []).map((_t) => _t.toLowerCase()) : null; if (blacklist && blacklist.includes(attachmentType)) { @@ -52,7 +49,7 @@ export const MatchAttachmentTypeTrigger = automodTrigger()({ } const whitelist = trigger.whitelist_enabled - ? (trigger.filetype_whitelist || []).map(_t => _t.toLowerCase()) + ? (trigger.filetype_whitelist || []).map((_t) => _t.toLowerCase()) : null; if (whitelist && !whitelist.includes(attachmentType)) { diff --git a/backend/src/plugins/Automod/triggers/matchMimeType.ts b/backend/src/plugins/Automod/triggers/matchMimeType.ts index 5e00d4b6..6e1b447e 100644 --- a/backend/src/plugins/Automod/triggers/matchMimeType.ts +++ b/backend/src/plugins/Automod/triggers/matchMimeType.ts @@ -34,7 +34,7 @@ export const MatchMimeTypeTrigger = automodTrigger()({ const contentType = (rawContentType || "").split(";")[0]; // Remove "; charset=utf8" and similar from the end const blacklist = trigger.blacklist_enabled - ? (trigger.mime_type_blacklist ?? []).map(_t => _t.toLowerCase()) + ? (trigger.mime_type_blacklist ?? []).map((_t) => _t.toLowerCase()) : null; if (contentType && blacklist?.includes(contentType)) { @@ -47,7 +47,7 @@ export const MatchMimeTypeTrigger = automodTrigger()({ } const whitelist = trigger.whitelist_enabled - ? (trigger.mime_type_whitelist ?? []).map(_t => _t.toLowerCase()) + ? (trigger.mime_type_whitelist ?? []).map((_t) => _t.toLowerCase()) : null; if (whitelist && (!contentType || !whitelist.includes(contentType))) { diff --git a/backend/src/plugins/Automod/triggers/matchWords.ts b/backend/src/plugins/Automod/triggers/matchWords.ts index 387f2208..fda24726 100644 --- a/backend/src/plugins/Automod/triggers/matchWords.ts +++ b/backend/src/plugins/Automod/triggers/matchWords.ts @@ -64,7 +64,7 @@ export const MatchWordsTrigger = automodTrigger()({ // When performing loose matching, allow any amount of whitespace or up to looseMatchingThreshold number of other // characters between the matched characters. E.g. if we're matching banana, a loose match could also match b a n a n a let pattern = trigger.loose_matching - ? [...word].map(c => escapeStringRegexp(c)).join(`(?:\\s*|.{0,${looseMatchingThreshold})`) + ? [...word].map((c) => escapeStringRegexp(c)).join(`(?:\\s*|.{0,${looseMatchingThreshold})`) : escapeStringRegexp(word); if (trigger.only_full_words) { diff --git a/backend/src/plugins/Automod/triggers/memberJoinSpam.ts b/backend/src/plugins/Automod/triggers/memberJoinSpam.ts index 7ab1964a..60ad02a7 100644 --- a/backend/src/plugins/Automod/triggers/memberJoinSpam.ts +++ b/backend/src/plugins/Automod/triggers/memberJoinSpam.ts @@ -30,7 +30,7 @@ export const MemberJoinSpamTrigger = automodTrigger()({ const totalCount = sumRecentActionCounts(matchingActions); if (totalCount >= triggerConfig.amount) { - const extraContexts = matchingActions.map(a => a.context).filter(c => c !== context); + const extraContexts = matchingActions.map((a) => a.context).filter((c) => c !== context); pluginData.state.recentSpam.push({ type: RecentActionType.MemberJoin, diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts index 84cce432..b417fec6 100644 --- a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -35,7 +35,7 @@ export const AddDashboardUserCmd = botControlCmd({ await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]); } - const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); + const userNameList = args.users.map((user) => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); sendSuccessMessage( pluginData, msg.channel as TextChannel, diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts index c7e2f28c..385df13b 100644 --- a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -52,10 +52,7 @@ export const AddServerFromInviteCmd = botControlCmd({ invite.guild.id, msg.author.id, [ApiPermissions.ManageAccess], - moment - .utc() - .add(1, "hour") - .format(DBDateFormat), + moment.utc().add(1, "hour").format(DBDateFormat), ); } diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index 304e7705..9e786b71 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -48,10 +48,7 @@ export const AllowServerCmd = botControlCmd({ args.guildId, msg.author.id, [ApiPermissions.ManageAccess], - moment - .utc() - .add(1, "hour") - .format(DBDateFormat), + moment.utc().add(1, "hour").format(DBDateFormat), ); } diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index 359a6e5b..a97efca6 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -21,7 +21,7 @@ export const ListDashboardUsersCmd = botControlCmd({ const dashboardUsers = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); const users = await Promise.all( - dashboardUsers.map(async perm => ({ + dashboardUsers.map(async (perm) => ({ user: await resolveUser(pluginData.client, perm.target_id), permission: perm, })), diff --git a/backend/src/plugins/BotControl/commands/PerformanceCmd.ts b/backend/src/plugins/BotControl/commands/PerformanceCmd.ts index 6e4c857a..55670507 100644 --- a/backend/src/plugins/BotControl/commands/PerformanceCmd.ts +++ b/backend/src/plugins/BotControl/commands/PerformanceCmd.ts @@ -13,7 +13,7 @@ export const PerformanceCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const stats = pluginData.getKnubInstance().getPluginPerformanceStats(); const averageLoadTimeEntries = Object.entries(stats.averageLoadTimes); - averageLoadTimeEntries.sort(sorter(v => v[1].time, "DESC")); + averageLoadTimeEntries.sort(sorter((v) => v[1].time, "DESC")); const lines = averageLoadTimeEntries.map( ([pluginName, { time }]) => `${pluginName}: **${formatNumber(Math.round(time))}ms**`, ); diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts index 3dfafb42..dff4928d 100644 --- a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -34,7 +34,7 @@ export const RemoveDashboardUserCmd = botControlCmd({ await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id); } - const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); + const userNameList = args.users.map((user) => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); sendSuccessMessage( pluginData, msg.channel as TextChannel, diff --git a/backend/src/plugins/BotControl/commands/ServersCmd.ts b/backend/src/plugins/BotControl/commands/ServersCmd.ts index d226e14c..f8c91b61 100644 --- a/backend/src/plugins/BotControl/commands/ServersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ServersCmd.ts @@ -22,7 +22,7 @@ export const ServersCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const showList = Boolean(args.all || args.initialized || args.uninitialized || args.search); - const search = args.search ? new RegExp([...args.search].map(s => escapeStringRegexp(s)).join(".*"), "i") : null; + const search = args.search ? new RegExp([...args.search].map((s) => escapeStringRegexp(s)).join(".*"), "i") : null; const joinedGuilds = Array.from(pluginData.client.guilds.cache.values()); const loadedGuilds = pluginData.getKnubInstance().getLoadedGuilds(); @@ -32,21 +32,21 @@ export const ServersCmd = botControlCmd({ let filteredGuilds = Array.from(joinedGuilds); if (args.initialized) { - filteredGuilds = filteredGuilds.filter(g => loadedGuildsMap.has(g.id)); + filteredGuilds = filteredGuilds.filter((g) => loadedGuildsMap.has(g.id)); } if (args.uninitialized) { - filteredGuilds = filteredGuilds.filter(g => !loadedGuildsMap.has(g.id)); + filteredGuilds = filteredGuilds.filter((g) => !loadedGuildsMap.has(g.id)); } if (args.search) { - filteredGuilds = filteredGuilds.filter(g => search!.test(`${g.id} ${g.name}`)); + filteredGuilds = filteredGuilds.filter((g) => search!.test(`${g.id} ${g.name}`)); } if (filteredGuilds.length) { - filteredGuilds.sort(sorter(g => g.name.toLowerCase())); + filteredGuilds.sort(sorter((g) => g.name.toLowerCase())); const longestId = filteredGuilds.reduce((longest, guild) => Math.max(longest, guild.id.length), 0); - const lines = filteredGuilds.map(g => { + const lines = filteredGuilds.map((g) => { const paddedId = g.id.padEnd(longestId, " "); const owner = getUser(pluginData.client, g.ownerId); return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.tag}** \`${owner.id}\`)`; @@ -57,7 +57,7 @@ export const ServersCmd = botControlCmd({ } } else { const total = joinedGuilds.length; - const initialized = joinedGuilds.filter(g => loadedGuildsMap.has(g.id)).length; + const initialized = joinedGuilds.filter((g) => loadedGuildsMap.has(g.id)).length; const unInitialized = total - initialized; msg.channel.send( diff --git a/backend/src/plugins/Cases/functions/createCaseNote.ts b/backend/src/plugins/Cases/functions/createCaseNote.ts index c03a5b3c..30f0ac2e 100644 --- a/backend/src/plugins/Cases/functions/createCaseNote.ts +++ b/backend/src/plugins/Cases/functions/createCaseNote.ts @@ -22,7 +22,7 @@ export async function createCaseNote(pluginData: GuildPluginData `__[${d}]__`).join(" ") + " " + body; + body = args.noteDetails.map((d) => `__[${d}]__`).join(" ") + " " + body; } await pluginData.state.cases.createNote(theCase.id, { diff --git a/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts b/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts index 94f35d7f..eb28584d 100644 --- a/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts +++ b/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts @@ -7,11 +7,11 @@ export async function getCaseTypeAmountForUserId( userID: string, type: CaseTypes, ): Promise { - const cases = (await pluginData.state.cases.getByUserId(userID)).filter(c => !c.is_hidden); + const cases = (await pluginData.state.cases.getByUserId(userID)).filter((c) => !c.is_hidden); let typeAmount = 0; if (cases.length > 0) { - cases.forEach(singleCase => { + cases.forEach((singleCase) => { if (singleCase.type === type.valueOf()) { typeAmount++; } diff --git a/backend/src/plugins/Censor/CensorPlugin.ts b/backend/src/plugins/Censor/CensorPlugin.ts index 8a533093..d8b74477 100644 --- a/backend/src/plugins/Censor/CensorPlugin.ts +++ b/backend/src/plugins/Censor/CensorPlugin.ts @@ -71,10 +71,10 @@ export const CensorPlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state, guild } = pluginData; - state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); + state.onMessageCreateFn = (msg) => onMessageCreate(pluginData, msg); state.savedMessages.events.on("create", state.onMessageCreateFn); - state.onMessageUpdateFn = msg => onMessageUpdate(pluginData, msg); + state.onMessageUpdateFn = (msg) => onMessageUpdate(pluginData, msg); state.savedMessages.events.on("update", state.onMessageUpdateFn); }, diff --git a/backend/src/plugins/Censor/util/applyFiltersToMsg.ts b/backend/src/plugins/Censor/util/applyFiltersToMsg.ts index ffadcc67..5e92a305 100644 --- a/backend/src/plugins/Censor/util/applyFiltersToMsg.ts +++ b/backend/src/plugins/Censor/util/applyFiltersToMsg.ts @@ -19,7 +19,7 @@ export async function applyFiltersToMsg( let messageContent = savedMessage.data.content || ""; if (savedMessage.data.attachments) messageContent += " " + JSON.stringify(savedMessage.data.attachments); if (savedMessage.data.embeds) { - const embeds = (savedMessage.data.embeds as MessageEmbed[]).map(e => cloneDeep(e)); + const embeds = (savedMessage.data.embeds as MessageEmbed[]).map((e) => cloneDeep(e)); for (const embed of embeds) { if (embed.type === "video") { // Ignore video descriptions as they're not actually shown on the embed @@ -52,7 +52,7 @@ export async function applyFiltersToMsg( const inviteCodes = getInviteCodesInString(messageContent); const invites: Array = await Promise.all( - inviteCodes.map(code => resolveInvite(pluginData.client, code)), + inviteCodes.map((code) => resolveInvite(pluginData.client, code)), ); for (const invite of invites) { diff --git a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts index 8bdd4133..9ff38a84 100644 --- a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts +++ b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts @@ -14,5 +14,5 @@ export const ChannelArchiverPlugin = zeppelinGuildPlugin"}`; + let content = `[${ts}] [${message.author.id}] [${message.author.username}#${message.author.discriminator}]: ${ + message.content || "" + }`; if (message.attachments.size) { if (args["attachment-channel"]) { diff --git a/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts b/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts index bfd94cbe..8ecde5e7 100644 --- a/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts +++ b/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts @@ -14,9 +14,9 @@ export async function getCompanionChannelOptsForVoiceChannelId( const config = await pluginData.config.getMatchingConfig({ userId, channelId: voiceChannel.id }); return Object.values(config.entries) .filter( - opts => + (opts) => opts.voice_channel_ids.includes(voiceChannel.id) || (voiceChannel.parentId && opts.voice_channel_ids.includes(voiceChannel.parentId)), ) - .map(opts => Object.assign({}, defaultCompanionChannelOpts, opts)); + .map((opts) => Object.assign({}, defaultCompanionChannelOpts, opts)); } diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts index c4e66f49..ea09b548 100644 --- a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -65,7 +65,7 @@ export async function handleCompanionPermissions( if (!channel || !(channel instanceof TextChannel)) continue; pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channelId, 3 * 1000); const fullSerialized = new Permissions(BigInt(permissions)).serialize(); - const onlyAllowed = filterObject(fullSerialized, v => v === true); + const onlyAllowed = filterObject(fullSerialized, (v) => v === true); await channel.permissionOverwrites.create(userId, onlyAllowed, { reason: `Companion Channel for ${voiceChannel!.id} | User Joined`, }); diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index e69c84db..5735337a 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -48,8 +48,9 @@ export async function muteAction( const muteMessage = `Muted **${result.case.user_name}** ${ durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" - } (Case #${result.case.case_number}) (user notified via ${result.notifyResult.method ?? - "dm"})\nPlease update the new case with the \`update\` command`; + } (Case #${result.case.case_number}) (user notified via ${ + result.notifyResult.method ?? "dm" + })\nPlease update the new case with the \`update\` command`; interaction.followUp({ ephemeral: true, content: muteMessage }); } catch (e) { diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts index 77311ba7..8272d8b3 100644 --- a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts +++ b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts @@ -26,7 +26,7 @@ export async function loadAllCommands(pluginData: GuildPluginData { + const setCommands = await comms.set(newCommands, pluginData.guild.id).catch((e) => { pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unable to overwrite context menus: ${e}` }); return undefined; }); diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index d2566515..07738195 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -55,7 +55,7 @@ const defaultOptions: PluginOptions = { ], }; -const configPreprocessor: ConfigPreprocessorFn = options => { +const configPreprocessor: ConfigPreprocessorFn = (options) => { for (const [counterName, counter] of Object.entries(options.config?.counters || {})) { counter.name = counterName; counter.per_user = counter.per_user ?? false; diff --git a/backend/src/plugins/Counters/commands/CountersListCmd.ts b/backend/src/plugins/Counters/commands/CountersListCmd.ts index c92a2fe4..f725ac71 100644 --- a/backend/src/plugins/Counters/commands/CountersListCmd.ts +++ b/backend/src/plugins/Counters/commands/CountersListCmd.ts @@ -13,13 +13,13 @@ export const CountersListCmd = typedGuildCommand()({ async run({ pluginData, message, args }) { const config = await pluginData.config.getForMessage(message); - const countersToShow = Array.from(Object.values(config.counters)).filter(c => c.can_view !== false); + const countersToShow = Array.from(Object.values(config.counters)).filter((c) => c.can_view !== false); if (!countersToShow.length) { sendErrorMessage(pluginData, message.channel, "No counters are configured for this server"); return; } - const counterLines = countersToShow.map(counter => { + const counterLines = countersToShow.map((counter) => { const title = counter.pretty_name ? `**${counter.pretty_name}** (\`${counter.name}\`)` : `\`${counter.name}\``; const types: string[] = []; diff --git a/backend/src/plugins/Counters/functions/changeCounterValue.ts b/backend/src/plugins/Counters/functions/changeCounterValue.ts index de61d9be..74a295f9 100644 --- a/backend/src/plugins/Counters/functions/changeCounterValue.ts +++ b/backend/src/plugins/Counters/functions/changeCounterValue.ts @@ -38,10 +38,10 @@ export async function changeCounterValue( if (triggers) { const triggersArr = Array.from(triggers.values()); await Promise.all( - triggersArr.map(trigger => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)), + triggersArr.map((trigger) => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)), ); await Promise.all( - triggersArr.map(trigger => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)), + triggersArr.map((trigger) => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)), ); } diff --git a/backend/src/plugins/Counters/functions/decayCounter.ts b/backend/src/plugins/Counters/functions/decayCounter.ts index c7b813ea..0317ee8c 100644 --- a/backend/src/plugins/Counters/functions/decayCounter.ts +++ b/backend/src/plugins/Counters/functions/decayCounter.ts @@ -25,8 +25,8 @@ export async function decayCounter( const triggers = pluginData.state.counterTriggersByCounterId.get(counterId); if (triggers) { const triggersArr = Array.from(triggers.values()); - await Promise.all(triggersArr.map(trigger => checkAllValuesForTrigger(pluginData, counterName, trigger))); - await Promise.all(triggersArr.map(trigger => checkAllValuesForReverseTrigger(pluginData, counterName, trigger))); + await Promise.all(triggersArr.map((trigger) => checkAllValuesForTrigger(pluginData, counterName, trigger))); + await Promise.all(triggersArr.map((trigger) => checkAllValuesForReverseTrigger(pluginData, counterName, trigger))); } lock.unlock(); diff --git a/backend/src/plugins/Counters/functions/setCounterValue.ts b/backend/src/plugins/Counters/functions/setCounterValue.ts index 697c8503..7a988ee2 100644 --- a/backend/src/plugins/Counters/functions/setCounterValue.ts +++ b/backend/src/plugins/Counters/functions/setCounterValue.ts @@ -35,10 +35,10 @@ export async function setCounterValue( if (triggers) { const triggersArr = Array.from(triggers.values()); await Promise.all( - triggersArr.map(trigger => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)), + triggersArr.map((trigger) => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)), ); await Promise.all( - triggersArr.map(trigger => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)), + triggersArr.map((trigger) => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)), ); } diff --git a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts index 6b83770a..52d6fd59 100644 --- a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts +++ b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts @@ -29,7 +29,7 @@ export async function addRoleAction( throw new ActionError("Missing permissions"); } const rolesToAdd = (Array.isArray(action.role) ? action.role : [action.role]).filter( - id => !target.roles.cache.has(id), + (id) => !target.roles.cache.has(id), ); if (rolesToAdd.length === 0) { throw new ActionError("Target already has the role(s) specified"); diff --git a/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts b/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts index bc4e8d0f..c93ad872 100644 --- a/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts +++ b/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts @@ -5,7 +5,7 @@ export const GuildBanRemoveAlertsEvt = locateUserEvt({ async listener(meta) { const alerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.ban.user.id); - alerts.forEach(alert => { + alerts.forEach((alert) => { meta.pluginData.state.alerts.delete(alert.id); }); }, diff --git a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts index eaedc015..6510540c 100644 --- a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts +++ b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts @@ -19,7 +19,7 @@ export const VoiceStateUpdateAlertEvt = locateUserEvt({ const triggeredAlerts = await meta.pluginData.state.alerts.getAlertsByUserId(memberId); const voiceChannel = meta.args.oldState.channel!; - triggeredAlerts.forEach(alert => { + triggeredAlerts.forEach((alert) => { const txtChannel = meta.pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel; txtChannel.send({ content: `🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``, diff --git a/backend/src/plugins/LocateUser/utils/fillAlertsList.ts b/backend/src/plugins/LocateUser/utils/fillAlertsList.ts index 3c5c90d1..56e4315f 100644 --- a/backend/src/plugins/LocateUser/utils/fillAlertsList.ts +++ b/backend/src/plugins/LocateUser/utils/fillAlertsList.ts @@ -4,7 +4,7 @@ import { LocateUserPluginType } from "../types"; export async function fillActiveAlertsList(pluginData: GuildPluginData) { const allAlerts = await pluginData.state.alerts.getAllGuildAlerts(); - allAlerts.forEach(alert => { + allAlerts.forEach((alert) => { if (!pluginData.state.usersWithAlerts.includes(alert.user_id)) { pluginData.state.usersWithAlerts.push(alert.user_id); } diff --git a/backend/src/plugins/LocateUser/utils/sendAlerts.ts b/backend/src/plugins/LocateUser/utils/sendAlerts.ts index 67f865bf..d93bf47d 100644 --- a/backend/src/plugins/LocateUser/utils/sendAlerts.ts +++ b/backend/src/plugins/LocateUser/utils/sendAlerts.ts @@ -10,7 +10,7 @@ export async function sendAlerts(pluginData: GuildPluginData { + triggeredAlerts.forEach((alert) => { const prepend = `<@!${alert.requestor_id}>, an alert requested by you has triggered!\nReminder: \`${alert.body}\`\n`; const txtChannel = pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel; sendWhere(pluginData, member, txtChannel, prepend); diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index a6eb8443..cc77ac90 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -177,7 +177,7 @@ export const LogsPlugin = zeppelinGuildPlugin()({ ], public: { - getLogMessage: pluginData => { + getLogMessage: (pluginData) => { return ( type: TLogType, data: TypedTemplateSafeValueContainer, @@ -277,10 +277,10 @@ export const LogsPlugin = zeppelinGuildPlugin()({ state.logListener = ({ type, data }) => log(pluginData, type, data); state.guildLogs.on("log", state.logListener); - state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg); + state.onMessageDeleteFn = (msg) => onMessageDelete(pluginData, msg); state.savedMessages.events.on("delete", state.onMessageDeleteFn); - state.onMessageDeleteBulkFn = msg => onMessageDeleteBulk(pluginData, msg); + state.onMessageDeleteBulkFn = (msg) => onMessageDeleteBulk(pluginData, msg); state.savedMessages.events.on("deleteBulk", state.onMessageDeleteBulkFn); state.onMessageUpdateFn = (newMsg, oldMsg) => onMessageUpdate(pluginData, newMsg, oldMsg); diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index 07e0165f..061ab4fe 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -58,10 +58,10 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ logMemberRoleChanges(pluginData, { member, addedRoles: addedRoles.map( - roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + (roleId) => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, ), removedRoles: removedRoles.map( - roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + (roleId) => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, ), mod: null, }); @@ -70,7 +70,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ logMemberRoleAdd(pluginData, { member, roles: addedRoles.map( - roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + (roleId) => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, ), mod: null, }); @@ -79,7 +79,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ logMemberRoleRemove(pluginData, { member, roles: removedRoles.map( - roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, + (roleId) => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }, ), mod: null, }); diff --git a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts index ffa68c03..d7046826 100644 --- a/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts +++ b/backend/src/plugins/Logs/logFunctions/logAutomodAction.ts @@ -21,7 +21,7 @@ export function logAutomodAction(pluginData: GuildPluginData, da createTypedTemplateSafeValueContainer({ rule: data.rule, user: data.user ? userToTemplateSafeUser(data.user) : null, - users: data.users.map(user => userToTemplateSafeUser(user)), + users: data.users.map((user) => userToTemplateSafeUser(user)), actionsTaken: data.actionsTaken, matchSummary: data.matchSummary ?? "", }), diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts index bfbc03fc..2db68838 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleAdd.ts @@ -19,7 +19,7 @@ export function logMemberRoleAdd(pluginData: GuildPluginData, da createTypedTemplateSafeValueContainer({ mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), - roles: data.roles.map(r => r.name).join(", "), + roles: data.roles.map((r) => r.name).join(", "), }), { userId: data.member.id, diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts index b4364419..7eee36a9 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleChanges.ts @@ -21,8 +21,8 @@ export function logMemberRoleChanges(pluginData: GuildPluginData createTypedTemplateSafeValueContainer({ mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), - addedRoles: data.addedRoles.map(r => r.name).join(", "), - removedRoles: data.removedRoles.map(r => r.name).join(", "), + addedRoles: data.addedRoles.map((r) => r.name).join(", "), + removedRoles: data.removedRoles.map((r) => r.name).join(", "), }), { userId: data.member.id, diff --git a/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts index a32f9cd9..54dfff29 100644 --- a/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts +++ b/backend/src/plugins/Logs/logFunctions/logMemberRoleRemove.ts @@ -19,7 +19,7 @@ export function logMemberRoleRemove(pluginData: GuildPluginData, createTypedTemplateSafeValueContainer({ mod: data.mod ? userToTemplateSafeUser(data.mod) : null, member: memberToTemplateSafeMember(data.member), - roles: data.roles.map(r => r.name).join(", "), + roles: data.roles.map((r) => r.name).join(", "), }), { userId: data.member.id, diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index 39d9d440..71f55c65 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -406,10 +406,7 @@ export const LogTypeData = z.object({ }), [LogType.MEMBER_ROLE_CHANGES]: z.object({ - mod: z - .instanceof(TemplateSafeUser) - .or(z.instanceof(TemplateSafeUnknownUser)) - .or(z.null()), + mod: z.instanceof(TemplateSafeUser).or(z.instanceof(TemplateSafeUnknownUser)).or(z.null()), member: z.instanceof(TemplateSafeMember), addedRoles: z.string(), removedRoles: z.string(), diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index 81714486..3d6bcec5 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -60,7 +60,7 @@ export async function getLogMessage( const inputArray = Array.isArray(inputUserOrMember) ? inputUserOrMember : [inputUserOrMember]; // TODO: Resolve IDs to users/members const usersOrMembers = inputArray.filter( - v => v instanceof TemplateSafeUser || v instanceof TemplateSafeMember, + (v) => v instanceof TemplateSafeUser || v instanceof TemplateSafeMember, ) as Array; const mentions: string[] = []; @@ -83,7 +83,7 @@ export async function getLogMessage( const memberConfig = (await pluginData.config.getMatchingConfig({ level, - memberRoles: member ? member.roles.map(r => r.id) : [], + memberRoles: member ? member.roles.map((r) => r.id) : [], userId: user.id, })) || ({} as any); @@ -97,7 +97,7 @@ export async function getLogMessage( return mentions.join(", "); }, - channelMention: channel => { + channelMention: (channel) => { if (!channel) return ""; return verboseChannelMention(channel); }, @@ -109,12 +109,12 @@ export async function getLogMessage( if (type === LogType.BOT_ALERT) { const valuesWithoutTmplEval = { ...values }; - values.tmplEval = str => { + values.tmplEval = (str) => { return renderTemplate(str, valuesWithoutTmplEval); }; } - const renderLogString = str => renderTemplate(str, values); + const renderLogString = (str) => renderTemplate(str, values); let formatted; try { diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index 0afc1e1d..4c2ca0f9 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -100,14 +100,14 @@ export async function log( channelId, new MessageBuffer({ timeout: batchTime, - consume: part => { + consume: (part) => { const parse: MessageMentionTypes[] = pluginData.config.get().allow_user_mentions ? ["users"] : []; channel .send({ ...part, allowedMentions: { parse }, }) - .catch(err => { + .catch((err) => { // tslint:disable-next-line:no-console console.warn( `Error while sending ${typeStr} log to ${pluginData.guild.id}/${channelId}: ${err.message}`, diff --git a/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts b/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts index c62e260b..1dec0101 100644 --- a/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts +++ b/backend/src/plugins/Logs/util/onMessageDeleteBulk.ts @@ -17,7 +17,7 @@ export async function onMessageDeleteBulk(pluginData: GuildPluginData `\`${item.user_id}\``))); + const authorIds = Array.from(new Set(savedMessages.map((item) => `\`${item.user_id}\``))); logMessageDeleteBulk(pluginData, { count: savedMessages.length, diff --git a/backend/src/plugins/Logs/util/onMessageUpdate.ts b/backend/src/plugins/Logs/util/onMessageUpdate.ts index b3269b45..efd92d28 100644 --- a/backend/src/plugins/Logs/util/onMessageUpdate.ts +++ b/backend/src/plugins/Logs/util/onMessageUpdate.ts @@ -17,12 +17,12 @@ export async function onMessageUpdate( let logUpdate = false; const oldEmbedsToCompare = ((oldSavedMessage.data.embeds || []) as MessageEmbed[]) - .map(e => cloneDeep(e)) - .filter(e => (e as MessageEmbed).type === "rich"); + .map((e) => cloneDeep(e)) + .filter((e) => (e as MessageEmbed).type === "rich"); const newEmbedsToCompare = ((savedMessage.data.embeds || []) as MessageEmbed[]) - .map(e => cloneDeep(e)) - .filter(e => (e as MessageEmbed).type === "rich"); + .map((e) => cloneDeep(e)) + .filter((e) => (e as MessageEmbed).type === "rich"); for (const embed of [...oldEmbedsToCompare, ...newEmbedsToCompare]) { if (embed.thumbnail) { diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 3e1e91a6..5acaf5fc 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -52,7 +52,11 @@ export const MessageCreateEvt = messageSaverEvt({ console.warn(`Tried to save duplicate message from messageCreate event: ${context} / saved at: ${timestamp}`); return; } - recentlyCreatedMessages.set(meta.args.message.id, [meta.pluginData.state.debugId, Date.now(), meta.pluginData.guild.id]); + recentlyCreatedMessages.set(meta.args.message.id, [ + meta.pluginData.state.debugId, + Date.now(), + meta.pluginData.guild.id, + ]); await meta.pluginData.state.savedMessages.createFromMsg(meta.args.message); }, @@ -97,7 +101,7 @@ export const MessageDeleteBulkEvt = messageSaverEvt({ allowSelf: true, async listener(meta) { - const ids = meta.args.messages.map(m => m.id); + const ids = meta.args.messages.map((m) => m.id); await meta.pluginData.state.savedMessages.markBulkAsDeleted(ids); }, }); diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts index 45acbb73..14e89870 100644 --- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesModCmd.ts @@ -46,9 +46,9 @@ export const CasesModCmd = modActionsCmd({ pluginData.client, msg.channel, totalPages, - async page => { + async (page) => { const cases = await casesPlugin.getRecentCasesByMod(modId, casesPerPage, (page - 1) * casesPerPage); - const lines = await asyncMap(cases, c => casesPlugin.getCaseSummary(c, true, msg.author.id)); + const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id)); const firstCaseNum = (page - 1) * casesPerPage + 1; const lastCaseNum = page * casesPerPage; diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts index 39d4050b..90023026 100644 --- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts @@ -53,13 +53,13 @@ export const CasesUserCmd = modActionsCmd({ if (typesToShow.length > 0) { // Reversed: Hide specified types - if (args.reverseFilters) cases = cases.filter(c => !typesToShow.includes(c.type)); + if (args.reverseFilters) cases = cases.filter((c) => !typesToShow.includes(c.type)); // Normal: Show only specified types - else cases = cases.filter(c => typesToShow.includes(c.type)); + else cases = cases.filter((c) => typesToShow.includes(c.type)); } - const normalCases = cases.filter(c => !c.is_hidden); - const hiddenCases = cases.filter(c => c.is_hidden); + const normalCases = cases.filter((c) => !c.is_hidden); + const hiddenCases = cases.filter((c) => c.is_hidden); const userName = user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : user.tag; @@ -83,7 +83,7 @@ export const CasesUserCmd = modActionsCmd({ } else { // Compact view (= regular message with a preview of each case) const casesPlugin = pluginData.getPlugin(CasesPlugin); - const lines = await asyncMap(casesToDisplay, c => casesPlugin.getCaseSummary(c, true, msg.author.id)); + const lines = await asyncMap(casesToDisplay, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id)); const prefix = getGuildPrefix(pluginData); const linesPerChunk = 10; diff --git a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts index 4fee866f..83044c85 100644 --- a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts @@ -42,7 +42,7 @@ export const MassunbanCmd = modActionsCmd({ // Ignore automatic unban cases and logs for these users // We'll create our own cases below and post a single "mass unbanned" log instead - args.userIds.forEach(userId => { + args.userIds.forEach((userId) => { // Use longer timeouts since this can take a while ignoreEvent(pluginData, IgnoredEventType.Unban, userId, 120 * 1000); pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, userId, 120 * 1000); @@ -91,19 +91,19 @@ export const MassunbanCmd = modActionsCmd({ }); if (failedUnbans.length) { - const notBanned = failedUnbans.filter(x => x.reason === UnbanFailReasons.NOT_BANNED); - const unbanFailed = failedUnbans.filter(x => x.reason === UnbanFailReasons.UNBAN_FAILED); + const notBanned = failedUnbans.filter((x) => x.reason === UnbanFailReasons.NOT_BANNED); + const unbanFailed = failedUnbans.filter((x) => x.reason === UnbanFailReasons.UNBAN_FAILED); let failedMsg = ""; if (notBanned.length > 0) { failedMsg += `${notBanned.length}x ${UnbanFailReasons.NOT_BANNED}:`; - notBanned.forEach(fail => { + notBanned.forEach((fail) => { failedMsg += " " + fail.userId; }); } if (unbanFailed.length > 0) { failedMsg += `\n${unbanFailed.length}x ${UnbanFailReasons.UNBAN_FAILED}:`; - unbanFailed.forEach(fail => { + unbanFailed.forEach((fail) => { failedMsg += " " + fail.userId; }); } diff --git a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts index 7b5d50b1..ecd696df 100644 --- a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts @@ -53,7 +53,7 @@ export const MassmuteCmd = modActionsCmd({ // Ignore automatic mute cases and logs for these users // We'll create our own cases below and post a single "mass muted" log instead - args.userIds.forEach(userId => { + args.userIds.forEach((userId) => { // Use longer timeouts since this can take a while pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_MUTE, userId, 120 * 1000); }); diff --git a/backend/src/plugins/ModActions/functions/clearIgnoredEvents.ts b/backend/src/plugins/ModActions/functions/clearIgnoredEvents.ts index cbcf082f..c16dabcf 100644 --- a/backend/src/plugins/ModActions/functions/clearIgnoredEvents.ts +++ b/backend/src/plugins/ModActions/functions/clearIgnoredEvents.ts @@ -7,7 +7,7 @@ export function clearIgnoredEvents( userId: string, ) { pluginData.state.ignoredEvents.splice( - pluginData.state.ignoredEvents.findIndex(info => type === info.type && userId === info.userId), + pluginData.state.ignoredEvents.findIndex((info) => type === info.type && userId === info.userId), 1, ); } diff --git a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts b/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts index 809fe909..77194a3f 100644 --- a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts +++ b/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts @@ -1,6 +1,6 @@ import { MessageAttachment } from "discord.js"; export function formatReasonWithAttachments(reason: string, attachments: MessageAttachment[]) { - const attachmentUrls = attachments.map(a => a.url); + const attachmentUrls = attachments.map((a) => a.url); return ((reason || "") + " " + attachmentUrls.join(" ")).trim(); } diff --git a/backend/src/plugins/ModActions/functions/isEventIgnored.ts b/backend/src/plugins/ModActions/functions/isEventIgnored.ts index 32ae4acf..8ec27baf 100644 --- a/backend/src/plugins/ModActions/functions/isEventIgnored.ts +++ b/backend/src/plugins/ModActions/functions/isEventIgnored.ts @@ -6,5 +6,5 @@ export function isEventIgnored( type: IgnoredEventType, userId: string, ) { - return pluginData.state.ignoredEvents.some(info => type === info.type && userId === info.userId); + return pluginData.state.ignoredEvents.some((info) => type === info.type && userId === info.userId); } diff --git a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts index ad5b9666..cd18bf0f 100644 --- a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts @@ -13,7 +13,7 @@ export const ClearBannedMutesCmd = mutesCmd({ const activeMutes = await pluginData.state.mutes.getActiveMutes(); const bans = await pluginData.guild.bans.fetch({ cache: true }); - const bannedIds = bans.map(b => b.user.id); + const bannedIds = bans.map((b) => b.user.id); await msg.channel.send(`Found ${activeMutes.length} mutes and ${bannedIds.length} bans, cross-referencing...`); diff --git a/backend/src/plugins/Mutes/commands/MutesCmd.ts b/backend/src/plugins/Mutes/commands/MutesCmd.ts index 99356e54..eeff2c4f 100644 --- a/backend/src/plugins/Mutes/commands/MutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/MutesCmd.ts @@ -53,12 +53,12 @@ export const MutesCmd = mutesCmd({ if (args.manual) { // Show only manual mutes (i.e. "Muted" role added without a logged mute) - const muteUserIds = new Set(activeMutes.map(m => m.user_id)); + const muteUserIds = new Set(activeMutes.map((m) => m.user_id)); const manuallyMutedMembers: GuildMember[] = []; const muteRole = pluginData.config.get().mute_role; if (muteRole) { - pluginData.guild.members.cache.forEach(member => { + pluginData.guild.members.cache.forEach((member) => { if (muteUserIds.has(member.id)) return; if (member.roles.cache.has(muteRole as Snowflake)) manuallyMutedMembers.push(member); }); @@ -66,7 +66,7 @@ export const MutesCmd = mutesCmd({ totalMutes = manuallyMutedMembers.length; - lines = manuallyMutedMembers.map(member => { + lines = manuallyMutedMembers.map((member) => { return `<@!${member.id}> (**${member.user.tag}**, \`${member.id}\`) 🔧 Manual mute`; }); } else { @@ -76,11 +76,8 @@ export const MutesCmd = mutesCmd({ // Filter: mute age if (args.age) { - const cutoff = moment - .utc() - .subtract(args.age, "ms") - .format(DBDateFormat); - filteredMutes = filteredMutes.filter(m => m.created_at <= cutoff); + const cutoff = moment.utc().subtract(args.age, "ms").format(DBDateFormat); + filteredMutes = filteredMutes.filter((m) => m.created_at <= cutoff); hasFilters = true; } @@ -93,7 +90,7 @@ export const MutesCmd = mutesCmd({ if (!member) { if (!bannedIds) { const bans = await pluginData.guild.bans.fetch({ cache: true }); - bannedIds = bans.map(u => u.user.id); + bannedIds = bans.map((u) => u.user.id); } muteWithDetails.banned = bannedIds.includes(mute.user_id); @@ -106,18 +103,18 @@ export const MutesCmd = mutesCmd({ // Filter: left the server if (args.left != null) { - filteredMutes = filteredMutes.filter(m => (args.left && !m.member) || (!args.left && m.member)); + filteredMutes = filteredMutes.filter((m) => (args.left && !m.member) || (!args.left && m.member)); hasFilters = true; } totalMutes = filteredMutes.length; // Create a message line for each mute - const caseIds = filteredMutes.map(m => m.case_id).filter(v => !!v); + const caseIds = filteredMutes.map((m) => m.case_id).filter((v) => !!v); const muteCases = caseIds.length ? await pluginData.state.cases.get(caseIds) : []; const muteCasesById = muteCases.reduce((map, c) => map.set(c.id, c), new Map()); - lines = filteredMutes.map(mute => { + lines = filteredMutes.map((mute) => { const user = pluginData.client.users.resolve(mute.user_id as Snowflake); const username = user ? user.tag : "Unknown#0000"; const theCase = muteCasesById.get(mute.case_id); @@ -152,7 +149,7 @@ export const MutesCmd = mutesCmd({ let currentPage = 1; const totalPages = Math.ceil(lines.length / mutesPerPage); - const drawListPage = async page => { + const drawListPage = async (page) => { page = Math.max(1, Math.min(totalPages, page)); currentPage = page; @@ -197,19 +194,9 @@ export const MutesCmd = mutesCmd({ const idMod = `${listMessage.id}:muteList`; const buttons: MessageButton[] = []; - buttons.push( - new MessageButton() - .setStyle("SECONDARY") - .setEmoji("⬅") - .setCustomId(`previousButton:${idMod}`), - ); + buttons.push(new MessageButton().setStyle("SECONDARY").setEmoji("⬅").setCustomId(`previousButton:${idMod}`)); - buttons.push( - new MessageButton() - .setStyle("SECONDARY") - .setEmoji("➡") - .setCustomId(`nextButton:${idMod}`), - ); + buttons.push(new MessageButton().setStyle("SECONDARY").setEmoji("➡").setCustomId(`nextButton:${idMod}`)); const row = new MessageActionRow().addComponents(buttons); await listMessage.edit({ components: [row] }); diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index 308fa5f1..dc83fee6 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -22,7 +22,7 @@ export async function clearExpiredMutes(pluginData: GuildPluginData roleId !== muteRole); + const newRoles = [...member.roles.cache.keys()].filter((roleId) => roleId !== muteRole); for (const toRestore of mute.roles_to_restore) { if (guildRoles.has(toRestore) && toRestore !== muteRole && !newRoles.includes(toRestore)) { newRoles.push(toRestore); diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 4c39e091..cf0aeca0 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -67,12 +67,12 @@ export async function muteUser( if (!Array.isArray(removeRoles)) { if (removeRoles) { // exclude managed roles from being removed - const managedRoles = pluginData.guild.roles.cache.filter(x => x.managed).map(y => y.id); - newRoles = currentUserRoles.filter(r => !managedRoles.includes(r)); + const managedRoles = pluginData.guild.roles.cache.filter((x) => x.managed).map((y) => y.id); + newRoles = currentUserRoles.filter((r) => !managedRoles.includes(r)); await member.roles.set(newRoles as Snowflake[]); } } else { - newRoles = currentUserRoles.filter(x => !(removeRoles).includes(x)); + newRoles = currentUserRoles.filter((x) => !(removeRoles).includes(x)); await member.roles.set(newRoles as Snowflake[]); } @@ -82,7 +82,7 @@ export async function muteUser( rolesToRestore = currentUserRoles; } } else { - rolesToRestore = currentUserRoles.filter(x => (restoreRoles).includes(x)); + rolesToRestore = currentUserRoles.filter((x) => (restoreRoles).includes(x)); } // Apply mute role if it's missing @@ -100,9 +100,9 @@ export async function muteUser( } const zep = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); - const zepRoles = pluginData.guild.roles.cache.filter(x => zep!.roles.cache.has(x.id)); + const zepRoles = pluginData.guild.roles.cache.filter((x) => zep!.roles.cache.has(x.id)); // If we have roles and one of them is above the muted role, throw generic error - if (zepRoles.size >= 0 && zepRoles.some(zepRole => zepRole.position > actualMuteRole.position)) { + if (zepRoles.size >= 0 && zepRoles.some((zepRole) => zepRole.position > actualMuteRole.position)) { lock.unlock(); logs.logBotAlert({ body: `Cannot mute user ${member.id}: ${e}`, diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index 403d37d8..0f2d88d5 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -43,7 +43,7 @@ export async function unmuteUser( } if (existingMute?.roles_to_restore) { const guildRoles = pluginData.guild.roles.cache; - const newRoles = [...member.roles.cache.keys()].filter(roleId => roleId !== muteRole); + const newRoles = [...member.roles.cache.keys()].filter((roleId) => roleId !== muteRole); for (const toRestore of existingMute.roles_to_restore) { if (guildRoles.has(toRestore) && toRestore !== muteRole && !newRoles.includes(toRestore)) { newRoles.push(toRestore); diff --git a/backend/src/plugins/NameHistory/commands/NamesCmd.ts b/backend/src/plugins/NameHistory/commands/NamesCmd.ts index 710e7ba6..fcb8c242 100644 --- a/backend/src/plugins/NameHistory/commands/NamesCmd.ts +++ b/backend/src/plugins/NameHistory/commands/NamesCmd.ts @@ -26,9 +26,9 @@ export const NamesCmd = nameHistoryCmd({ } const nicknameRows = nicknames.map( - r => `\`[${r.timestamp}]\` ${r.nickname ? `**${disableCodeBlocks(r.nickname)}**` : "*None*"}`, + (r) => `\`[${r.timestamp}]\` ${r.nickname ? `**${disableCodeBlocks(r.nickname)}**` : "*None*"}`, ); - const usernameRows = usernames.map(r => `\`[${r.timestamp}]\` **${disableCodeBlocks(r.username)}**`); + const usernameRows = usernames.map((r) => `\`[${r.timestamp}]\` **${disableCodeBlocks(r.username)}**`); const user = await pluginData.client.users.fetch(args.userId as Snowflake).catch(() => null); const currentUsername = user ? user.tag : args.userId; diff --git a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts index 17b1fc17..a0a88766 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts @@ -21,7 +21,7 @@ export const ScheduledPostsListCmd = postCmd({ scheduledPosts.sort(sorter("post_at")); let i = 1; - const postLines = scheduledPosts.map(p => { + const postLines = scheduledPosts.map((p) => { let previewText = p.content.content || p.content.embeds?.[0]?.description || p.content.embeds?.[0]?.title || ""; const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH; diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index dc1d662e..32320368 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -147,18 +147,10 @@ export async function actualPostCmd( channel_id: targetChannel.id, content, attachments: [...msg.attachments.values()], - post_at: postAt - .clone() - .tz("Etc/UTC") - .format(DBDateFormat), + post_at: postAt.clone().tz("Etc/UTC").format(DBDateFormat), enable_mentions: opts["enable-mentions"], repeat_interval: opts.repeat, - repeat_until: repeatUntil - ? repeatUntil - .clone() - .tz("Etc/UTC") - .format(DBDateFormat) - : null, + repeat_until: repeatUntil ? repeatUntil.clone().tz("Etc/UTC").format(DBDateFormat) : null, repeat_times: repeatTimes ?? null, }); diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index 076e13ed..a217c1e5 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -42,7 +42,7 @@ const defaultOptions: PluginOptions = { const MAXIMUM_COMPONENT_ROWS = 5; -const configPreprocessor: ConfigPreprocessorFn = options => { +const configPreprocessor: ConfigPreprocessorFn = (options) => { if (options.config.button_groups) { for (const [groupName, group] of Object.entries(options.config.button_groups)) { const defaultButtonNames = Object.keys(group.default_buttons); diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index 64a6e412..e724168a 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -58,17 +58,15 @@ export const InitReactionRolesCmd = reactionRolesCmd({ const emojiRolePairs: TReactionRolePair[] = args.reactionRolePairs .trim() .split("\n") - .map(v => v.split(/[\s=,]+/).map(v => v.trim())) // tslint:disable-line - .map( - (pair): TReactionRolePair => { - const customEmojiMatch = pair[0].match(/^$/); - if (customEmojiMatch) { - return [customEmojiMatch[2], pair[1], customEmojiMatch[1]]; - } else { - return pair as TReactionRolePair; - } - }, - ); + .map((v) => v.split(/[\s=,]+/).map((v) => v.trim())) // tslint:disable-line + .map((pair): TReactionRolePair => { + const customEmojiMatch = pair[0].match(/^$/); + if (customEmojiMatch) { + return [customEmojiMatch[2], pair[1], customEmojiMatch[1]]; + } else { + return pair as TReactionRolePair; + } + }); // Verify the specified emojis and roles are valid and usable for (const pair of emojiRolePairs) { diff --git a/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts index 040acf1b..c9747ed2 100644 --- a/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts @@ -31,9 +31,7 @@ export const PostButtonRolesCmd = reactionRolesCmd({ const buttons: MessageButton[] = []; const toInsert: Array<{ customId; buttonGroup; buttonName }> = []; for (const [buttonName, button] of Object.entries(group.default_buttons)) { - const customId = createHash("md5") - .update(`${buttonName}${moment.utc().valueOf()}`) - .digest("hex"); + const customId = createHash("md5").update(`${buttonName}${moment.utc().valueOf()}`).digest("hex"); const btn = new MessageButton() .setLabel(button.label ?? "") diff --git a/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts b/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts index 1d7a116f..324405b3 100644 --- a/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts +++ b/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts @@ -29,7 +29,7 @@ export const AddReactionRoleEvt = reactionRolesEvt({ if (emoji.name === CLEAR_ROLES_EMOJI) { // User reacted with "clear roles" emoji -> clear their roles - const reactionRoleRoleIds = reactionRoles.map(rr => rr.role_id); + const reactionRoleRoleIds = reactionRoles.map((rr) => rr.role_id); for (const roleId of reactionRoleRoleIds) { addMemberPendingRoleChange(pluginData, userId, "-", roleId); } diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 0c6334e9..a2641bfe 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -65,7 +65,7 @@ export async function applyReactionRoleReactionsToMessage( await sleep(1500); // Add reaction role reactions - const emojisToAdd = reactionRoles.map(rr => rr.emoji); + const emojisToAdd = reactionRoles.map((rr) => rr.emoji); emojisToAdd.push(CLEAR_ROLES_EMOJI); for (const rawEmoji of emojisToAdd) { diff --git a/backend/src/plugins/ReactionRoles/util/runAutoRefresh.ts b/backend/src/plugins/ReactionRoles/util/runAutoRefresh.ts index c365c18f..c1d2b42a 100644 --- a/backend/src/plugins/ReactionRoles/util/runAutoRefresh.ts +++ b/backend/src/plugins/ReactionRoles/util/runAutoRefresh.ts @@ -5,7 +5,7 @@ import { refreshReactionRoles } from "./refreshReactionRoles"; export async function runAutoRefresh(pluginData: GuildPluginData) { // Refresh reaction roles on all reaction role messages const reactionRoles = await pluginData.state.reactionRoles.all(); - const idPairs = new Set(reactionRoles.map(r => `${r.channel_id}-${r.message_id}`)); + const idPairs = new Set(reactionRoles.map((r) => `${r.channel_id}-${r.message_id}`)); for (const pair of idPairs) { const [channelId, messageId] = pair.split("-"); await refreshReactionRoles(pluginData, channelId, messageId); diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index 7cce547f..24c765a7 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -53,10 +53,7 @@ export const RemindCmd = remindersCmd({ await pluginData.state.reminders.add( msg.author.id, msg.channel.id, - reminderTime - .clone() - .tz("Etc/UTC") - .format("YYYY-MM-DD HH:mm:ss"), + reminderTime.clone().tz("Etc/UTC").format("YYYY-MM-DD HH:mm:ss"), reminderBody, moment.utc().format("YYYY-MM-DD HH:mm:ss"), ); diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index 998518da..0b09535c 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -60,7 +60,7 @@ export const MassAddRoleCmd = rolesCmd({ return; } - const membersWithoutTheRole = members.filter(m => !m.roles.cache.has(roleId)); + const membersWithoutTheRole = members.filter((m) => !m.roles.cache.has(roleId)); let assigned = 0; const failed: string[] = []; const alreadyHadRole = members.length - membersWithoutTheRole.length; diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index 1d4c7b0e..e5cedc42 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -60,7 +60,7 @@ export const MassRemoveRoleCmd = rolesCmd({ return; } - const membersWithTheRole = members.filter(m => m.roles.cache.has(roleId)); + const membersWithTheRole = members.filter((m) => m.roles.cache.has(roleId)); let assigned = 0; const failed: string[] = []; const didNotHaveRole = members.length - membersWithTheRole.length; diff --git a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts index 981776fc..82396cb9 100644 --- a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts +++ b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts @@ -70,7 +70,7 @@ export const SelfGrantableRolesPlugin = zeppelinGuildPlugin { + configPreprocessor: (options) => { const config = options.config; for (const [key, entry] of Object.entries(config.entries)) { // Apply default entry config @@ -79,7 +79,7 @@ export const SelfGrantableRolesPlugin = zeppelinGuildPlugin a.toLowerCase()); + entry.roles[roleId] = aliases.map((a) => a.toLowerCase()); } } } diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts index 8fa9cca4..ce8a3a73 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts @@ -31,7 +31,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ const hasUnknownRoles = matchedRoleIds.length !== roleNames.length; const rolesToAdd: Map = Array.from(matchedRoleIds.values()) - .map(id => pluginData.guild.roles.cache.get(id as Snowflake)!) + .map((id) => pluginData.guild.roles.cache.get(id as Snowflake)!) .filter(Boolean) .reduce((map, role) => { map.set(role.id, role); @@ -94,7 +94,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ } const mentionRoles = pluginData.config.get().mention_roles; - const addedRolesStr = Array.from(rolesToAdd.values()).map(r => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)); + const addedRolesStr = Array.from(rolesToAdd.values()).map((r) => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)); const addedRolesWord = rolesToAdd.size === 1 ? "role" : "roles"; const messageParts: string[] = []; @@ -104,11 +104,11 @@ export const RoleAddCmd = selfGrantableRolesCmd({ const skippedRolesStr = skipped.size ? "skipped " + Array.from(skipped.values()) - .map(r => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)) + .map((r) => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)) .join(",") : null; const removedRolesStr = removed.size - ? "removed " + Array.from(removed.values()).map(r => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)) + ? "removed " + Array.from(removed.values()).map((r) => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`)) : null; const skippedRemovedStr = [skippedRolesStr, removedRolesStr].filter(Boolean).join(" and "); diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index 7a4bb83b..007aa459 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -29,20 +29,20 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ const matchedRoleIds = findMatchingRoles(roleNames, applyingEntries); const rolesToRemove = Array.from(matchedRoleIds.values()).map( - id => pluginData.guild.roles.cache.get(id as Snowflake)!, + (id) => pluginData.guild.roles.cache.get(id as Snowflake)!, ); - const roleIdsToRemove = rolesToRemove.map(r => r.id); + const roleIdsToRemove = rolesToRemove.map((r) => r.id); // Remove the roles if (rolesToRemove.length) { - const newRoleIds = msg.member.roles.cache.filter(role => !roleIdsToRemove.includes(role.id)); + const newRoleIds = msg.member.roles.cache.filter((role) => !roleIdsToRemove.includes(role.id)); try { await msg.member.edit({ roles: newRoleIds, }); - const removedRolesStr = rolesToRemove.map(r => `**${r.name}**`); + const removedRolesStr = rolesToRemove.map((r) => `**${r.name}**`); const removedRolesWord = rolesToRemove.length === 1 ? "role" : "roles"; if (rolesToRemove.length !== roleNames.length) { diff --git a/backend/src/plugins/SelfGrantableRoles/util/findMatchingRoles.ts b/backend/src/plugins/SelfGrantableRoles/util/findMatchingRoles.ts index 69daae02..42a6dd34 100644 --- a/backend/src/plugins/SelfGrantableRoles/util/findMatchingRoles.ts +++ b/backend/src/plugins/SelfGrantableRoles/util/findMatchingRoles.ts @@ -11,5 +11,5 @@ export function findMatchingRoles(roleNames: string[], entries: TSelfGrantableRo return map; }, new Map()); - return roleNames.map(roleName => aliasToRoleId.get(roleName)).filter(Boolean); + return roleNames.map((roleName) => aliasToRoleId.get(roleName)).filter(Boolean); } diff --git a/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts b/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts index 65495be4..25482d1c 100644 --- a/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts +++ b/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts @@ -11,5 +11,5 @@ export async function getApplyingEntries( ([k, e]) => e.can_use && !(!e.can_ignore_cooldown && pluginData.state.cooldowns.isOnCooldown(`${k}:${msg.author.id}`)), ) - .map(pair => pair[1]); + .map((pair) => pair[1]); } diff --git a/backend/src/plugins/SelfGrantableRoles/util/normalizeRoleNames.ts b/backend/src/plugins/SelfGrantableRoles/util/normalizeRoleNames.ts index 7f19a7bb..d0c6d6d2 100644 --- a/backend/src/plugins/SelfGrantableRoles/util/normalizeRoleNames.ts +++ b/backend/src/plugins/SelfGrantableRoles/util/normalizeRoleNames.ts @@ -1,3 +1,3 @@ export function normalizeRoleNames(roleNames: string[]) { - return roleNames.map(v => v.toLowerCase()); + return roleNames.map((v) => v.toLowerCase()); } diff --git a/backend/src/plugins/SelfGrantableRoles/util/splitRoleNames.ts b/backend/src/plugins/SelfGrantableRoles/util/splitRoleNames.ts index efd460d3..3baefee5 100644 --- a/backend/src/plugins/SelfGrantableRoles/util/splitRoleNames.ts +++ b/backend/src/plugins/SelfGrantableRoles/util/splitRoleNames.ts @@ -1,6 +1,6 @@ export function splitRoleNames(roleNames: string[]) { return roleNames - .map(v => v.split(/[\s,]+/)) + .map((v) => v.split(/[\s,]+/)) .flat() .filter(Boolean); } diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index 44fa92d8..8bdc76e2 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -69,7 +69,7 @@ export const SlowmodePlugin = zeppelinGuildPlugin()({ state.serverLogs = new GuildLogs(pluginData.guild.id); state.clearInterval = setInterval(() => clearExpiredSlowmodes(pluginData), BOT_SLOWMODE_CLEAR_INTERVAL); - state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); + state.onMessageCreateFn = (msg) => onMessageCreate(pluginData, msg); state.savedMessages.events.on("create", state.onMessageCreateFn); }, diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts index 0ef7733c..58e2bffd 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts @@ -30,7 +30,7 @@ export const SlowmodeListCmd = slowmodeCmd({ } if (slowmodes.length) { - const lines = slowmodes.map(slowmode => { + const lines = slowmodes.map((slowmode) => { const humanized = humanizeDuration(slowmode.seconds * 1000); const type = slowmode.native ? "native slowmode" : "bot slowmode"; diff --git a/backend/src/plugins/Spam/SpamPlugin.ts b/backend/src/plugins/Spam/SpamPlugin.ts index 28cae2be..84182993 100644 --- a/backend/src/plugins/Spam/SpamPlugin.ts +++ b/backend/src/plugins/Spam/SpamPlugin.ts @@ -82,7 +82,7 @@ export const SpamPlugin = zeppelinGuildPlugin()({ const { state } = pluginData; state.expiryInterval = setInterval(() => clearOldRecentActions(pluginData), 1000 * 60); - state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); + state.onMessageCreateFn = (msg) => onMessageCreate(pluginData, msg); state.savedMessages.events.on("create", state.onMessageCreateFn); }, diff --git a/backend/src/plugins/Spam/util/clearOldRecentActions.ts b/backend/src/plugins/Spam/util/clearOldRecentActions.ts index 4f42211c..1c0857b3 100644 --- a/backend/src/plugins/Spam/util/clearOldRecentActions.ts +++ b/backend/src/plugins/Spam/util/clearOldRecentActions.ts @@ -6,5 +6,7 @@ const MAX_INTERVAL = 300; export function clearOldRecentActions(pluginData: GuildPluginData) { // TODO: Figure out expiry time from longest interval in the config? const expiryTimestamp = Date.now() - 1000 * MAX_INTERVAL; - pluginData.state.recentActions = pluginData.state.recentActions.filter(action => action.timestamp >= expiryTimestamp); + pluginData.state.recentActions = pluginData.state.recentActions.filter( + (action) => action.timestamp >= expiryTimestamp, + ); } diff --git a/backend/src/plugins/Spam/util/clearRecentUserActions.ts b/backend/src/plugins/Spam/util/clearRecentUserActions.ts index cfaf7b53..f4fab914 100644 --- a/backend/src/plugins/Spam/util/clearRecentUserActions.ts +++ b/backend/src/plugins/Spam/util/clearRecentUserActions.ts @@ -7,7 +7,7 @@ export function clearRecentUserActions( userId: string, actionGroupId: string, ) { - pluginData.state.recentActions = pluginData.state.recentActions.filter(action => { + pluginData.state.recentActions = pluginData.state.recentActions.filter((action) => { return action.type !== type || action.userId !== userId || action.actionGroupId !== actionGroupId; }); } diff --git a/backend/src/plugins/Spam/util/getRecentActions.ts b/backend/src/plugins/Spam/util/getRecentActions.ts index ff3f31ac..de652a28 100644 --- a/backend/src/plugins/Spam/util/getRecentActions.ts +++ b/backend/src/plugins/Spam/util/getRecentActions.ts @@ -8,7 +8,7 @@ export function getRecentActions( actionGroupId: string, since: number, ) { - return pluginData.state.recentActions.filter(action => { + return pluginData.state.recentActions.filter((action) => { if (action.timestamp < since) return false; if (action.type !== type) return false; if (action.actionGroupId !== actionGroupId) return false; diff --git a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts index 17d9c748..ab95f5b3 100644 --- a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts @@ -103,8 +103,8 @@ export async function logAndDetectMessageSpam( // Get the offending message IDs // We also get the IDs of any messages after the last offending message, to account for lag before detection - const savedMessages = recentActions.map(a => a.extraData as SavedMessage); - const msgIds = savedMessages.map(m => m.id); + const savedMessages = recentActions.map((a) => a.extraData as SavedMessage); + const msgIds = savedMessages.map((m) => m.id); const lastDetectedMsgId = msgIds[msgIds.length - 1]; const additionalMessages = await pluginData.state.savedMessages.getUserMessagesByChannelAfterId( @@ -112,11 +112,11 @@ export async function logAndDetectMessageSpam( savedMessage.channel_id, lastDetectedMsgId, ); - additionalMessages.forEach(m => msgIds.push(m.id)); + additionalMessages.forEach((m) => msgIds.push(m.id)); // Then, if enabled, remove the spam messages if (spamConfig.clean !== false) { - msgIds.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); + msgIds.forEach((id) => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); (pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake)! as TextChannel | undefined) ?.bulkDelete(msgIds as Snowflake[]) .catch(noop); @@ -126,7 +126,7 @@ export async function logAndDetectMessageSpam( const uniqueMessages = Array.from(new Set([...savedMessages, ...additionalMessages])); uniqueMessages.sort((a, b) => (a.id > b.id ? 1 : -1)); const lastHandledMsgId = uniqueMessages - .map(m => m.id) + .map((m) => m.id) .reduce((last, id): string => { return id > last ? id : last; }); @@ -188,7 +188,7 @@ export async function logAndDetectMessageSpam( }); } }, - err => { + (err) => { logger.error(`Error while detecting spam:\n${err}`); }, ); diff --git a/backend/src/plugins/Starboard/StarboardPlugin.ts b/backend/src/plugins/Starboard/StarboardPlugin.ts index a7ab3ff2..d6a60a11 100644 --- a/backend/src/plugins/Starboard/StarboardPlugin.ts +++ b/backend/src/plugins/Starboard/StarboardPlugin.ts @@ -157,7 +157,7 @@ export const StarboardPlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state } = pluginData; - state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg); + state.onMessageDeleteFn = (msg) => onMessageDelete(pluginData, msg); state.savedMessages.events.on("delete", state.onMessageDeleteFn); }, diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index 992713bf..45b7e731 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -37,11 +37,11 @@ export const StarboardReactionAddEvt = starboardEvt({ const boardLock = await pluginData.locks.acquire(allStarboardsLock()); const applicableStarboards = Object.values(config.boards) - .filter(board => board.enabled) + .filter((board) => board.enabled) // Can't star messages in the starboard channel itself - .filter(board => board.channel_id !== msg.channel.id) + .filter((board) => board.channel_id !== msg.channel.id) // Matching emoji - .filter(board => { + .filter((board) => { return board.star_emoji!.some((boardEmoji: string) => { if (emoji.id) { // Custom emoji diff --git a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts index dc6e8781..053389db 100644 --- a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts +++ b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts @@ -59,10 +59,7 @@ export function createStarboardEmbedFromMessage( // If there are no embeds, add the first image attachment explicitly else if (msg.attachments.size) { for (const attachment of msg.attachments) { - const ext = path - .extname(attachment[1].name!) - .slice(1) - .toLowerCase(); + const ext = path.extname(attachment[1].name!).slice(1).toLowerCase(); if (imageAttachmentExtensions.includes(ext)) { embed.image = { url: attachment[1].url }; diff --git a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts index f4635371..6535852f 100644 --- a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts +++ b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts @@ -11,7 +11,7 @@ export async function removeMessageFromStarboard( await pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(msg.message_id).catch(noop); // this code is now Almeida-certified and no longer ugly :ok_hand: :cake: - const channel = pluginData.client.channels.cache.find(c => c.id === msg.starboard_channel_id); + const channel = pluginData.client.channels.cache.find((c) => c.id === msg.starboard_channel_id); if (!channel?.isText()) return; const message = await channel.messages.fetch(msg.starboard_message_id).catch(noop); if (!message?.deletable) return; diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 0b5763ff..1d1c7c0c 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -114,10 +114,10 @@ export const TagsPlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state, guild } = pluginData; - state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); + state.onMessageCreateFn = (msg) => onMessageCreate(pluginData, msg); state.savedMessages.events.on("create", state.onMessageCreateFn); - state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg); + state.onMessageDeleteFn = (msg) => onMessageDelete(pluginData, msg); state.savedMessages.events.on("delete", state.onMessageDeleteFn); const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); @@ -167,10 +167,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ } const delayMS = convertDelayStringToMS(delay) ?? 0; - return moment - .utc(reference, "x") - .add(delayMS) - .valueOf(); + return moment.utc(reference, "x").add(delayMS).valueOf(); }, timeSub(...args) { @@ -189,10 +186,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ } const delayMS = convertDelayStringToMS(delay) ?? 0; - return moment - .utc(reference, "x") - .subtract(delayMS) - .valueOf(); + return moment.utc(reference, "x").subtract(delayMS).valueOf(); }, timeAgo(delay) { @@ -210,7 +204,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ return timeAndDate.inGuildTz(parsed).format("YYYY-MM-DD"); }, - mention: input => { + mention: (input) => { if (typeof input !== "string") { return ""; } @@ -233,7 +227,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ return ""; }, - isMention: input => { + isMention: (input) => { if (typeof input !== "string") { return false; } diff --git a/backend/src/plugins/Tags/commands/TagListCmd.ts b/backend/src/plugins/Tags/commands/TagListCmd.ts index 41febd5d..c50baaa5 100644 --- a/backend/src/plugins/Tags/commands/TagListCmd.ts +++ b/backend/src/plugins/Tags/commands/TagListCmd.ts @@ -13,7 +13,7 @@ export const TagListCmd = tagsCmd({ } const prefix = (await pluginData.config.getForMessage(msg)).prefix; - const tagNames = tags.map(tag => tag.tag).sort(); + const tagNames = tags.map((tag) => tag.tag).sort(); createChunkedMessage(msg.channel, `Available tags (use with ${prefix}tag): \`\`\`${tagNames.join(", ")}\`\`\``); }, diff --git a/backend/src/plugins/Tags/util/onMessageCreate.ts b/backend/src/plugins/Tags/util/onMessageCreate.ts index 46dcf570..f64906e8 100644 --- a/backend/src/plugins/Tags/util/onMessageCreate.ts +++ b/backend/src/plugins/Tags/util/onMessageCreate.ts @@ -79,7 +79,7 @@ export async function onMessageCreate(pluginData: GuildPluginData pluginData.cooldowns.isOnCooldown(cd[0])); + const isOnCooldown = cooldowns.some((cd) => pluginData.cooldowns.isOnCooldown(cd[0])); if (isOnCooldown) return; for (const cd of cooldowns) { diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index de175d73..5174e7b1 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -65,6 +65,6 @@ export async function renderTagBody( return { content: await renderTemplate(body, data) }; } else { // Embed - return renderRecursively(body, str => renderTemplate(str, data)); + return renderRecursively(body, (str) => renderTemplate(str, data)); } } diff --git a/backend/src/plugins/Tags/util/renderTagFromString.ts b/backend/src/plugins/Tags/util/renderTagFromString.ts index 4a22310c..b482741d 100644 --- a/backend/src/plugins/Tags/util/renderTagFromString.ts +++ b/backend/src/plugins/Tags/util/renderTagFromString.ts @@ -17,7 +17,7 @@ export async function renderTagFromString( member: GuildMember, ): Promise { const variableStr = str.slice(prefix.length + tagName.length).trim(); - const tagArgs = parseArguments(variableStr).map(v => v.value); + const tagArgs = parseArguments(variableStr).map((v) => v.value); // Format the string try { diff --git a/backend/src/plugins/Utility/commands/AboutCmd.ts b/backend/src/plugins/Utility/commands/AboutCmd.ts index 219afc15..369c1201 100644 --- a/backend/src/plugins/Utility/commands/AboutCmd.ts +++ b/backend/src/plugins/Utility/commands/AboutCmd.ts @@ -55,10 +55,7 @@ export const AboutCmd = utilityCmd({ ]; const loadedPlugins = Array.from( - pluginData - .getKnubInstance() - .getLoadedGuild(pluginData.guild.id)! - .loadedPlugins.keys(), + pluginData.getKnubInstance().getLoadedGuild(pluginData.guild.id)!.loadedPlugins.keys(), ); loadedPlugins.sort(); @@ -100,9 +97,9 @@ export const AboutCmd = utilityCmd({ // For the embed color, find the highest colored role the bot has - this is their color on the server as well const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); - let botRoles = botMember?.roles.cache.map(r => (msg.channel as GuildChannel).guild.roles.cache.get(r.id)!) || []; - botRoles = botRoles.filter(r => !!r); // Drop any unknown roles - botRoles = botRoles.filter(r => r.color); // Filter to those with a color + let botRoles = botMember?.roles.cache.map((r) => (msg.channel as GuildChannel).guild.roles.cache.get(r.id)!) || []; + botRoles = botRoles.filter((r) => !!r); // Drop any unknown roles + botRoles = botRoles.filter((r) => r.color); // Filter to those with a color botRoles.sort(sorter("position", "DESC")); // Sort by position (highest first) if (botRoles.length) { aboutContent.embeds![0].color = botRoles[0].color; diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 7a852721..60e79f26 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -30,10 +30,10 @@ export async function cleanMessages( // Delete & archive in ID order savedMessages = Array.from(savedMessages).sort((a, b) => (a.id > b.id ? 1 : -1)); - const idsToDelete = savedMessages.map(m => m.id) as Snowflake[]; + const idsToDelete = savedMessages.map((m) => m.id) as Snowflake[]; // Make sure the deletions aren't double logged - idsToDelete.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); + idsToDelete.forEach((id) => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE_BULK, idsToDelete[0]); // Actually delete the messages @@ -171,12 +171,12 @@ export async function cleanCmd(pluginData: GuildPluginData, a let responseMsg: Message | undefined; if (messagesToClean.length > 0) { // Save to-be-deleted messages that were missing from the database - const existingStored = await pluginData.state.savedMessages.getMultiple(messagesToClean.map(m => m.id)); - const alreadyStored = existingStored.map(stored => stored.id); - const messagesToStore = messagesToClean.filter(potentialMsg => !alreadyStored.includes(potentialMsg.id)); + const existingStored = await pluginData.state.savedMessages.getMultiple(messagesToClean.map((m) => m.id)); + const alreadyStored = existingStored.map((stored) => stored.id); + const messagesToStore = messagesToClean.filter((potentialMsg) => !alreadyStored.includes(potentialMsg.id)); await pluginData.state.savedMessages.createFromMessages(messagesToStore); - const savedMessagesToClean = await pluginData.state.savedMessages.getMultiple(messagesToClean.map(m => m.id)); + const savedMessagesToClean = await pluginData.state.savedMessages.getMultiple(messagesToClean.map((m) => m.id)); const cleanResult = await cleanMessages(pluginData, targetChannel, savedMessagesToClean, msg.author); let responseText = `Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`; diff --git a/backend/src/plugins/Utility/commands/HelpCmd.ts b/backend/src/plugins/Utility/commands/HelpCmd.ts index 5966cbc9..b03d3aa4 100644 --- a/backend/src/plugins/Utility/commands/HelpCmd.ts +++ b/backend/src/plugins/Utility/commands/HelpCmd.ts @@ -58,10 +58,7 @@ export const HelpCmd = utilityCmd({ const description = command.config!.extra!.blueprint.description; const usage = command.config!.extra!.blueprint.usage; - const commandSlug = trigger - .trim() - .toLowerCase() - .replace(/\s/g, "-"); + const commandSlug = trigger.trim().toLowerCase().replace(/\s/g, "-"); let snippet = `**${prefix}${trigger}**`; if (description) snippet += `\n${description}`; diff --git a/backend/src/plugins/Utility/commands/RolesCmd.ts b/backend/src/plugins/Utility/commands/RolesCmd.ts index 2aa3f459..6734d167 100644 --- a/backend/src/plugins/Utility/commands/RolesCmd.ts +++ b/backend/src/plugins/Utility/commands/RolesCmd.ts @@ -28,7 +28,7 @@ export const RolesCmd = utilityCmd({ if (args.search) { const searchStr = args.search.toLowerCase(); - roles = roles.filter(r => r.name.toLowerCase().includes(searchStr) || r.id === searchStr); + roles = roles.filter((r) => r.name.toLowerCase().includes(searchStr) || r.id === searchStr); } if (args.counts) { @@ -75,7 +75,7 @@ export const RolesCmd = utilityCmd({ } else if (sort === "memberCount" && args.counts) { roles.sort(sorter("_memberCount", sortDir)); } else if (sort === "name") { - roles.sort(sorter(r => r.name.toLowerCase(), sortDir)); + roles.sort(sorter((r) => r.name.toLowerCase(), sortDir)); } else { sendErrorMessage(pluginData, msg.channel, "Unknown sorting method"); return; @@ -85,7 +85,7 @@ export const RolesCmd = utilityCmd({ const chunks = chunkArray(roles, 20); for (const [i, chunk] of chunks.entries()) { - const roleLines = chunk.map(role => { + const roleLines = chunk.map((role) => { const paddedId = role.id.padEnd(longestId, " "); let line = `${paddedId} ${role.name}`; if (role._memberCount != null) { diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index 0f852e14..935fa0b1 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -50,7 +50,7 @@ export const VcmoveCmd = utilityCmd({ const voiceChannels = [...pluginData.guild.channels.cache.values()].filter( (c): c is VoiceChannel => c.type === ChannelTypeStrings.VOICE, ); - const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, ch => ch.name); + const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); return; @@ -129,7 +129,7 @@ export const VcmoveAllCmd = utilityCmd({ const voiceChannels = [...pluginData.guild.channels.cache.values()].filter( (c): c is VoiceChannel => c.type === ChannelTypeStrings.VOICE, ); - const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, ch => ch.name); + const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); return; diff --git a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts index 5052ac11..9d050b3f 100644 --- a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts @@ -96,8 +96,8 @@ export async function getChannelInfoEmbed( if (channel.type === ChannelTypeStrings.VOICE || channel.type === ChannelTypeStrings.STAGE) { const voiceMembers = Array.from((channel as VoiceChannel | StageChannel).members.values()); - const muted = voiceMembers.filter(vm => vm.voice.mute || vm.voice.selfMute); - const deafened = voiceMembers.filter(vm => vm.voice.deaf || vm.voice.selfDeaf); + const muted = voiceMembers.filter((vm) => vm.voice.mute || vm.voice.selfMute); + const deafened = voiceMembers.filter((vm) => vm.voice.deaf || vm.voice.selfDeaf); const voiceOrStage = channel.type === ChannelTypeStrings.VOICE ? "Voice" : "Stage"; embed.fields.push({ @@ -112,10 +112,10 @@ export async function getChannelInfoEmbed( if (channel.type === ChannelTypeStrings.CATEGORY) { const textChannels = pluginData.guild.channels.cache.filter( - ch => ch.parentId === channel.id && ch.type !== ChannelTypeStrings.VOICE, + (ch) => ch.parentId === channel.id && ch.type !== ChannelTypeStrings.VOICE, ); const voiceChannels = pluginData.guild.channels.cache.filter( - ch => + (ch) => ch.parentId === channel.id && (ch.type === ChannelTypeStrings.VOICE || ch.type === ChannelTypeStrings.STAGE), ); diff --git a/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts b/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts index 02228924..a97d5310 100644 --- a/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts @@ -7,7 +7,7 @@ export async function getEmojiInfoEmbed( pluginData: GuildPluginData, emojiId: string, ): Promise { - const emoji = pluginData.guild.emojis.cache.find(e => e.id === emojiId); + const emoji = pluginData.guild.emojis.cache.find((e) => e.id === emojiId); if (!emoji) { return null; } diff --git a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts index 4a7215ed..a0a987e7 100644 --- a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts @@ -120,9 +120,7 @@ export async function getInviteInfoEmbed( /*if (invite.channel.icon) { embed.author.icon_url = `https://cdn.discordapp.com/channel-icons/${invite.channel.id}/${invite.channel.icon}.png?size=256`; - }*/ const channelCreatedAtTimestamp = snowflakeToTimestamp( - invite.channel.id, - ); + }*/ const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id); const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x"); const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, { largest: 2, diff --git a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts index 4f16c31a..36a664e3 100644 --- a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts @@ -35,13 +35,13 @@ export async function getRoleInfoEmbed( round: true, }); - const rolePerms = Object.keys(role.permissions.toJSON()).map(p => + const rolePerms = Object.keys(role.permissions.toJSON()).map((p) => p // Voice channel related permission names start with 'voice' .replace(/^voice/i, "") .replace(/([a-z])([A-Z])/g, "$1 $2") .toLowerCase() - .replace(/(^\w{1})|(\s{1}\w{1})/g, l => l.toUpperCase()), + .replace(/(^\w{1})|(\s{1}\w{1})/g, (l) => l.toUpperCase()), ); // -1 because of the @everyone role @@ -54,10 +54,7 @@ export async function getRoleInfoEmbed( ID: \`${role.id}\` Created: **${roleAge} ago** (\`${prettyCreatedAt}\`) Position: **${role.position} / ${totalGuildRoles}** - Color: **#${role.color - .toString(16) - .toUpperCase() - .padStart(6, "0")}** + Color: **#${role.color.toString(16).toUpperCase().padStart(6, "0")}** Mentionable: **${role.mentionable ? "Yes" : "No"}** Hoisted: **${role.hoist ? "Yes" : "No"}** Permissions: \`${rolePerms.length ? rolePerms.join(", ") : "None"}\` diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index 91728e04..03d7212d 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -134,7 +134,7 @@ export async function getServerInfoEmbed( } if (!onlineMemberCount && thisServer) { - onlineMemberCount = thisServer.members.cache.filter(m => m.presence?.status !== "offline").size; // Extremely inaccurate fallback + onlineMemberCount = thisServer.members.cache.filter((m) => m.presence?.status !== "offline").size; // Extremely inaccurate fallback } const offlineMemberCount = totalMembers - onlineMemberCount; @@ -162,9 +162,9 @@ export async function getServerInfoEmbed( // CHANNEL COUNTS if (thisServer) { const totalChannels = thisServer.channels.cache.size; - const categories = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.CATEGORY); - const textChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.TEXT); - const voiceChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.VOICE); + const categories = thisServer.channels.cache.filter((channel) => channel.type === ChannelTypeStrings.CATEGORY); + const textChannels = thisServer.channels.cache.filter((channel) => channel.type === ChannelTypeStrings.TEXT); + const voiceChannels = thisServer.channels.cache.filter((channel) => channel.type === ChannelTypeStrings.VOICE); embed.fields.push({ name: preEmbedPadding + "Channels", diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index d4e74637..39cf4166 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -102,14 +102,14 @@ export async function getUserInfoEmbed( largest: 2, round: true, }); - const roles = Array.from(member.roles.cache.values()).filter(r => r.id !== pluginData.guild.id); + const roles = Array.from(member.roles.cache.values()).filter((r) => r.id !== pluginData.guild.id); roles.sort(sorter("position", "DESC")); embed.fields.push({ name: preEmbedPadding + "Member information", value: trimLines(` ${user.bot ? "Added" : "Joined"}: **${joinAge} ago** (\`${prettyJoinedAt}\`) - ${roles.length > 0 ? "Roles: " + roles.map(r => `<@&${r.id}>`).join(", ") : ""} + ${roles.length > 0 ? "Roles: " + roles.map((r) => `<@&${r.id}>`).join(", ") : ""} `), }); @@ -130,14 +130,14 @@ export async function getUserInfoEmbed( value: `⚠ ${user.bot ? "Bot" : "User"} is not on the server`, }); } - const cases = (await pluginData.state.cases.getByUserId(user.id)).filter(c => !c.is_hidden); + const cases = (await pluginData.state.cases.getByUserId(user.id)).filter((c) => !c.is_hidden); if (cases.length > 0) { cases.sort((a, b) => { return a.created_at < b.created_at ? 1 : -1; }); - const caseSummary = cases.slice(0, 3).map(c => { + const caseSummary = cases.slice(0, 3).map((c) => { const summaryText = `${CaseTypes[c.type]} (#${c.case_number})`; if (c.log_message_id) { diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index 4717dd31..b2b417b7 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -90,7 +90,7 @@ export async function displaySearch( const perPage = args.ids ? SEARCH_ID_RESULTS_PER_PAGE : SEARCH_RESULTS_PER_PAGE; - const loadSearchPage = async page => { + const loadSearchPage = async (page) => { if (searching) return; searching = true; @@ -101,7 +101,7 @@ export async function displaySearch( searchMsgPromise = originalSearchMsg.edit("Searching..."); } else { searchMsgPromise = msg.channel.send("Searching..."); - searchMsgPromise.then(m => (originalSearchMsg = m)); + searchMsgPromise.then((m) => (originalSearchMsg = m)); } let searchResult; @@ -182,10 +182,7 @@ export async function displaySearch( .setEmoji("➡") .setCustomId(`nextButton:${idMod}`) .setDisabled(currentPage === searchResult.lastPage), - new MessageButton() - .setStyle("SECONDARY") - .setEmoji("🔄") - .setCustomId(`reloadButton:${idMod}`), + new MessageButton().setStyle("SECONDARY").setEmoji("🔄").setCustomId(`reloadButton:${idMod}`), ); const row = new MessageActionRow().addComponents(buttons); @@ -308,7 +305,7 @@ async function performMemberSearch( if (args.role) { const roleIds = args.role.split(","); - matchingMembers = matchingMembers.filter(member => { + matchingMembers = matchingMembers.filter((member) => { for (const role of roleIds) { if (!member.roles.cache.has(role as Snowflake)) return false; } @@ -318,11 +315,11 @@ async function performMemberSearch( } if (args.voice) { - matchingMembers = matchingMembers.filter(m => m.voice.channelId); + matchingMembers = matchingMembers.filter((m) => m.voice.channelId); } if (args.bot) { - matchingMembers = matchingMembers.filter(m => m.user.bot); + matchingMembers = matchingMembers.filter((m) => m.user.bot); } if (args.query) { @@ -379,7 +376,7 @@ async function performMemberSearch( }); } else { */ - matchingMembers = await asyncFilter(matchingMembers, async member => { + matchingMembers = await asyncFilter(matchingMembers, async (member) => { if (member.nickname && (await execRegExp(queryRegex, member.nickname).catch(allowTimeout))) { return true; } @@ -396,12 +393,12 @@ async function performMemberSearch( const realSortDir = sortDir === "-" ? "DESC" : "ASC"; if (sortBy === "id") { - matchingMembers.sort(sorter(m => BigInt(m.id), realSortDir)); + matchingMembers.sort(sorter((m) => BigInt(m.id), realSortDir)); } else { matchingMembers.sort( multiSorter([ - [m => m.user.username.toLowerCase(), realSortDir], - [m => m.discriminator, realSortDir], + [(m) => m.user.username.toLowerCase(), realSortDir], + [(m) => m.discriminator, realSortDir], ]), ); } @@ -435,7 +432,7 @@ async function performBanSearch( throw new SearchError(`Unable to search bans: missing "Ban Members" permission`); } - let matchingBans = (await pluginData.guild.bans.fetch({ cache: false })).map(x => x.user); + let matchingBans = (await pluginData.guild.bans.fetch({ cache: false })).map((x) => x.user); if (args.query) { let isSafeRegex = true; @@ -450,7 +447,7 @@ async function performBanSearch( } const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex); - matchingBans = await asyncFilter(matchingBans, async user => { + matchingBans = await asyncFilter(matchingBans, async (user) => { const fullUsername = user.tag; if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; return false; @@ -461,12 +458,12 @@ async function performBanSearch( const realSortDir = sortDir === "-" ? "DESC" : "ASC"; if (sortBy === "id") { - matchingBans.sort(sorter(m => BigInt(m.id), realSortDir)); + matchingBans.sort(sorter((m) => BigInt(m.id), realSortDir)); } else { matchingBans.sort( multiSorter([ - [m => m.username.toLowerCase(), realSortDir], - [m => m.discriminator, realSortDir], + [(m) => m.username.toLowerCase(), realSortDir], + [(m) => m.discriminator, realSortDir], ]), ); } @@ -491,7 +488,7 @@ async function performBanSearch( function formatSearchResultList(members: Array): string { const longestId = members.reduce((longest, member) => Math.max(longest, member.id.length), 0); - const lines = members.map(member => { + const lines = members.map((member) => { const paddedId = member.id.padEnd(longestId, " "); let line; if (member instanceof GuildMember) { @@ -506,5 +503,5 @@ function formatSearchResultList(members: Array): string { } function formatSearchResultIdList(members: Array): string { - return members.map(m => m.id).join(" "); + return members.map((m) => m.id).join(" "); } diff --git a/backend/src/plugins/ZeppelinPluginBlueprint.ts b/backend/src/plugins/ZeppelinPluginBlueprint.ts index 9bcd3417..0bc7dbce 100644 --- a/backend/src/plugins/ZeppelinPluginBlueprint.ts +++ b/backend/src/plugins/ZeppelinPluginBlueprint.ts @@ -41,7 +41,7 @@ export function zeppelinGuildPlugin(): < - TBlueprint extends ZeppelinGuildPluginBlueprint> + TBlueprint extends ZeppelinGuildPluginBlueprint>, >( blueprint: TBlueprint, ) => TBlueprint & { @@ -50,9 +50,9 @@ export function zeppelinGuildPlugin(): < export function zeppelinGuildPlugin(...args) { if (args.length) { - const blueprint = (typedGuildPlugin( + const blueprint = typedGuildPlugin( ...(args as Parameters), - ) as unknown) as ZeppelinGuildPluginBlueprint; + ) as unknown as ZeppelinGuildPluginBlueprint; blueprint.configPreprocessor = getPluginConfigPreprocessor(blueprint, blueprint.configPreprocessor); return blueprint; } else { @@ -75,7 +75,7 @@ export function zeppelinGlobalPlugin(): < - TBlueprint extends ZeppelinGlobalPluginBlueprint + TBlueprint extends ZeppelinGlobalPluginBlueprint, >( blueprint: TBlueprint, ) => TBlueprint & { @@ -84,9 +84,9 @@ export function zeppelinGlobalPlugin(): < export function zeppelinGlobalPlugin(...args) { if (args.length) { - const blueprint = (typedGlobalPlugin( + const blueprint = typedGlobalPlugin( ...(args as Parameters), - ) as unknown) as ZeppelinGlobalPluginBlueprint; + ) as unknown as ZeppelinGlobalPluginBlueprint; // @ts-ignore FIXME: Check the types here blueprint.configPreprocessor = getPluginConfigPreprocessor(blueprint, blueprint.configPreprocessor); return blueprint; diff --git a/backend/src/templateFormatter.test.ts b/backend/src/templateFormatter.test.ts index 39132731..24aaa881 100644 --- a/backend/src/templateFormatter.test.ts +++ b/backend/src/templateFormatter.test.ts @@ -1,12 +1,12 @@ import test from "ava"; import { parseTemplate, renderParsedTemplate, renderTemplate, TemplateSafeValueContainer } from "./templateFormatter"; -test("Parses plain string templates correctly", t => { +test("Parses plain string templates correctly", (t) => { const result = parseTemplate("foo bar baz"); t.deepEqual(result, ["foo bar baz"]); }); -test("Parses templates with variables correctly", t => { +test("Parses templates with variables correctly", (t) => { const result = parseTemplate("foo {bar} baz"); t.deepEqual(result, [ "foo ", @@ -18,7 +18,7 @@ test("Parses templates with variables correctly", t => { ]); }); -test("Parses templates with function variables correctly", t => { +test("Parses templates with function variables correctly", (t) => { const result = parseTemplate('foo {bar("str", 5.07)} baz'); t.deepEqual(result, [ "foo ", @@ -30,7 +30,7 @@ test("Parses templates with function variables correctly", t => { ]); }); -test("Parses function variables with variable arguments correctly", t => { +test("Parses function variables with variable arguments correctly", (t) => { const result = parseTemplate('foo {bar("str", 5.07, someVar)} baz'); t.deepEqual(result, [ "foo ", @@ -49,7 +49,7 @@ test("Parses function variables with variable arguments correctly", t => { ]); }); -test("Parses function variables with function variable arguments correctly", t => { +test("Parses function variables with function variable arguments correctly", (t) => { const result = parseTemplate('foo {bar("str", 5.07, deeply(nested(8)))} baz'); t.deepEqual(result, [ "foo ", @@ -73,7 +73,7 @@ test("Parses function variables with function variable arguments correctly", t = ]); }); -test("Renders a parsed template correctly", async t => { +test("Renders a parsed template correctly", async (t) => { const parseResult = parseTemplate('foo {bar("str", 5.07, deeply(nested(8)))} baz'); const values = new TemplateSafeValueContainer({ bar(strArg, numArg, varArg) { @@ -91,18 +91,18 @@ test("Renders a parsed template correctly", async t => { t.is(renderResult, "foo str 5.07 !! baz"); }); -test("Supports base values in renderTemplate", async t => { +test("Supports base values in renderTemplate", async (t) => { const result = await renderTemplate('{if("", "+", "-")} {if(1, "+", "-")}'); t.is(result, "- +"); }); -test("Edge case #1", async t => { +test("Edge case #1", async (t) => { const result = await renderTemplate("{foo} {bar()}"); // No "Unclosed function" exception = success t.pass(); }); -test("Parses empty string args as empty strings", async t => { +test("Parses empty string args as empty strings", async (t) => { const result = parseTemplate('{foo("")}'); t.deepEqual(result, [ { diff --git a/backend/src/templateFormatter.ts b/backend/src/templateFormatter.ts index 2ed902d9..0dcb7bde 100644 --- a/backend/src/templateFormatter.ts +++ b/backend/src/templateFormatter.ts @@ -51,7 +51,7 @@ function isTemplateSafeValue(value: unknown): value is TemplateSafeValue { typeof value === "number" || typeof value === "boolean" || typeof value === "function" || - (Array.isArray(value) && value.every(v => isTemplateSafeValue(v))) || + (Array.isArray(value) && value.every((v) => isTemplateSafeValue(v))) || value instanceof TemplateSafeValueContainer ); } @@ -89,7 +89,7 @@ export function createTypedTemplateSafeValueContainer { + arr.forEach((item) => { if (typeof item === "object") { delete item._state; delete item._parent; diff --git a/backend/src/utils.test.ts b/backend/src/utils.test.ts index abd89774..ecf773a3 100644 --- a/backend/src/utils.test.ts +++ b/backend/src/utils.test.ts @@ -5,51 +5,51 @@ import { ErisAllowedMentionFormat } from "./utils/erisAllowedMentionsToDjsMentio type AssertEquals = TActual extends TExpected ? true : false; -test("getUrlsInString(): detects full links", t => { +test("getUrlsInString(): detects full links", (t) => { const urls = getUrlsInString("foo https://google.com/ bar"); t.is(urls.length, 1); t.is(urls[0].hostname, "google.com"); }); -test("getUrlsInString(): detects partial links", t => { +test("getUrlsInString(): detects partial links", (t) => { const urls = getUrlsInString("foo google.com bar"); t.is(urls.length, 1); t.is(urls[0].hostname, "google.com"); }); -test("getUrlsInString(): detects subdomains", t => { +test("getUrlsInString(): detects subdomains", (t) => { const urls = getUrlsInString("foo photos.google.com bar"); t.is(urls.length, 1); t.is(urls[0].hostname, "photos.google.com"); }); -test("delay strings: basic support", t => { +test("delay strings: basic support", (t) => { const delayString = "2w4d7h32m17s"; const expected = 1_582_337_000; t.is(convertDelayStringToMS(delayString), expected); }); -test("delay strings: default unit (minutes)", t => { +test("delay strings: default unit (minutes)", (t) => { t.is(convertDelayStringToMS("10"), 10 * 60 * 1000); }); -test("delay strings: custom default unit", t => { +test("delay strings: custom default unit", (t) => { t.is(convertDelayStringToMS("10", "s"), 10 * 1000); }); -test("delay strings: reverse conversion", t => { +test("delay strings: reverse conversion", (t) => { const ms = 1_582_337_020; const expected = "2w4d7h32m17s20x"; t.is(convertMSToDelayString(ms), expected); }); -test("delay strings: reverse conversion (conservative)", t => { +test("delay strings: reverse conversion (conservative)", (t) => { const ms = 1_209_600_000; const expected = "2w"; t.is(convertMSToDelayString(ms), expected); }); -test("tAllowedMentions matches Eris's AllowedMentions", t => { +test("tAllowedMentions matches Eris's AllowedMentions", (t) => { type TAllowedMentions = ioTs.TypeOf; const typeTest: AssertEquals = true; t.pass(); diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 6e40d666..e5bde6fe 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -106,7 +106,7 @@ export const tNormalizedNullOrUndefined = new t.Type typeof v === "undefined", (v, c) => (v == null ? t.success(undefined) : t.failure(v, c, "Value must be null or undefined")), - s => undefined, + (s) => undefined, ); /** @@ -154,10 +154,10 @@ export function tDeepPartial(type: T): TDeepPartial { } else if (type instanceof t.DictionaryType) { return t.record(type.domain, tDeepPartial(type.codomain)) as TDeepPartial; } else if (type instanceof t.UnionType) { - return t.union(type.types.map(unionType => tDeepPartial(unionType))) as TDeepPartial; + return t.union(type.types.map((unionType) => tDeepPartial(unionType))) as TDeepPartial; } else if (type instanceof t.IntersectionType) { - const types = type.types.map(intersectionType => tDeepPartial(intersectionType)); - return (t.intersection(types as [t.Mixed, t.Mixed]) as unknown) as TDeepPartial; + const types = type.types.map((intersectionType) => tDeepPartial(intersectionType)); + return t.intersection(types as [t.Mixed, t.Mixed]) as unknown as TDeepPartial; } else if (type instanceof t.ArrayType) { return t.array(tDeepPartial(type.type)) as TDeepPartial; } else { @@ -433,7 +433,7 @@ export function validateAndParseMessageContent(input: unknown): StrictMessageCon dropNullValuesRecursively(input); try { - return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; + return zStrictMessageContent.parse(input) as unknown as StrictMessageContent; } catch (err) { if (err instanceof ZodError) { // TODO: Allow error to be thrown and handle at use location @@ -492,34 +492,34 @@ export const tAlphanumeric = new t.Type( "tAlphanumeric", (s): s is string => typeof s === "string", (from, to) => - either.chain(t.string.validate(from, to), s => { + either.chain(t.string.validate(from, to), (s) => { return s.match(/\W/) ? t.failure(from, to, "String must be alphanumeric") : t.success(s); }), - s => s, + (s) => s, ); export const tDateTime = new t.Type( "tDateTime", (s): s is string => typeof s === "string", (from, to) => - either.chain(t.string.validate(from, to), s => { + either.chain(t.string.validate(from, to), (s) => { const parsed = s.length === 10 ? moment.utc(s, "YYYY-MM-DD") : s.length === 19 ? moment.utc(s, "YYYY-MM-DD HH:mm:ss") : null; return parsed && parsed.isValid() ? t.success(s) : t.failure(from, to, "Invalid datetime"); }), - s => s, + (s) => s, ); export const tDelayString = new t.Type( "tDelayString", (s): s is string => typeof s === "string", (from, to) => - either.chain(t.string.validate(from, to), s => { + either.chain(t.string.validate(from, to), (s) => { const ms = convertDelayStringToMS(s); return ms === null ? t.failure(from, to, "Invalid delay string") : t.success(s); }), - s => s, + (s) => s, ); // To avoid running into issues with the JS max date vaLue, we cap maximum delay strings *far* below that. @@ -608,8 +608,8 @@ export function stripObjectToScalars(obj, includedNested: string[] = []) { } else if (typeof obj[key] === "object") { const prefix = `${key}.`; const nestedNested = includedNested - .filter(p => p === key || p.startsWith(prefix)) - .map(p => (p === key ? p : p.slice(prefix.length))); + .filter((p) => p === key || p.startsWith(prefix)) + .map((p) => (p === key ? p : p.slice(prefix.length))); if (nestedNested.length) { result[key] = stripObjectToScalars(obj[key], nestedNested); @@ -628,7 +628,7 @@ export function isSnowflake(v: string): boolean { } export function sleep(ms: number): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(resolve, ms); }); } @@ -676,7 +676,7 @@ export async function findRelevantAuditLogEntry( const cutoffTS = Date.now() - 1000 * 60 * 2; - const relevantEntry = entries.find(entry => { + const relevantEntry = entries.find((entry) => { return (entry.target as { id }).id === userId && entry.createdTimestamp >= cutoffTS; }); @@ -744,7 +744,8 @@ export function isNotNull(value): value is Exclude { // discord.gg/invite/ // discord.gg/ // discord.com/friend-invite/ -const quickInviteDetection = /discord(?:app)?\.com\/(?:friend-)?invite\/([a-z0-9\-]+)|discord\.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi; +const quickInviteDetection = + /discord(?:app)?\.com\/(?:friend-)?invite\/([a-z0-9\-]+)|discord\.gg\/(?:\S+\/)?([a-z0-9\-]+)/gi; const isInviteHostRegex = /(?:^|\.)(?:discord.gg|discord.com|discordapp.com)$/i; const longInvitePathRegex = /^\/(?:friend-)?invite\/([a-z0-9\-]+)$/i; @@ -758,19 +759,19 @@ export function getInviteCodesInString(str: string): string[] { // Quick detection const quickDetectionMatch = str.matchAll(quickInviteDetection); if (quickDetectionMatch) { - inviteCodes.push(...[...quickDetectionMatch].map(m => m[1] || m[2])); + inviteCodes.push(...[...quickDetectionMatch].map((m) => m[1] || m[2])); } // Deep detection via URL parsing const linksInString = getUrlsInString(str, true); - const potentialInviteLinks = linksInString.filter(url => isInviteHostRegex.test(url.hostname)); - const withNormalizedPaths = potentialInviteLinks.map(url => { + const potentialInviteLinks = linksInString.filter((url) => isInviteHostRegex.test(url.hostname)); + const withNormalizedPaths = potentialInviteLinks.map((url) => { url.pathname = url.pathname.replace(/\/{2,}/g, "/").replace(/\/+$/g, ""); return url; }); const codesFromInviteLinks = withNormalizedPaths - .map(url => { + .map((url) => { // discord.gg/[anything/] if (url.hostname === "discord.gg") { const parts = url.pathname.split("/").filter(Boolean); @@ -816,7 +817,7 @@ export function trimLines(str: string) { return str .trim() .split("\n") - .map(l => l.trim()) + .map((l) => l.trim()) .join("\n") .trim(); } @@ -824,7 +825,7 @@ export function trimLines(str: string) { export function trimEmptyLines(str: string) { return str .split("\n") - .filter(l => l.trim() !== "") + .filter((l) => l.trim() !== "") .join("\n"); } @@ -860,7 +861,7 @@ export function trimIndents(str: string, indentLength: number) { const regex = new RegExp(`^\\s{0,${indentLength}}`, "g"); return str .split("\n") - .map(line => line.replace(regex, "")) + .map((line) => line.replace(regex, "")) .join("\n"); } @@ -871,7 +872,7 @@ export function indentLine(str: string, indentLength: number) { export function indentLines(str: string, indentLength: number) { return str .split("\n") - .map(line => indentLine(line, indentLength)) + .map((line) => indentLine(line, indentLength)) .join("\n"); } @@ -977,7 +978,7 @@ export function chunkMessageLines(str: string, maxChunkLength = 1990): string[] const chunks = chunkLines(str, maxChunkLength); let openCodeBlock = false; - return chunks.map(chunk => { + return chunks.map((chunk) => { // If the chunk starts with a newline, add an invisible unicode char so Discord doesn't strip it away if (chunk[0] === "\n") chunk = "\u200b" + chunk; // If the chunk ends with a newline, add an invisible unicode char so Discord doesn't strip it away @@ -1019,14 +1020,14 @@ export async function createChunkedMessage( * Downloads the file from the given URL to a temporary file, with retry support */ export function downloadFile(attachmentUrl: string, retries = 3): Promise<{ path: string; deleteFn: () => void }> { - return new Promise(resolve => { + return new Promise((resolve) => { tmp.file((err, path, fd, deleteFn) => { if (err) throw err; const writeStream = fs.createWriteStream(path); https - .get(attachmentUrl, res => { + .get(attachmentUrl, (res) => { res.pipe(writeStream); writeStream.on("finish", () => { writeStream.end(); @@ -1036,7 +1037,7 @@ export function downloadFile(attachmentUrl: string, retries = 3): Promise<{ path }); }); }) - .on("error", httpsErr => { + .on("error", (httpsErr) => { fsp.unlink(path); if (retries === 0) { @@ -1061,7 +1062,7 @@ export function simpleClosestStringMatch(searchStr, haystack, getter?) { const normalizedSearchStr = searchStr.toLowerCase(); // See if any haystack item contains a part of the search string - const itemsWithRankings: Array> = haystack.map(item => { + const itemsWithRankings: Array> = haystack.map((item) => { const itemStr: string = getter ? getter(item) : item; const normalizedItemStr = itemStr.toLowerCase(); @@ -1100,14 +1101,14 @@ type sorterFn = (a: any, b: any) => number; function resolveGetter(getter: sorterGetterResolvable): sorterGetterFn { if (typeof getter === "string") { - return obj => obj[getter]; + return (obj) => obj[getter]; } return getter; } export function multiSorter(getters: Array): sorterFn { - const resolvedGetters: sorterGetterFnWithDirection[] = getters.map(getter => { + const resolvedGetters: sorterGetterFnWithDirection[] = getters.map((getter) => { if (Array.isArray(getter)) { return [resolveGetter(getter[0]), getter[1]] as sorterGetterFnWithDirection; } else { @@ -1288,7 +1289,7 @@ export function resolveUserId(bot: Client, value: string) { // A non-mention, full username? const usernameMatch = value.match(/^@?([^#]+)#(\d{4})$/); if (usernameMatch) { - const user = bot.users.cache.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]); + const user = bot.users.cache.find((u) => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]); if (user) return user.id; } @@ -1403,7 +1404,7 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string) // Role name const roleList = (await bot.guilds.fetch(guildId as Snowflake)).roles.cache; - const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase()); + const role = roleList.filter((x) => x.name.toLocaleLowerCase() === value.toLocaleLowerCase()); if (role.size >= 1) { return role.firstKey(); } @@ -1459,7 +1460,7 @@ export function messageSummary(msg: SavedMessage) { let result = "```\n" + (msg.data.content ? Util.escapeCodeBlock(msg.data.content) : "") + "```"; // Rich embed - const richEmbed = (msg.data.embeds || []).find(e => (e as MessageEmbed).type === "rich"); + const richEmbed = (msg.data.embeds || []).find((e) => (e as MessageEmbed).type === "rich"); if (richEmbed) result += "Embed:```" + Util.escapeCodeBlock(JSON.stringify(richEmbed)) + "```"; // Attachments @@ -1587,7 +1588,7 @@ export function canUseEmoji(client: Client, emoji: string): boolean { return true; } else if (isSnowflake(emoji)) { for (const guild of client.guilds.cache) { - if (guild[1].emojis.cache.some(e => (e as any).id === emoji)) { + if (guild[1].emojis.cache.some((e) => (e as any).id === emoji)) { return true; } } diff --git a/backend/src/utils/crypt.test.ts b/backend/src/utils/crypt.test.ts index 38b381ca..54417c8b 100644 --- a/backend/src/utils/crypt.test.ts +++ b/backend/src/utils/crypt.test.ts @@ -1,7 +1,7 @@ import test from "ava"; import { decrypt, encrypt } from "./crypt"; -test("encrypt() followed by decrypt()", t => { +test("encrypt() followed by decrypt()", (t) => { const original = "banana 123 👀 💕"; // Includes emojis to verify utf8 stuff works const encrypted = encrypt(original); const decrypted = decrypt(encrypted); diff --git a/backend/src/utils/normalizeText.test.ts b/backend/src/utils/normalizeText.test.ts index 9c9c03be..cd5a139c 100644 --- a/backend/src/utils/normalizeText.test.ts +++ b/backend/src/utils/normalizeText.test.ts @@ -1,25 +1,25 @@ import test from "ava"; import { normalizeText } from "./normalizeText"; -test("Replaces special characters", t => { +test("Replaces special characters", (t) => { const from = "𝗧:regional_indicator_e:ᔕ7 𝗧:regional_indicator_e:ᔕ7 𝗧:regional_indicator_e:ᔕ7"; const to = "test test test"; t.deepEqual(normalizeText(from), to); }); -test("Does not change lowercase ASCII text", t => { +test("Does not change lowercase ASCII text", (t) => { const text = "lorem ipsum dolor sit amet consectetur adipiscing elit"; t.deepEqual(normalizeText(text), text); }); -test("Replaces whitespace", t => { +test("Replaces whitespace", (t) => { const from = "foo bar"; const to = "foo bar"; t.deepEqual(normalizeText(from), to); }); -test("Result is always lowercase", t => { +test("Result is always lowercase", (t) => { const from = "TEST"; const to = "test"; t.deepEqual(normalizeText(from), to); diff --git a/backend/src/utils/parseFuzzyTimezone.ts b/backend/src/utils/parseFuzzyTimezone.ts index ef570e26..d4989681 100644 --- a/backend/src/utils/parseFuzzyTimezone.ts +++ b/backend/src/utils/parseFuzzyTimezone.ts @@ -1,7 +1,7 @@ import escapeStringRegexp from "escape-string-regexp"; import moment from "moment-timezone"; -const normalizeTzName = str => str.replace(/[^a-zA-Z0-9+\-]/g, "").toLowerCase(); +const normalizeTzName = (str) => str.replace(/[^a-zA-Z0-9+\-]/g, "").toLowerCase(); const validTimezones = moment.tz.names(); const normalizedTimezoneMap = validTimezones.reduce((map, tz) => { diff --git a/backend/src/utils/tColor.ts b/backend/src/utils/tColor.ts index ccc4f6a3..f240f7f4 100644 --- a/backend/src/utils/tColor.ts +++ b/backend/src/utils/tColor.ts @@ -8,9 +8,9 @@ export const tColor = new t.Type( "tColor", (s): s is number => typeof s === "number", (from, to) => - either.chain(t.string.validate(from, to), input => { + either.chain(t.string.validate(from, to), (input) => { const parsedColor = parseColor(input); return parsedColor == null ? t.failure(from, to, "Invalid color") : t.success(rgbToInt(parsedColor)); }), - s => intToRgb(s).join(","), + (s) => intToRgb(s).join(","), ); diff --git a/backend/src/utils/tValidTimezone.ts b/backend/src/utils/tValidTimezone.ts index 43c61c3e..35fc97c5 100644 --- a/backend/src/utils/tValidTimezone.ts +++ b/backend/src/utils/tValidTimezone.ts @@ -6,8 +6,8 @@ export const tValidTimezone = new t.Type( "tValidTimezone", (s): s is string => typeof s === "string", (from, to) => - either.chain(t.string.validate(from, to), input => { + either.chain(t.string.validate(from, to), (input) => { return isValidTimezone(input) ? t.success(input) : t.failure(from, to, `Invalid timezone: ${input}`); }), - s => s, + (s) => s, ); diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts index 14547ff8..d57e1fca 100644 --- a/backend/src/utils/templateSafeObjects.ts +++ b/backend/src/utils/templateSafeObjects.ts @@ -262,7 +262,7 @@ export function memberToTemplateSafeMember(member: GuildMember | PartialGuildMem ...templateSafeUser, user: templateSafeUser, nick: member.nickname ?? "*None*", - roles: [...member.roles.cache.mapValues(r => roleToTemplateSafeRole(r)).values()], + roles: [...member.roles.cache.mapValues((r) => roleToTemplateSafeRole(r)).values()], joinedAt: member.joinedTimestamp ?? undefined, guildName: member.guild.name, }); @@ -322,7 +322,7 @@ export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessag data: new TemplateSafeSavedMessageData({ attachments: (savedMessage.data.attachments ?? []).map( - att => + (att) => new TemplateSafeValueContainer({ id: att.id, contentType: att.contentType, @@ -343,7 +343,7 @@ export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessag content: savedMessage.data.content, embeds: (savedMessage.data.embeds ?? []).map( - embed => + (embed) => new TemplateSafeValueContainer({ title: embed.title, description: embed.description, @@ -352,7 +352,7 @@ export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessag color: embed.color, fields: (embed.fields ?? []).map( - field => + (field) => new TemplateSafeValueContainer({ name: field.name, value: field.value, @@ -407,7 +407,7 @@ export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessag ), stickers: (savedMessage.data.stickers ?? []).map( - sticker => + (sticker) => new TemplateSafeValueContainer({ format: sticker.format, guildId: sticker.guildId, @@ -459,7 +459,7 @@ export function getTemplateSafeMemberLevel(pluginData: GuildPluginData, mem const levels = pluginData.fullConfig.levels ?? {}; for (const [id, level] of Object.entries(levels)) { - if (member.id === id || member.roles?.find(r => r.id === id)) { + if (member.id === id || member.roles?.find((r) => r.id === id)) { return level as number; } } diff --git a/backend/src/utils/typeUtils.ts b/backend/src/utils/typeUtils.ts index 619f6cec..a32aa0e1 100644 --- a/backend/src/utils/typeUtils.ts +++ b/backend/src/utils/typeUtils.ts @@ -1,8 +1,7 @@ // From https://stackoverflow.com/a/56370310/316944 export type Tail = ((...t: T) => void) extends (h: any, ...r: infer R) => void ? R : never; -export declare type WithRequiredProps = T & - { - // https://mariusschulz.com/blog/mapped-type-modifiers-in-typescript#removing-the-mapped-type-modifier - [PK in K]-?: Exclude; - }; +export declare type WithRequiredProps = T & { + // https://mariusschulz.com/blog/mapped-type-modifiers-in-typescript#removing-the-mapped-type-modifier + [PK in K]-?: Exclude; +}; diff --git a/backend/src/utils/validateNoObjectAliases.test.ts b/backend/src/utils/validateNoObjectAliases.test.ts index fe2ff6bf..b418f72b 100644 --- a/backend/src/utils/validateNoObjectAliases.test.ts +++ b/backend/src/utils/validateNoObjectAliases.test.ts @@ -1,7 +1,7 @@ import test from "ava"; import { ObjectAliasError, validateNoObjectAliases } from "./validateNoObjectAliases"; -test("validateNoObjectAliases() disallows object aliases at top level", t => { +test("validateNoObjectAliases() disallows object aliases at top level", (t) => { const obj: any = { objectRef: { foo: "bar", @@ -12,7 +12,7 @@ test("validateNoObjectAliases() disallows object aliases at top level", t => { t.throws(() => validateNoObjectAliases(obj), { instanceOf: ObjectAliasError }); }); -test("validateNoObjectAliases() disallows aliases to nested objects", t => { +test("validateNoObjectAliases() disallows aliases to nested objects", (t) => { const obj: any = { nested: { objectRef: { @@ -25,7 +25,7 @@ test("validateNoObjectAliases() disallows aliases to nested objects", t => { t.throws(() => validateNoObjectAliases(obj), { instanceOf: ObjectAliasError }); }); -test("validateNoObjectAliases() disallows nested object aliases", t => { +test("validateNoObjectAliases() disallows nested object aliases", (t) => { const obj: any = { nested: { objectRef: { diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index e7e4ae7d..d39467c9 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -8,7 +8,7 @@ export async function waitForButtonConfirm( toPost: MessageOptions, options?: WaitForOptions, ): Promise { - return new Promise(async resolve => { + return new Promise(async (resolve) => { const idMod = `${channel.guild.id}-${moment.utc().valueOf()}`; const row = new MessageActionRow().addComponents([ new MessageButton() diff --git a/backend/src/validation.test.ts b/backend/src/validation.test.ts index 795e67ff..d41d6c4a 100644 --- a/backend/src/validation.test.ts +++ b/backend/src/validation.test.ts @@ -3,7 +3,7 @@ import * as t from "io-ts"; import { tDeepPartial } from "./utils"; import * as validatorUtils from "./validatorUtils"; -test("tDeepPartial works", ava => { +test("tDeepPartial works", (ava) => { const originalSchema = t.type({ listOfThings: t.record( t.string, diff --git a/backend/src/validatorUtils.ts b/backend/src/validatorUtils.ts index 8a6863f9..7b75637c 100644 --- a/backend/src/validatorUtils.ts +++ b/backend/src/validatorUtils.ts @@ -25,7 +25,7 @@ export const TRegex = new t.Type( "TRegex", (s): s is RegExp => s instanceof RegExp, (from, to) => - either.chain(t.string.validate(from, to), s => { + either.chain(t.string.validate(from, to), (s) => { try { return t.success(inputPatternToRegExp(s)); } catch (err) { @@ -36,7 +36,7 @@ export const TRegex = new t.Type( throw err; } }), - s => `/${s.source}/${s.flags}`, + (s) => `/${s.source}/${s.flags}`, ); // From io-ts/lib/PathReporter @@ -57,7 +57,7 @@ function stringify(v) { // tslint:disable function getContextPath(context) { return context - .map(function(_a) { + .map(function (_a) { var key = _a.key, type = _a.type; return key + ": " + type.name; @@ -80,8 +80,8 @@ export class StrictValidationError extends Error { } const report = fold((errors: any): StrictValidationError | void => { - const errorStrings = errors.map(err => { - const context = err.context.map(c => c.key).filter(k => k && !k.startsWith("{")); + const errorStrings = errors.map((err) => { + const context = err.context.map((c) => c.key).filter((k) => k && !k.startsWith("{")); while (context.length > 0 && !isNaN(context[context.length - 1])) context.splice(-1); const value = stringify(err.value); @@ -99,8 +99,8 @@ export function validate(schema: t.Type, value: any): StrictValidationError pipe( validationResult, fold( - err => report(validationResult), - result => null, + (err) => report(validationResult), + (result) => null, ), ) || null ); @@ -119,8 +119,8 @@ export function decodeAndValidateStrict( return pipe( validationResult, fold( - err => report(validationResult), - result => { + (err) => report(validationResult), + (result) => { // Make sure there are no extra properties if (debug) { console.log( @@ -133,7 +133,7 @@ export function decodeAndValidateStrict( } if (JSON.stringify(value) !== JSON.stringify(result)) { const diff = deepDiff(result, value); - const errors = diff.filter(d => d.kind === "N").map(d => `Unknown property <${d.path.join(".")}>`); + const errors = diff.filter((d) => d.kind === "N").map((d) => `Unknown property <${d.path.join(".")}>`); if (errors.length) return new StrictValidationError(errors); } diff --git a/backend/start-dev.js b/backend/start-dev.js index 5b6c3b44..70c912f2 100644 --- a/backend/start-dev.js +++ b/backend/start-dev.js @@ -5,9 +5,7 @@ const childProcess = require("child_process"); -const cmd = process.platform === "win32" - ? "npm.cmd" - : "npm"; +const cmd = process.platform === "win32" ? "npm.cmd" : "npm"; childProcess.spawn(cmd, ["run", "start-bot-dev"], { stdio: [process.stdin, process.stdout, process.stderr], diff --git a/backend/tsconfig.json b/backend/tsconfig.json index d20c0579..23f838e2 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -7,17 +7,13 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "target": "es2020", - "lib": [ - "esnext" - ], + "lib": ["esnext"], "baseUrl": ".", "resolveJsonModule": true, "esModuleInterop": true, "outDir": "./dist", "paths": { - "@shared/*": [ - "../shared/src/*" - ] + "@shared/*": ["../shared/src/*"] }, "sourceMap": true, "alwaysStrict": true, @@ -26,7 +22,5 @@ "strict": true, "strictPropertyInitialization": false }, - "include": [ - "src/**/*.ts" - ] + "include": ["src/**/*.ts"] } diff --git a/dashboard/.htmlnanorc.js b/dashboard/.htmlnanorc.js index 7aeaf2ab..5dff0968 100644 --- a/dashboard/.htmlnanorc.js +++ b/dashboard/.htmlnanorc.js @@ -1,3 +1,3 @@ module.exports = { - collapseWhitespace: false + collapseWhitespace: false, }; diff --git a/dashboard/src/api.ts b/dashboard/src/api.ts index 5782aa47..2a5fa858 100644 --- a/dashboard/src/api.ts +++ b/dashboard/src/api.ts @@ -22,13 +22,13 @@ function buildQueryString(params: QueryParamObject) { return ( "?" + Array.from(Object.entries(params)) - .map(pair => `${encodeURIComponent(pair[0])}=${encodeURIComponent(pair[1] || "")}`) + .map((pair) => `${encodeURIComponent(pair[0])}=${encodeURIComponent(pair[1] || "")}`) .join("&") ); } export function request(resource, fetchOpts: RequestInit = {}) { - return fetch(`${apiUrl}/${resource}`, fetchOpts).then(async res => { + return fetch(`${apiUrl}/${resource}`, fetchOpts).then(async (res) => { if (!res.ok) { if (res.status === 401) { RootStore.dispatch("auth/expiredLogin"); diff --git a/dashboard/src/directives/trim-indents.ts b/dashboard/src/directives/trim-indents.ts index 79f61ba3..eff9b92e 100644 --- a/dashboard/src/directives/trim-indents.ts +++ b/dashboard/src/directives/trim-indents.ts @@ -19,7 +19,7 @@ Vue.directive("trim-indents", { el.innerHTML = withoutStartEndWhitespace .split("\n") - .map(line => line.slice(spacesToTrim)) + .map((line) => line.slice(spacesToTrim)) .join("\n"); }, }); diff --git a/dashboard/src/index.html b/dashboard/src/index.html index b1be5ddf..7151c5b0 100644 --- a/dashboard/src/index.html +++ b/dashboard/src/index.html @@ -1,18 +1,20 @@ - + - - - - - Zeppelin - Moderation bot for Discord - - - + + + + + Zeppelin - Moderation bot for Discord + + + -
- +
+ diff --git a/dashboard/src/splash.html b/dashboard/src/splash.html index 4b2abb9f..18da7ee9 100644 --- a/dashboard/src/splash.html +++ b/dashboard/src/splash.html @@ -2,7 +2,7 @@
- +

Zeppelin

diff --git a/dashboard/src/store/guilds.ts b/dashboard/src/store/guilds.ts index dfe6ac43..10c2eb70 100644 --- a/dashboard/src/store/guilds.ts +++ b/dashboard/src/store/guilds.ts @@ -90,7 +90,7 @@ export const GuildStore: Module = { Vue.set( state.guildPermissionAssignments, guildId, - permissionAssignments.map(p => ({ + permissionAssignments.map((p) => ({ ...p, permissions: new Set(p.permissions), })), @@ -102,12 +102,12 @@ export const GuildStore: Module = { if (permissions.length === 0) { // No permissions -> remove permission assignment guildPermissionAssignments.splice( - guildPermissionAssignments.findIndex(p => p.target_id === targetId && p.type === type), + guildPermissionAssignments.findIndex((p) => p.target_id === targetId && p.type === type), 1, ); } else { // Update/add permission assignment - const itemToEdit = guildPermissionAssignments.find(p => p.target_id === targetId && p.type === type); + const itemToEdit = guildPermissionAssignments.find((p) => p.target_id === targetId && p.type === type); if (itemToEdit) { itemToEdit.permissions = new Set(permissions); } else { diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js index 800b9f1a..d3fbd401 100644 --- a/dashboard/tailwind.config.js +++ b/dashboard/tailwind.config.js @@ -3,22 +3,22 @@ module.exports = { theme: { extend: { lineHeight: { - zero: '0' + zero: "0", }, flex: { - full: '0 0 100%', - flexible: '1 1 0' - } + full: "0 0 100%", + flexible: "1 1 0", + }, }, screens: { - sm: '640px', - md: '768px', - 'until-lg': { max: '1023px' }, - lg: '1024px', - xl: '1280px', - '2xl': '1536px' - } + sm: "640px", + md: "768px", + "until-lg": { max: "1023px" }, + lg: "1024px", + xl: "1280px", + "2xl": "1536px", + }, }, variants: {}, - plugins: [] -} + plugins: [], +}; diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json index 42745a2a..1bd1fd32 100644 --- a/dashboard/tsconfig.json +++ b/dashboard/tsconfig.json @@ -9,10 +9,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "strict": false, - "lib": [ - "esnext", - "dom" - ], + "lib": ["esnext", "dom"], "baseUrl": ".", "resolveJsonModule": true, "esModuleInterop": true, diff --git a/dashboard/webpack.config.js b/dashboard/webpack.config.js index e9f10e51..e4b78765 100644 --- a/dashboard/webpack.config.js +++ b/dashboard/webpack.config.js @@ -1,42 +1,40 @@ -require('dotenv').config(); +require("dotenv").config(); -const path = require('path'); -const VueLoaderPlugin = require('vue-loader/lib/plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const DotenvPlugin = require('dotenv-webpack'); -const merge = require('webpack-merge'); +const path = require("path"); +const VueLoaderPlugin = require("vue-loader/lib/plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const DotenvPlugin = require("dotenv-webpack"); +const merge = require("webpack-merge"); -const targetDir = path.normalize(path.join(__dirname, 'dist')); +const targetDir = path.normalize(path.join(__dirname, "dist")); -if (! process.env.NODE_ENV) { - console.error('Please set NODE_ENV'); +if (!process.env.NODE_ENV) { + console.error("Please set NODE_ENV"); process.exit(1); } const babelOpts = { - presets: [ - '@babel/preset-env', - ], + presets: ["@babel/preset-env"], }; -const tsconfig = require('./tsconfig.json'); +const tsconfig = require("./tsconfig.json"); const pathAliases = Object.entries(tsconfig.compilerOptions.paths || []).reduce((aliases, pair) => { let alias = pair[0]; - if (alias.endsWith('/*')) alias = alias.slice(0, -2); + if (alias.endsWith("/*")) alias = alias.slice(0, -2); let aliasPath = pair[1][0]; - if (aliasPath.endsWith('/*')) aliasPath = aliasPath.slice(0, -2); + if (aliasPath.endsWith("/*")) aliasPath = aliasPath.slice(0, -2); aliases[alias] = path.resolve(__dirname, aliasPath); return aliases; }, {}); let config = { - entry: './src/main.ts', + entry: "./src/main.ts", output: { - filename: '[name].[hash].js', + filename: "[name].[hash].js", path: targetDir, - publicPath: '/', + publicPath: "/", }, module: { rules: [ @@ -50,11 +48,11 @@ let config = { exclude: /node_modules/, use: [ { - loader: 'babel-loader', + loader: "babel-loader", options: babelOpts, }, { - loader: 'ts-loader', + loader: "ts-loader", options: { appendTsSuffixTo: [/\.vue$/], }, @@ -65,7 +63,7 @@ let config = { test: /\.m?js$/, exclude: /node_modules/, use: { - loader: 'babel-loader', + loader: "babel-loader", options: babelOpts, }, }, @@ -90,26 +88,23 @@ let config = { loader: "postcss-loader", options: { ident: "postcss", - plugins: loader => { + plugins: (loader) => { const plugins = [ - require('postcss-import')({ + require("postcss-import")({ resolve(id, base, options) { // Since WebStorm doesn't resolve imports from node_modules without a tilde (~) prefix, // strip the tilde here to get the best of both worlds (webstorm support + postcss-import support) - if (id[0] === '~') id = id.slice(1); + if (id[0] === "~") id = id.slice(1); // Call the original resolver after stripping the tilde - return require('postcss-import/lib/resolve-id')(id, base, options); + return require("postcss-import/lib/resolve-id")(id, base, options); }, }), - require('postcss-nesting')(), - require('tailwindcss')(), + require("postcss-nesting")(), + require("tailwindcss")(), ]; if (process.env.NODE_ENV === "production") { - plugins.push( - require('postcss-preset-env')(), - require('cssnano')(), - ); + plugins.push(require("postcss-preset-env")(), require("cssnano")()); } return plugins; @@ -137,9 +132,9 @@ let config = { { loader: "html-loader", options: { - root: path.resolve(__dirname, 'src'), - attrs: ['img:src', 'link:href'], - ...(process.env.NODE_ENV === 'production' && { + root: path.resolve(__dirname, "src"), + attrs: ["img:src", "link:href"], + ...(process.env.NODE_ENV === "production" && { minimize: true, removeComments: true, collapseWhitespace: true, @@ -153,29 +148,29 @@ let config = { plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ - template: 'src/index.html', + template: "src/index.html", files: { - "css": ["./src/style/initial.pcss"], - "js": ["./src/main.ts"], + css: ["./src/style/initial.pcss"], + js: ["./src/main.ts"], }, }), new DotenvPlugin(), ], resolve: { - extensions: ['.ts', '.tsx', '.js', '.mjs', '.vue'], + extensions: [".ts", ".tsx", ".js", ".mjs", ".vue"], alias: pathAliases, }, }; -if (process.env.NODE_ENV === 'production') { +if (process.env.NODE_ENV === "production") { config = merge(config, { - mode: 'production', - devtool: 'source-map', + mode: "production", + devtool: "source-map", }); } else { config = merge(config, { - mode: 'development', - devtool: 'eval', + mode: "development", + devtool: "eval", devServer: { ...(process.env.DEV_HOST ? { host: process.env.DEV_HOST } : undefined), historyApiFallback: true, diff --git a/presetup-configurator/snowpack.config.js b/presetup-configurator/snowpack.config.js index 0125dd6f..2d15cba7 100644 --- a/presetup-configurator/snowpack.config.js +++ b/presetup-configurator/snowpack.config.js @@ -1,8 +1,6 @@ module.exports = { mount: { - "src": "/", + src: "/", }, - plugins: [ - '@snowpack/plugin-typescript', - ], + plugins: ["@snowpack/plugin-typescript"], }; diff --git a/presetup-configurator/src/App.tsx b/presetup-configurator/src/App.tsx index 5201bdf8..54e9e365 100644 --- a/presetup-configurator/src/App.tsx +++ b/presetup-configurator/src/App.tsx @@ -3,9 +3,11 @@ import { Configurator } from "./Configurator"; import "./App.css"; export function App() { - return
-
- + return ( +
+
+ +
-
; + ); } diff --git a/presetup-configurator/src/Configurator.css b/presetup-configurator/src/Configurator.css index 8bebd777..7d8ba426 100644 --- a/presetup-configurator/src/Configurator.css +++ b/presetup-configurator/src/Configurator.css @@ -1,5 +1,4 @@ .Configurator { - } .Configurator .options { diff --git a/presetup-configurator/src/Configurator.tsx b/presetup-configurator/src/Configurator.tsx index 8e2222de..02fd9d7d 100644 --- a/presetup-configurator/src/Configurator.tsx +++ b/presetup-configurator/src/Configurator.tsx @@ -5,7 +5,7 @@ import yaml from "js-yaml"; import "./Configurator.css"; export function Configurator() { - const [prefix, setPrefix] = useState('!'); + const [prefix, setPrefix] = useState("!"); const [levels, setLevels] = useState([]); const [withModCommands, setWithModCommands] = useState(false); @@ -42,7 +42,7 @@ export function Configurator() { resultObj.plugins.mutes = { config: { mute_role: muteRoleId, - } + }, }; if (dmModActionReasons) { @@ -108,7 +108,7 @@ export function Configurator() { setCopied(true); } - const [copyResetTimeout, setCopyResetTimeout] = useState(null); + const [copyResetTimeout, setCopyResetTimeout] = useState(null); useEffect(() => { if (!copied) { return; @@ -129,8 +129,9 @@ export function Configurator() {

Prefix

@@ -142,24 +143,30 @@ export function Configurator() {

Mod commands

{withModCommands && (
@@ -169,21 +176,23 @@ export function Configurator() {

Logs

- {withLogs && ( - - )} + {withLogs && }
{/* Result */} -