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

feat: handle template errors

Fixes ZDEV-20
This commit is contained in:
Dragory 2024-01-27 16:01:48 +02:00
parent 2ce5082018
commit ffa9eeb3f5
No known key found for this signature in database
14 changed files with 231 additions and 94 deletions

View file

@ -11,6 +11,7 @@ export enum ERRORS {
MUTE_ROLE_ABOVE_ZEP, MUTE_ROLE_ABOVE_ZEP,
USER_ABOVE_ZEP, USER_ABOVE_ZEP,
USER_NOT_MODERATABLE, USER_NOT_MODERATABLE,
TEMPLATE_PARSE_ERROR,
} }
export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = { export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
@ -24,6 +25,7 @@ export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
[ERRORS.MUTE_ROLE_ABOVE_ZEP]: "Specified mute role is above Zeppelin in the role hierarchy", [ERRORS.MUTE_ROLE_ABOVE_ZEP]: "Specified mute role is above Zeppelin in the role hierarchy",
[ERRORS.USER_ABOVE_ZEP]: "Cannot mute user, specified user is above Zeppelin in the role hierarchy", [ERRORS.USER_ABOVE_ZEP]: "Cannot mute user, specified user is above Zeppelin in the role hierarchy",
[ERRORS.USER_NOT_MODERATABLE]: "Cannot mute user, specified user is not moderatable", [ERRORS.USER_NOT_MODERATABLE]: "Cannot mute user, specified user is not moderatable",
[ERRORS.TEMPLATE_PARSE_ERROR]: "Template parse error",
}; };
export class RecoverablePluginError extends Error { export class RecoverablePluginError extends Error {

View file

@ -1,13 +1,14 @@
import { PermissionsBitField, PermissionsString } from "discord.js"; import { PermissionsBitField, PermissionsString } from "discord.js";
import { U } from "ts-toolbelt"; import { U } from "ts-toolbelt";
import z from "zod"; import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { isValidSnowflake, keys, noop, zBoundedCharacters } from "../../../utils"; import { isValidSnowflake, keys, noop, zBoundedCharacters } from "../../../utils";
import { import {
guildToTemplateSafeGuild, guildToTemplateSafeGuild,
savedMessageToTemplateSafeSavedMessage, savedMessageToTemplateSafeSavedMessage,
userToTemplateSafeUser, userToTemplateSafeUser,
} from "../../../utils/templateSafeObjects"; } from "../../../utils/templateSafeObjects";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
type LegacyPermMap = Record<string, keyof (typeof PermissionsBitField)["Flags"]>; type LegacyPermMap = Record<string, keyof (typeof PermissionsBitField)["Flags"]>;
@ -71,30 +72,52 @@ export const ChangePermsAction = automodAction({
perms: z.record(z.enum(allPermissionNames), z.boolean().nullable()), perms: z.record(z.enum(allPermissionNames), z.boolean().nullable()),
}), }),
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const user = contexts.find((c) => c.user)?.user; const user = contexts.find((c) => c.user)?.user;
const message = contexts.find((c) => c.message)?.message; const message = contexts.find((c) => c.message)?.message;
const renderTarget = async (str: string) => let target: string;
renderTemplate( try {
str, target = await renderTemplate(
actionConfig.target,
new TemplateSafeValueContainer({ new TemplateSafeValueContainer({
user: user ? userToTemplateSafeUser(user) : null, user: user ? userToTemplateSafeUser(user) : null,
guild: guildToTemplateSafeGuild(pluginData.guild), guild: guildToTemplateSafeGuild(pluginData.guild),
message: message ? savedMessageToTemplateSafeSavedMessage(message) : null, message: message ? savedMessageToTemplateSafeSavedMessage(message) : null,
}), }),
); );
const renderChannel = async (str: string) => } catch (err) {
renderTemplate( if (err instanceof TemplateParseError) {
str, pluginData.getPlugin(LogsPlugin).logBotAlert({
new TemplateSafeValueContainer({ body: `Error in target format of automod rule ${ruleName}: ${err.message}`,
user: user ? userToTemplateSafeUser(user) : null, });
guild: guildToTemplateSafeGuild(pluginData.guild), return;
message: message ? savedMessageToTemplateSafeSavedMessage(message) : null, }
}), throw err;
); }
const target = await renderTarget(actionConfig.target);
const channelId = actionConfig.channel ? await renderChannel(actionConfig.channel) : null; let channelId: string | null = null;
if (actionConfig.channel) {
try {
channelId = await renderTemplate(
actionConfig.channel,
new TemplateSafeValueContainer({
user: user ? userToTemplateSafeUser(user) : null,
guild: guildToTemplateSafeGuild(pluginData.guild),
message: message ? savedMessageToTemplateSafeSavedMessage(message) : null,
}),
);
} catch (err) {
if (err instanceof TemplateParseError) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Error in channel format of automod rule ${ruleName}: ${err.message}`,
});
return;
}
throw err;
}
}
const role = pluginData.guild.roles.resolve(target); const role = pluginData.guild.roles.resolve(target);
if (!role) { if (!role) {
const member = await pluginData.guild.members.fetch(target).catch(noop); const member = await pluginData.guild.members.fetch(target).catch(noop);

View file

@ -1,6 +1,6 @@
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js"; import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js";
import z from "zod"; import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { import {
convertDelayStringToMS, convertDelayStringToMS,
noop, noop,
@ -58,10 +58,21 @@ export const ReplyAction = automodAction({
}), }),
); );
const formatted = let formatted: string | MessageCreateOptions;
typeof actionConfig === "string" try {
? await renderReplyText(actionConfig) formatted =
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageCreateOptions); typeof actionConfig === "string"
? await renderReplyText(actionConfig)
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageCreateOptions);
} catch (err) {
if (err instanceof TemplateParseError) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Error in reply format of automod rule \`${ruleName}\`: ${err.message}`,
});
return;
}
throw err;
}
if (formatted) { if (formatted) {
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel; const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel;

View file

@ -1,8 +1,9 @@
import { ChannelType, GuildTextThreadCreateOptions, ThreadAutoArchiveDuration, ThreadChannel } from "discord.js"; import { ChannelType, GuildTextThreadCreateOptions, ThreadAutoArchiveDuration, ThreadChannel } from "discord.js";
import z from "zod"; import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { MINUTES, convertDelayStringToMS, noop, zBoundedCharacters, zDelayString } from "../../../utils"; import { MINUTES, convertDelayStringToMS, noop, zBoundedCharacters, zDelayString } from "../../../utils";
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [ const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
@ -21,7 +22,7 @@ export const StartThreadAction = automodAction({
limit_per_channel: z.number().nullable().default(5), limit_per_channel: z.number().nullable().default(5),
}), }),
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
// check if the message still exists, we don't want to create threads for deleted messages // check if the message still exists, we don't want to create threads for deleted messages
const threads = contexts.filter((c) => { const threads = contexts.filter((c) => {
if (!c.message || !c.user) return false; if (!c.message || !c.user) return false;
@ -48,15 +49,25 @@ export const StartThreadAction = automodAction({
const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id); const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id);
if (!channel || !("threads" in channel) || channel.isThreadOnly()) continue; if (!channel || !("threads" in channel) || channel.isThreadOnly()) continue;
const renderThreadName = async (str: string) => let threadName: string;
renderTemplate( try {
str, threadName = await renderTemplate(
actionConfig.name ?? "{user.renderedUsername}'s thread",
new TemplateSafeValueContainer({ new TemplateSafeValueContainer({
user: userToTemplateSafeUser(threadContext.user!), user: userToTemplateSafeUser(threadContext.user!),
msg: savedMessageToTemplateSafeSavedMessage(threadContext.message!), msg: savedMessageToTemplateSafeSavedMessage(threadContext.message!),
}), }),
); );
const threadName = await renderThreadName(actionConfig.name ?? "{user.renderedUsername}'s thread"); } catch (err) {
if (err instanceof TemplateParseError) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Error in thread name format of automod rule ${ruleName}: ${err.message}`,
});
return;
}
throw err;
}
const threadOptions: GuildTextThreadCreateOptions<unknown> = { const threadOptions: GuildTextThreadCreateOptions<unknown> = {
name: threadName, name: threadName,
autoArchiveDuration: autoArchive, autoArchiveDuration: autoArchive,

View file

@ -11,6 +11,7 @@ import {
messageToTemplateSafeMessage, messageToTemplateSafeMessage,
userToTemplateSafeUser, userToTemplateSafeUser,
} from "../../utils/templateSafeObjects"; } from "../../utils/templateSafeObjects";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { runEvent } from "./functions/runEvent"; import { runEvent } from "./functions/runEvent";
import { CustomEventsPluginType, zCustomEventsConfig } from "./types"; import { CustomEventsPluginType, zCustomEventsConfig } from "./types";
@ -25,6 +26,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
name: "custom_events", name: "custom_events",
showInDocs: false, showInDocs: false,
dependencies: () => [LogsPlugin],
configParser: (input) => zCustomEventsConfig.parse(input), configParser: (input) => zCustomEventsConfig.parse(input),
defaultOptions, defaultOptions,

View file

@ -4,6 +4,7 @@ import { canActOn } from "../../../pluginUtils";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import { resolveMember, zSnowflake } from "../../../utils"; import { resolveMember, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { catchTemplateError } from "../catchTemplateError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const zAddRoleAction = z.strictObject({ export const zAddRoleAction = z.strictObject({
@ -20,7 +21,10 @@ export async function addRoleAction(
event: TCustomEvent, event: TCustomEvent,
eventData: any, eventData: any,
) { ) {
const targetId = await renderTemplate(action.target, values, false); const targetId = await catchTemplateError(
() => renderTemplate(action.target, values, false),
"Invalid target format",
);
const target = await resolveMember(pluginData.client, pluginData.guild, targetId); const target = await resolveMember(pluginData.client, pluginData.guild, targetId);
if (!target) throw new ActionError(`Unknown target member: ${targetId}`); if (!target) throw new ActionError(`Unknown target member: ${targetId}`);

View file

@ -5,6 +5,7 @@ import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFor
import { zBoundedCharacters, zSnowflake } from "../../../utils"; import { zBoundedCharacters, zSnowflake } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CasesPlugin } from "../../Cases/CasesPlugin";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { catchTemplateError } from "../catchTemplateError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const zCreateCaseAction = z.strictObject({ export const zCreateCaseAction = z.strictObject({
@ -23,10 +24,12 @@ export async function createCaseAction(
event: TCustomEvent, event: TCustomEvent,
eventData: any, // eslint-disable-line @typescript-eslint/no-unused-vars eventData: any, // eslint-disable-line @typescript-eslint/no-unused-vars
) { ) {
const modId = await renderTemplate(action.mod, values, false); const modId = await catchTemplateError(() => renderTemplate(action.mod, values, false), "Invalid mod format");
const targetId = await renderTemplate(action.target, values, false); const targetId = await catchTemplateError(
() => renderTemplate(action.target, values, false),
const reason = await renderTemplate(action.reason, values, false); "Invalid target format",
);
const reason = await catchTemplateError(() => renderTemplate(action.reason, values, false), "Invalid reason format");
if (CaseTypes[action.case_type] == null) { if (CaseTypes[action.case_type] == null) {
throw new ActionError(`Invalid case type: ${action.type}`); throw new ActionError(`Invalid case type: ${action.type}`);

View file

@ -4,6 +4,7 @@ import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { zBoundedCharacters, zSnowflake } from "../../../utils"; import { zBoundedCharacters, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { catchTemplateError } from "../catchTemplateError";
import { CustomEventsPluginType } from "../types"; import { CustomEventsPluginType } from "../types";
export const zMessageAction = z.strictObject({ export const zMessageAction = z.strictObject({
@ -18,7 +19,10 @@ export async function messageAction(
action: TMessageAction, action: TMessageAction,
values: TemplateSafeValueContainer, values: TemplateSafeValueContainer,
) { ) {
const targetChannelId = await renderTemplate(action.channel, values, false); const targetChannelId = await catchTemplateError(
() => renderTemplate(action.channel, values, false),
"Invalid channel format",
);
const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake); const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake);
if (!targetChannel) throw new ActionError("Unknown target channel"); if (!targetChannel) throw new ActionError("Unknown target channel");
if (!(targetChannel instanceof TextChannel)) throw new ActionError("Target channel is not a text channel"); if (!(targetChannel instanceof TextChannel)) throw new ActionError("Target channel is not a text channel");

View file

@ -5,6 +5,7 @@ import { canActOn } from "../../../pluginUtils";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { resolveMember, zSnowflake } from "../../../utils"; import { resolveMember, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { catchTemplateError } from "../catchTemplateError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const zMoveToVoiceChannelAction = z.strictObject({ export const zMoveToVoiceChannelAction = z.strictObject({
@ -21,7 +22,10 @@ export async function moveToVoiceChannelAction(
event: TCustomEvent, event: TCustomEvent,
eventData: any, eventData: any,
) { ) {
const targetId = await renderTemplate(action.target, values, false); const targetId = await catchTemplateError(
() => renderTemplate(action.target, values, false),
"Invalid target format",
);
const target = await resolveMember(pluginData.client, pluginData.guild, targetId); const target = await resolveMember(pluginData.client, pluginData.guild, targetId);
if (!target) throw new ActionError("Unknown target member"); if (!target) throw new ActionError("Unknown target member");
@ -29,7 +33,10 @@ export async function moveToVoiceChannelAction(
throw new ActionError("Missing permissions"); throw new ActionError("Missing permissions");
} }
const targetChannelId = await renderTemplate(action.channel, values, false); const targetChannelId = await catchTemplateError(
() => renderTemplate(action.channel, values, false),
"Invalid channel format",
);
const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake); const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake);
if (!targetChannel) throw new ActionError("Unknown target channel"); if (!targetChannel) throw new ActionError("Unknown target channel");
if (!(targetChannel instanceof VoiceChannel)) throw new ActionError("Target channel is not a voice channel"); if (!(targetChannel instanceof VoiceChannel)) throw new ActionError("Target channel is not a voice channel");

View file

@ -0,0 +1,13 @@
import { TemplateParseError } from "../../templateFormatter";
import { ActionError } from "./ActionError";
export function catchTemplateError(fn: () => Promise<string>, errorText: string): Promise<string> {
try {
return fn();
} catch (err) {
if (err instanceof TemplateParseError) {
throw new ActionError(`${errorText}: ${err.message}`);
}
throw err;
}
}

View file

@ -5,7 +5,7 @@ import { CaseTypes } from "../../../data/CaseTypes";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop"; import { registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop";
import { logger } from "../../../logger"; import { logger } from "../../../logger";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { import {
DAYS, DAYS,
SECONDS, SECONDS,
@ -52,30 +52,52 @@ export async function banUserId(
if (contactMethods.length) { if (contactMethods.length) {
if (!banTime && config.ban_message) { if (!banTime && config.ban_message) {
const banMessage = await renderTemplate( let banMessage: string;
config.ban_message, try {
new TemplateSafeValueContainer({ banMessage = await renderTemplate(
guildName: pluginData.guild.name, config.ban_message,
reason, new TemplateSafeValueContainer({
moderator: banOptions.caseArgs?.modId guildName: pluginData.guild.name,
? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) reason,
: null, moderator: banOptions.caseArgs?.modId
}), ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId))
); : null,
}),
);
} catch (err) {
if (err instanceof TemplateParseError) {
return {
status: "failed",
error: `Invalid ban_message format: ${err.message}`,
};
}
throw err;
}
notifyResult = await notifyUser(member.user, banMessage, contactMethods); notifyResult = await notifyUser(member.user, banMessage, contactMethods);
} else if (banTime && config.tempban_message) { } else if (banTime && config.tempban_message) {
const banMessage = await renderTemplate( let banMessage: string;
config.tempban_message, try {
new TemplateSafeValueContainer({ banMessage = await renderTemplate(
guildName: pluginData.guild.name, config.tempban_message,
reason, new TemplateSafeValueContainer({
moderator: banOptions.caseArgs?.modId guildName: pluginData.guild.name,
? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) reason,
: null, moderator: banOptions.caseArgs?.modId
banTime: humanizeDuration(banTime), ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId))
}), : null,
); banTime: humanizeDuration(banTime),
}),
);
} catch (err) {
if (err instanceof TemplateParseError) {
return {
status: "failed",
error: `Invalid tempban_message format: ${err.message}`,
};
}
throw err;
}
notifyResult = await notifyUser(member.user, banMessage, contactMethods); notifyResult = await notifyUser(member.user, banMessage, contactMethods);
} else { } else {

View file

@ -2,7 +2,7 @@ import { GuildMember } from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { CaseTypes } from "../../../data/CaseTypes"; import { CaseTypes } from "../../../data/CaseTypes";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderTemplate, TemplateParseError, TemplateSafeValueContainer } from "../../../templateFormatter";
import { createUserNotificationError, notifyUser, resolveUser, ucfirst, UserNotificationResult } from "../../../utils"; import { createUserNotificationError, notifyUser, resolveUser, ucfirst, UserNotificationResult } from "../../../utils";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CasesPlugin } from "../../Cases/CasesPlugin";
@ -31,16 +31,27 @@ export async function kickMember(
if (contactMethods.length) { if (contactMethods.length) {
if (config.kick_message) { if (config.kick_message) {
const kickMessage = await renderTemplate( let kickMessage: string;
config.kick_message, try {
new TemplateSafeValueContainer({ kickMessage = await renderTemplate(
guildName: pluginData.guild.name, config.kick_message,
reason, new TemplateSafeValueContainer({
moderator: kickOptions.caseArgs?.modId guildName: pluginData.guild.name,
? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) reason,
: null, moderator: kickOptions.caseArgs?.modId
}), ? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId))
); : null,
}),
);
} catch (err) {
if (err instanceof TemplateParseError) {
return {
status: "failed",
error: `Invalid kick_message format: ${err.message}`,
};
}
throw err;
}
notifyResult = await notifyUser(member.user, kickMessage, contactMethods); notifyResult = await notifyUser(member.user, kickMessage, contactMethods);
} else { } else {

View file

@ -1,7 +1,7 @@
import { GuildMember, Snowflake } from "discord.js"; import { GuildMember, Snowflake } from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { CaseTypes } from "../../../data/CaseTypes"; import { CaseTypes } from "../../../data/CaseTypes";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { UserNotificationResult, createUserNotificationError, notifyUser, resolveUser, ucfirst } from "../../../utils"; import { UserNotificationResult, createUserNotificationError, notifyUser, resolveUser, ucfirst } from "../../../utils";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { waitForButtonConfirm } from "../../../utils/waitForInteraction";
@ -20,16 +20,27 @@ export async function warnMember(
let notifyResult: UserNotificationResult; let notifyResult: UserNotificationResult;
if (config.warn_message) { if (config.warn_message) {
const warnMessage = await renderTemplate( let warnMessage: string;
config.warn_message, try {
new TemplateSafeValueContainer({ warnMessage = await renderTemplate(
guildName: pluginData.guild.name, config.warn_message,
reason, new TemplateSafeValueContainer({
moderator: warnOptions.caseArgs?.modId guildName: pluginData.guild.name,
? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) reason,
: null, moderator: warnOptions.caseArgs?.modId
}), ? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId))
); : null,
}),
);
} catch (err) {
if (err instanceof TemplateParseError) {
return {
status: "failed",
error: `Invalid warn_message format: ${err.message}`,
};
}
throw err;
}
const contactMethods = warnOptions?.contactMethods const contactMethods = warnOptions?.contactMethods
? warnOptions.contactMethods ? warnOptions.contactMethods
: getDefaultContactMethods(pluginData, "warn"); : getDefaultContactMethods(pluginData, "warn");

View file

@ -9,7 +9,7 @@ import { Case } from "../../../data/entities/Case";
import { Mute } from "../../../data/entities/Mute"; import { Mute } from "../../../data/entities/Mute";
import { registerExpiringMute } from "../../../data/loops/expiringMutesLoop"; import { registerExpiringMute } from "../../../data/loops/expiringMutesLoop";
import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { import {
UserNotificationMethod, UserNotificationMethod,
UserNotificationResult, UserNotificationResult,
@ -61,9 +61,10 @@ export async function muteUser(
const member = await resolveMember(pluginData.client, pluginData.guild, user.id, true); // Grab the fresh member so we don't have stale role info const member = await resolveMember(pluginData.client, pluginData.guild, user.id, true); // Grab the fresh member so we don't have stale role info
const config = await pluginData.config.getMatchingConfig({ member, userId }); const config = await pluginData.config.getMatchingConfig({ member, userId });
const logs = pluginData.getPlugin(LogsPlugin);
let rolesToRestore: string[] = []; let rolesToRestore: string[] = [];
if (member) { if (member) {
const logs = pluginData.getPlugin(LogsPlugin);
// remove and store any roles to be removed/restored // remove and store any roles to be removed/restored
const currentUserRoles = [...member.roles.cache.keys()]; const currentUserRoles = [...member.roles.cache.keys()];
let newRoles: string[] = currentUserRoles; let newRoles: string[] = currentUserRoles;
@ -187,19 +188,31 @@ export async function muteUser(
? config.timed_mute_message ? config.timed_mute_message
: config.mute_message; : config.mute_message;
const muteMessage = let muteMessage: string | null = null;
template && try {
(await renderTemplate( muteMessage =
template, template &&
new TemplateSafeValueContainer({ (await renderTemplate(
guildName: pluginData.guild.name, template,
reason: reason || "None", new TemplateSafeValueContainer({
time: timeUntilUnmuteStr, guildName: pluginData.guild.name,
moderator: muteOptions.caseArgs?.modId reason: reason || "None",
? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) time: timeUntilUnmuteStr,
: null, moderator: muteOptions.caseArgs?.modId
}), ? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId))
)); : null,
}),
));
} catch (err) {
if (err instanceof TemplateParseError) {
logs.logBotAlert({
body: `Invalid mute message format. The mute was still applied: ${err.message}`,
});
} else {
lock.unlock();
throw err;
}
}
if (muteMessage && member) { if (muteMessage && member) {
let contactMethods: UserNotificationMethod[] = []; let contactMethods: UserNotificationMethod[] = [];