feat: update error notifications with ComponentsV2
Node.js CI / CI (pull_request) Failing after 10s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m2s

Updated Discord error notifications to use Components V2 with a beautiful
container layout. Changes include:

- Removed @mention from error notifications
- Updated channel ID to 1474606829504954511
- Implemented Container component (type 17) with Text Display components (type 10)
- Added accent color (#E91E63) and separators for visual appeal
- Organized error information with headers for scope, message, and stack trace
- Refactored pipeError to use options object for better code organization
This commit is contained in:
2026-02-20 19:52:44 -08:00
committed by Naomi Carrigan
parent 46aa662659
commit 637fdcf997
3 changed files with 133 additions and 18 deletions
+111 -12
View File
@@ -35,18 +35,117 @@ const pipeLog = async(
}); });
}; };
/**
* Options for the pipeError function.
*/
interface PipeErrorOptions {
/**
* The name of the application.
*/
appName: string;
/**
* The level of the log, used for priority.
*/
level: string;
/**
* The message to log.
*/
message: string;
/**
* The context in which the error occurred.
*/
scope: string;
/**
* The error stack trace.
*/
stack: string;
}
/**
* Options for creating the Discord error payload.
*/
interface DiscordErrorPayloadOptions {
/**
* The name of the application.
*/
appName: string;
/**
* The error message.
*/
message: string;
/**
* The context in which the error occurred.
*/
scope: string;
/**
* The error stack trace.
*/
stack: string;
}
/**
* Creates a Discord ComponentsV2 payload with a container layout
* for beautiful error notifications.
* @param options - The error details for the payload.
* @returns The Discord message payload.
*/
const createDiscordErrorPayload = (
options: DiscordErrorPayloadOptions,
): Record<string, unknown> => {
const { appName, message, scope, stack } = options;
return {
components: [
{
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API property.
accent_color: 15_277_667,
components: [
{
content: `# Error in ${appName}`,
type: 10,
},
{
divider: true,
spacing: 1,
type: 14,
},
{
content: `## Scope\n\n${scope}`,
type: 10,
},
{
divider: true,
spacing: 1,
type: 14,
},
{
content: `## Error Details\n\n### Message\n\n\`\`\`\n${message}\n\`\`\`\n\n### Stack\n\n\`\`\`\n${stack}\n\`\`\``,
type: 10,
},
],
spoiler: false,
type: 17,
},
],
flags: 32_768,
};
};
/** /**
* Pipes a log message to the Gotify server. Also notifies * Pipes a log message to the Gotify server. Also notifies
* Naomi in Discord. * Naomi in Discord.
* @param appName - The name of the application. * @param options - The error details including app name, level, message, scope, and stack trace.
* @param message - The message to log.
* @param level - The level of the log, used for priority.
*/ */
const pipeError = async( const pipeError = async(options: PipeErrorOptions): Promise<void> => {
appName: string, const { appName, level, message, scope, stack } = options;
message: string,
level: string,
): Promise<void> => {
const logToken = process.env.ERROR_LOG_TOKEN; const logToken = process.env.ERROR_LOG_TOKEN;
if (logToken === undefined) { if (logToken === undefined) {
return; return;
@@ -65,10 +164,10 @@ const pipeError = async(
}, },
method: "POST", method: "POST",
}); });
await fetch(`https://discord.com/api/v10/channels/1385797320389431336/messages`, { await fetch(`https://discord.com/api/v10/channels/1474606829504954511/messages`, {
body: JSON.stringify({ body: JSON.stringify(
content: `:warning: **${appName}** has encountered an error. <@465650873650118659> please check the logs.`, createDiscordErrorPayload({ appName, message, scope, stack }),
}), ),
headers: { headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention -- Standard header. // eslint-disable-next-line @typescript-eslint/naming-convention -- Standard header.
"Authorization": `Bot ${process.env.DISCORD_TOKEN ?? ""}`, "Authorization": `Bot ${process.env.DISCORD_TOKEN ?? ""}`,
+7 -5
View File
@@ -154,11 +154,13 @@ export const instantiateServer = async(): Promise<void> => {
`[ERROR]: ${context} - ${application}`, `[ERROR]: ${context} - ${application}`,
`${message}\n\n${stack}`, `${message}\n\n${stack}`,
); );
await pipeError( await pipeError({
application, appName: application,
`${context} - ${message}\n${stack}`, level: "error",
"error", message: message,
); scope: context,
stack: stack,
});
await response.status(200).send({ success: true }); await response.status(200).send({ success: true });
} catch (error) { } catch (error) {
await errorHandler(error, "Error Webhook"); await errorHandler(error, "Error Webhook");
+15 -1
View File
@@ -4,7 +4,7 @@
* @author Naomi Carrigan * @author Naomi Carrigan
*/ */
import { pipeLog } from "../modules/pipeLog.js"; import { pipeError, pipeLog } from "../modules/pipeLog.js";
import { sendMail } from "../modules/sendMail.js"; import { sendMail } from "../modules/sendMail.js";
/** /**
@@ -22,6 +22,13 @@ export const errorHandler = async(
`[ERROR] ${context}: ${error.message}`, `[ERROR] ${context}: ${error.message}`,
"error", "error",
); );
await pipeError({
appName: "Rosalia Nightsong",
level: "error",
message: error.message,
scope: context,
stack: error.stack ?? "No stack trace available",
});
await sendMail( await sendMail(
`[ERROR] ${context}: ${error.message}`, `[ERROR] ${context}: ${error.message}`,
JSON.stringify(error, null, 2), JSON.stringify(error, null, 2),
@@ -33,5 +40,12 @@ export const errorHandler = async(
`[ERROR] ${context}: ${JSON.stringify(error)}`, `[ERROR] ${context}: ${JSON.stringify(error)}`,
"error", "error",
); );
await pipeError({
appName: "Rosalia Nightsong",
level: "error",
message: JSON.stringify(error),
scope: context,
stack: "No stack trace available (not an Error instance)",
});
await sendMail(`[ERROR] ${context}`, JSON.stringify(error, null, 2)); await sendMail(`[ERROR] ${context}`, JSON.stringify(error, null, 2));
}; };