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;