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:
parent
2ce5082018
commit
ffa9eeb3f5
14 changed files with 231 additions and 94 deletions
|
@ -11,6 +11,7 @@ export enum ERRORS {
|
|||
MUTE_ROLE_ABOVE_ZEP,
|
||||
USER_ABOVE_ZEP,
|
||||
USER_NOT_MODERATABLE,
|
||||
TEMPLATE_PARSE_ERROR,
|
||||
}
|
||||
|
||||
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.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.TEMPLATE_PARSE_ERROR]: "Template parse error",
|
||||
};
|
||||
|
||||
export class RecoverablePluginError extends Error {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { PermissionsBitField, PermissionsString } from "discord.js";
|
||||
import { U } from "ts-toolbelt";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { isValidSnowflake, keys, noop, zBoundedCharacters } from "../../../utils";
|
||||
import {
|
||||
guildToTemplateSafeGuild,
|
||||
savedMessageToTemplateSafeSavedMessage,
|
||||
userToTemplateSafeUser,
|
||||
} from "../../../utils/templateSafeObjects";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
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()),
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const user = contexts.find((c) => c.user)?.user;
|
||||
const message = contexts.find((c) => c.message)?.message;
|
||||
|
||||
const renderTarget = async (str: string) =>
|
||||
renderTemplate(
|
||||
str,
|
||||
let target: string;
|
||||
try {
|
||||
target = await renderTemplate(
|
||||
actionConfig.target,
|
||||
new TemplateSafeValueContainer({
|
||||
user: user ? userToTemplateSafeUser(user) : null,
|
||||
guild: guildToTemplateSafeGuild(pluginData.guild),
|
||||
message: message ? savedMessageToTemplateSafeSavedMessage(message) : null,
|
||||
}),
|
||||
);
|
||||
const renderChannel = async (str: string) =>
|
||||
renderTemplate(
|
||||
str,
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Error in target format of automod rule ${ruleName}: ${err.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
);
|
||||
const target = await renderTarget(actionConfig.target);
|
||||
const channelId = actionConfig.channel ? await renderChannel(actionConfig.channel) : 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);
|
||||
if (!role) {
|
||||
const member = await pluginData.guild.members.fetch(target).catch(noop);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
noop,
|
||||
|
@ -58,10 +58,21 @@ export const ReplyAction = automodAction({
|
|||
}),
|
||||
);
|
||||
|
||||
const formatted =
|
||||
let formatted: string | MessageCreateOptions;
|
||||
try {
|
||||
formatted =
|
||||
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) {
|
||||
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ChannelType, GuildTextThreadCreateOptions, ThreadAutoArchiveDuration, ThreadChannel } from "discord.js";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { MINUTES, convertDelayStringToMS, noop, zBoundedCharacters, zDelayString } from "../../../utils";
|
||||
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
|
||||
|
@ -21,7 +22,7 @@ export const StartThreadAction = automodAction({
|
|||
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
|
||||
const threads = contexts.filter((c) => {
|
||||
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);
|
||||
if (!channel || !("threads" in channel) || channel.isThreadOnly()) continue;
|
||||
|
||||
const renderThreadName = async (str: string) =>
|
||||
renderTemplate(
|
||||
str,
|
||||
let threadName: string;
|
||||
try {
|
||||
threadName = await renderTemplate(
|
||||
actionConfig.name ?? "{user.renderedUsername}'s thread",
|
||||
new TemplateSafeValueContainer({
|
||||
user: userToTemplateSafeUser(threadContext.user!),
|
||||
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> = {
|
||||
name: threadName,
|
||||
autoArchiveDuration: autoArchive,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
messageToTemplateSafeMessage,
|
||||
userToTemplateSafeUser,
|
||||
} from "../../utils/templateSafeObjects";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { runEvent } from "./functions/runEvent";
|
||||
import { CustomEventsPluginType, zCustomEventsConfig } from "./types";
|
||||
|
@ -25,6 +26,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
|
|||
name: "custom_events",
|
||||
showInDocs: false,
|
||||
|
||||
dependencies: () => [LogsPlugin],
|
||||
configParser: (input) => zCustomEventsConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { canActOn } from "../../../pluginUtils";
|
|||
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
|
||||
import { resolveMember, zSnowflake } from "../../../utils";
|
||||
import { ActionError } from "../ActionError";
|
||||
import { catchTemplateError } from "../catchTemplateError";
|
||||
import { CustomEventsPluginType, TCustomEvent } from "../types";
|
||||
|
||||
export const zAddRoleAction = z.strictObject({
|
||||
|
@ -20,7 +21,10 @@ export async function addRoleAction(
|
|||
event: TCustomEvent,
|
||||
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);
|
||||
if (!target) throw new ActionError(`Unknown target member: ${targetId}`);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFor
|
|||
import { zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
import { ActionError } from "../ActionError";
|
||||
import { catchTemplateError } from "../catchTemplateError";
|
||||
import { CustomEventsPluginType, TCustomEvent } from "../types";
|
||||
|
||||
export const zCreateCaseAction = z.strictObject({
|
||||
|
@ -23,10 +24,12 @@ export async function createCaseAction(
|
|||
event: TCustomEvent,
|
||||
eventData: any, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
) {
|
||||
const modId = await renderTemplate(action.mod, values, false);
|
||||
const targetId = await renderTemplate(action.target, values, false);
|
||||
|
||||
const reason = await renderTemplate(action.reason, values, false);
|
||||
const modId = await catchTemplateError(() => renderTemplate(action.mod, values, false), "Invalid mod format");
|
||||
const targetId = await catchTemplateError(
|
||||
() => renderTemplate(action.target, values, false),
|
||||
"Invalid target format",
|
||||
);
|
||||
const reason = await catchTemplateError(() => renderTemplate(action.reason, values, false), "Invalid reason format");
|
||||
|
||||
if (CaseTypes[action.case_type] == null) {
|
||||
throw new ActionError(`Invalid case type: ${action.type}`);
|
||||
|
|
|
@ -4,6 +4,7 @@ import z from "zod";
|
|||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { ActionError } from "../ActionError";
|
||||
import { catchTemplateError } from "../catchTemplateError";
|
||||
import { CustomEventsPluginType } from "../types";
|
||||
|
||||
export const zMessageAction = z.strictObject({
|
||||
|
@ -18,7 +19,10 @@ export async function messageAction(
|
|||
action: TMessageAction,
|
||||
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);
|
||||
if (!targetChannel) throw new ActionError("Unknown target channel");
|
||||
if (!(targetChannel instanceof TextChannel)) throw new ActionError("Target channel is not a text channel");
|
||||
|
|
|
@ -5,6 +5,7 @@ import { canActOn } from "../../../pluginUtils";
|
|||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { resolveMember, zSnowflake } from "../../../utils";
|
||||
import { ActionError } from "../ActionError";
|
||||
import { catchTemplateError } from "../catchTemplateError";
|
||||
import { CustomEventsPluginType, TCustomEvent } from "../types";
|
||||
|
||||
export const zMoveToVoiceChannelAction = z.strictObject({
|
||||
|
@ -21,7 +22,10 @@ export async function moveToVoiceChannelAction(
|
|||
event: TCustomEvent,
|
||||
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);
|
||||
if (!target) throw new ActionError("Unknown target member");
|
||||
|
||||
|
@ -29,7 +33,10 @@ export async function moveToVoiceChannelAction(
|
|||
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);
|
||||
if (!targetChannel) throw new ActionError("Unknown target channel");
|
||||
if (!(targetChannel instanceof VoiceChannel)) throw new ActionError("Target channel is not a voice channel");
|
||||
|
|
13
backend/src/plugins/CustomEvents/catchTemplateError.ts
Normal file
13
backend/src/plugins/CustomEvents/catchTemplateError.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import { CaseTypes } from "../../../data/CaseTypes";
|
|||
import { LogType } from "../../../data/LogType";
|
||||
import { registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop";
|
||||
import { logger } from "../../../logger";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import {
|
||||
DAYS,
|
||||
SECONDS,
|
||||
|
@ -52,7 +52,9 @@ export async function banUserId(
|
|||
|
||||
if (contactMethods.length) {
|
||||
if (!banTime && config.ban_message) {
|
||||
const banMessage = await renderTemplate(
|
||||
let banMessage: string;
|
||||
try {
|
||||
banMessage = await renderTemplate(
|
||||
config.ban_message,
|
||||
new TemplateSafeValueContainer({
|
||||
guildName: pluginData.guild.name,
|
||||
|
@ -62,10 +64,21 @@ export async function banUserId(
|
|||
: 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);
|
||||
} else if (banTime && config.tempban_message) {
|
||||
const banMessage = await renderTemplate(
|
||||
let banMessage: string;
|
||||
try {
|
||||
banMessage = await renderTemplate(
|
||||
config.tempban_message,
|
||||
new TemplateSafeValueContainer({
|
||||
guildName: pluginData.guild.name,
|
||||
|
@ -76,6 +89,15 @@ export async function banUserId(
|
|||
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);
|
||||
} else {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GuildMember } from "discord.js";
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CaseTypes } from "../../../data/CaseTypes";
|
||||
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 { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
|
@ -31,7 +31,9 @@ export async function kickMember(
|
|||
|
||||
if (contactMethods.length) {
|
||||
if (config.kick_message) {
|
||||
const kickMessage = await renderTemplate(
|
||||
let kickMessage: string;
|
||||
try {
|
||||
kickMessage = await renderTemplate(
|
||||
config.kick_message,
|
||||
new TemplateSafeValueContainer({
|
||||
guildName: pluginData.guild.name,
|
||||
|
@ -41,6 +43,15 @@ export async function kickMember(
|
|||
: 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);
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GuildMember, Snowflake } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
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 { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { waitForButtonConfirm } from "../../../utils/waitForInteraction";
|
||||
|
@ -20,7 +20,9 @@ export async function warnMember(
|
|||
|
||||
let notifyResult: UserNotificationResult;
|
||||
if (config.warn_message) {
|
||||
const warnMessage = await renderTemplate(
|
||||
let warnMessage: string;
|
||||
try {
|
||||
warnMessage = await renderTemplate(
|
||||
config.warn_message,
|
||||
new TemplateSafeValueContainer({
|
||||
guildName: pluginData.guild.name,
|
||||
|
@ -30,6 +32,15 @@ export async function warnMember(
|
|||
: null,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
return {
|
||||
status: "failed",
|
||||
error: `Invalid warn_message format: ${err.message}`,
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
const contactMethods = warnOptions?.contactMethods
|
||||
? warnOptions.contactMethods
|
||||
: getDefaultContactMethods(pluginData, "warn");
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Case } from "../../../data/entities/Case";
|
|||
import { Mute } from "../../../data/entities/Mute";
|
||||
import { registerExpiringMute } from "../../../data/loops/expiringMutesLoop";
|
||||
import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import {
|
||||
UserNotificationMethod,
|
||||
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 config = await pluginData.config.getMatchingConfig({ member, userId });
|
||||
|
||||
const logs = pluginData.getPlugin(LogsPlugin);
|
||||
|
||||
let rolesToRestore: string[] = [];
|
||||
if (member) {
|
||||
const logs = pluginData.getPlugin(LogsPlugin);
|
||||
// remove and store any roles to be removed/restored
|
||||
const currentUserRoles = [...member.roles.cache.keys()];
|
||||
let newRoles: string[] = currentUserRoles;
|
||||
|
@ -187,7 +188,9 @@ export async function muteUser(
|
|||
? config.timed_mute_message
|
||||
: config.mute_message;
|
||||
|
||||
const muteMessage =
|
||||
let muteMessage: string | null = null;
|
||||
try {
|
||||
muteMessage =
|
||||
template &&
|
||||
(await renderTemplate(
|
||||
template,
|
||||
|
@ -200,6 +203,16 @@ export async function muteUser(
|
|||
: 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) {
|
||||
let contactMethods: UserNotificationMethod[] = [];
|
||||
|
|
Loading…
Add table
Reference in a new issue