From 637fdcf997dd8eb875190fa70069fd6f3bd2700b Mon Sep 17 00:00:00 2001 From: Hikari Date: Fri, 20 Feb 2026 19:52:44 -0800 Subject: [PATCH] feat: update error notifications with ComponentsV2 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 --- src/modules/pipeLog.ts | 123 ++++++++++++++++++++++++++++++++++---- src/server/serve.ts | 12 ++-- src/utils/errorHandler.ts | 16 ++++- 3 files changed, 133 insertions(+), 18 deletions(-) diff --git a/src/modules/pipeLog.ts b/src/modules/pipeLog.ts index 5c201a6..b00cb74 100644 --- a/src/modules/pipeLog.ts +++ b/src/modules/pipeLog.ts @@ -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 => { + 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 * Naomi in Discord. - * @param appName - The name of the application. - * @param message - The message to log. - * @param level - The level of the log, used for priority. + * @param options - The error details including app name, level, message, scope, and stack trace. */ -const pipeError = async( - appName: string, - message: string, - level: string, -): Promise => { +const pipeError = async(options: PipeErrorOptions): Promise => { + const { appName, level, message, scope, stack } = options; const logToken = process.env.ERROR_LOG_TOKEN; if (logToken === undefined) { return; @@ -65,10 +164,10 @@ const pipeError = async( }, method: "POST", }); - await fetch(`https://discord.com/api/v10/channels/1385797320389431336/messages`, { - body: JSON.stringify({ - content: `:warning: **${appName}** has encountered an error. <@465650873650118659> please check the logs.`, - }), + await fetch(`https://discord.com/api/v10/channels/1474606829504954511/messages`, { + body: JSON.stringify( + createDiscordErrorPayload({ appName, message, scope, stack }), + ), headers: { // eslint-disable-next-line @typescript-eslint/naming-convention -- Standard header. "Authorization": `Bot ${process.env.DISCORD_TOKEN ?? ""}`, diff --git a/src/server/serve.ts b/src/server/serve.ts index 20f2aae..42d2ecf 100644 --- a/src/server/serve.ts +++ b/src/server/serve.ts @@ -154,11 +154,13 @@ export const instantiateServer = async(): Promise => { `[ERROR]: ${context} - ${application}`, `${message}\n\n${stack}`, ); - await pipeError( - application, - `${context} - ${message}\n${stack}`, - "error", - ); + await pipeError({ + appName: application, + level: "error", + message: message, + scope: context, + stack: stack, + }); await response.status(200).send({ success: true }); } catch (error) { await errorHandler(error, "Error Webhook"); diff --git a/src/utils/errorHandler.ts b/src/utils/errorHandler.ts index d50ebdf..37f038e 100644 --- a/src/utils/errorHandler.ts +++ b/src/utils/errorHandler.ts @@ -4,7 +4,7 @@ * @author Naomi Carrigan */ -import { pipeLog } from "../modules/pipeLog.js"; +import { pipeError, pipeLog } from "../modules/pipeLog.js"; import { sendMail } from "../modules/sendMail.js"; /** @@ -22,6 +22,13 @@ export const errorHandler = async( `[ERROR] ${context}: ${error.message}`, "error", ); + await pipeError({ + appName: "Rosalia Nightsong", + level: "error", + message: error.message, + scope: context, + stack: error.stack ?? "No stack trace available", + }); await sendMail( `[ERROR] ${context}: ${error.message}`, JSON.stringify(error, null, 2), @@ -33,5 +40,12 @@ export const errorHandler = async( `[ERROR] ${context}: ${JSON.stringify(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)); };