From 088f74e5591790c96fca896044705ff6a8121530 Mon Sep 17 00:00:00 2001 From: Hikari Date: Tue, 31 Mar 2026 16:50:27 -0700 Subject: [PATCH] feat: include resource links in sanction DMs Adds appeal form, sanction logs, contact page, and community rejoin link to all sanction DM notifications, separated from the sanction details using a Components v2 separator. --- src/commands/ban.ts | 6 ++++-- src/commands/kick.ts | 6 ++++-- src/commands/mute.ts | 6 ++++-- src/commands/softban.ts | 6 ++++-- src/commands/warn.ts | 6 ++++-- src/utils/components.ts | 21 +++++++++++++++++++++ 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/commands/ban.ts b/src/commands/ban.ts index 55e038e..e4ff333 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { logModerationAction } from "../modules/logModAction.js"; import { sendSanction } from "../modules/sendSanction.js"; -import { errorReply, successReply } from "../utils/components.js"; +import { errorReply, sanctionDmMessage, successReply } from "../utils/components.js"; import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; @@ -103,7 +103,9 @@ const banCommand: Command = { try { await target.send( - `You have been banned from **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + sanctionDmMessage( + `You have been banned from **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + ), ); } catch { // DMs may be closed; continue without failing the command. diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 2f65560..b252533 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { logModerationAction } from "../modules/logModAction.js"; import { sendSanction } from "../modules/sendSanction.js"; -import { errorReply, successReply } from "../utils/components.js"; +import { errorReply, sanctionDmMessage, successReply } from "../utils/components.js"; import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; @@ -98,7 +98,9 @@ const kickCommand: Command = { try { await target.send( - `You have been kicked from **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + sanctionDmMessage( + `You have been kicked from **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + ), ); } catch { // DMs may be closed; continue without failing the command. diff --git a/src/commands/mute.ts b/src/commands/mute.ts index 4e78a3b..eda88a7 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { logModerationAction } from "../modules/logModAction.js"; import { sendSanction } from "../modules/sendSanction.js"; -import { errorReply, successReply } from "../utils/components.js"; +import { errorReply, sanctionDmMessage, successReply } from "../utils/components.js"; import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; @@ -155,7 +155,9 @@ const muteCommand: Command = { try { await target.send( - `You have been muted in **${interaction.guild?.name ?? "the server"}** for **${durationLabel}**.\n**Reason:** ${reason}`, + sanctionDmMessage( + `You have been muted in **${interaction.guild?.name ?? "the server"}** for **${durationLabel}**.\n**Reason:** ${reason}`, + ), ); } catch { // DMs may be closed; continue without failing the command. diff --git a/src/commands/softban.ts b/src/commands/softban.ts index bd86836..221f97c 100644 --- a/src/commands/softban.ts +++ b/src/commands/softban.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { logModerationAction } from "../modules/logModAction.js"; import { sendSanction } from "../modules/sendSanction.js"; -import { errorReply, successReply } from "../utils/components.js"; +import { errorReply, sanctionDmMessage, successReply } from "../utils/components.js"; import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; @@ -94,7 +94,9 @@ const softbanCommand: Command = { try { await target.send( - `You have been softbanned from **${interaction.guild?.name ?? "the server"}** (your recent messages have been removed).\n**Reason:** ${reason}`, + sanctionDmMessage( + `You have been softbanned from **${interaction.guild?.name ?? "the server"}** (your recent messages have been removed).\n**Reason:** ${reason}`, + ), ); } catch { // DMs may be closed; continue without failing the command. diff --git a/src/commands/warn.ts b/src/commands/warn.ts index 5aafd87..70f3df5 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { logModerationAction } from "../modules/logModAction.js"; import { sendSanction } from "../modules/sendSanction.js"; -import { errorReply, successReply } from "../utils/components.js"; +import { errorReply, sanctionDmMessage, successReply } from "../utils/components.js"; import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; @@ -99,7 +99,9 @@ const warnCommand: Command = { try { await target.send( - `You have received a warning in **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + sanctionDmMessage( + `You have received a warning in **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`, + ), ); } catch { // DMs may be closed; continue without failing the command. diff --git a/src/utils/components.ts b/src/utils/components.ts index 15efeb9..767ed38 100644 --- a/src/utils/components.ts +++ b/src/utils/components.ts @@ -173,10 +173,31 @@ const memberMessage = ( }; }; +const sanctionLinks = [ + "**Appeal this sanction:** https://forms.nhcarrigan.com/o/docs/forms/4w5VHsYiEkiS2mewvtuJYL/4", + "**View sanction logs:** https://hikari.nhcarrigan.com/sanctions", + "**Contact us:** https://docs.nhcarrigan.com/about/contact/", + "**Rejoin our community:** https://chat.nhcarrigan.com", +].join("\n"); + +/** + * Builds a Components v2 DM payload for sanction notifications. + * Includes a separator between the sanction details and the resource links. + * @param sanctionText - The formatted sanction message (action + reason). + * @returns A Discord message payload object. + */ +const sanctionDmMessage = (sanctionText: string): Record => { + return { + components: [ buildContainer("modAction", sanctionText, sanctionLinks) ], + flags: MessageFlags.IsComponentsV2, + }; +}; + export { activityMessage, errorReply, memberMessage, modLogMessage, + sanctionDmMessage, successReply, };