From 401d1220e1a5c2f8c37f2147cc392e0cd783b76f Mon Sep 17 00:00:00 2001 From: Fuwn Date: Wed, 24 Sep 2025 02:33:56 -0700 Subject: feat: Appeal slash command --- src/discord/commands.ts | 13 +++++++ src/discord/embeds.ts | 35 ++++++++++++++++++ src/register.ts | 2 ++ src/server.ts | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) diff --git a/src/discord/commands.ts b/src/discord/commands.ts index 652a20a..5f25097 100644 --- a/src/discord/commands.ts +++ b/src/discord/commands.ts @@ -71,6 +71,19 @@ export const COMPLAIN_COMMAND: DiscordCommand = { ], }; +export const APPEAL_COMMAND: DiscordCommand = { + name: "appeal", + description: "Submit an appeal to the moderators", + options: [ + { + type: 3, + name: "message", + description: "Your appeal message", + required: true, + }, + ], +}; + export const COLOURS_COMMAND: DiscordCommand = { name: "colours", description: "Show the distribution of colour roles in the server", diff --git a/src/discord/embeds.ts b/src/discord/embeds.ts index 2eeb800..6873617 100644 --- a/src/discord/embeds.ts +++ b/src/discord/embeds.ts @@ -146,3 +146,38 @@ export const createComplaintEmbed = ( }, }; }; + +export const createAppealEmbed = ( + appealContent: string, + appellant: { username: string; id: string; avatar?: string }, + timestamp: number, + isDM: boolean = true, +): DiscordEmbed => { + return { + title: "📋 New Appeal", + description: appealContent, + color: 0x5865f2, + fields: [ + { + name: "Appellant", + value: `${appellant.username} (${appellant.id})`, + inline: true, + }, + { + name: "Timestamp", + value: ``, + inline: true, + }, + ], + thumbnail: appellant.avatar + ? { + url: `https://cdn.discordapp.com/avatars/${appellant.id}/${appellant.avatar}.png?size=256`, + } + : undefined, + footer: { + text: isDM + ? "Appeal submitted via DM" + : "Appeal submitted from server", + }, + }; +}; diff --git a/src/register.ts b/src/register.ts index 4b5567d..a23c9c8 100644 --- a/src/register.ts +++ b/src/register.ts @@ -4,6 +4,7 @@ import { ROLEPLAY_COMMAND, TOP_COMMAND, COMPLAIN_COMMAND, + APPEAL_COMMAND, COLOURS_COMMAND, ROLEPLAY_SERIOUS_COMMAND, type DiscordCommand, @@ -32,6 +33,7 @@ const commands: DiscordCommand[] = [ NSFW_COMMAND, TOP_COMMAND, COMPLAIN_COMMAND, + APPEAL_COMMAND, COLOURS_COMMAND, ROLEPLAY_SERIOUS_COMMAND, ]; diff --git a/src/server.ts b/src/server.ts index dc7f2cc..990c67c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,7 @@ import { NSFW_COMMAND, TOP_COMMAND, COMPLAIN_COMMAND, + APPEAL_COMMAND, COLOURS_COMMAND, ROLEPLAY_SERIOUS_COMMAND, } from "./discord/commands.ts"; @@ -20,6 +21,7 @@ import type { Environment, DiscordEmbed } from "./discord/interfaces.ts"; import { createPostEmbed, createComplaintEmbed, + createAppealEmbed, createRoleDistributionEmbed, } from "./discord/embeds.ts"; import { JSONResponse } from "./discord/responses.ts"; @@ -27,6 +29,7 @@ import { verifyDiscordRequest } from "./discord/verification.ts"; const router = AutoRouter(); const COMPLAINT_CHANNEL_ID = "1415868433714778204"; +const APPEAL_CHANNEL_ID = "1420340807931531385"; const SERIOUS_ROLEPLAY_ROLE_ID = "1418311833303122021"; const ROLE_MANAGER_ROLE_ID = "1410993207608873070"; const GUILD_ID = "1406422617724026901"; @@ -86,6 +89,32 @@ const sendComplaintToChannel = async ( } }; +const sendAppealToChannel = async ( + environment: Environment, + embed: DiscordEmbed, +): Promise => { + const url = `https://discord.com/api/v10/channels/${APPEAL_CHANNEL_ID}/messages`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bot ${environment.DISCORD_TOKEN}`, + }, + body: JSON.stringify({ + embeds: [embed], + }), + }); + + return response.ok; + } catch (error) { + console.error("Error sending appeal to channel:", error); + + return false; + } +}; + const fetchRoleDistribution = async ( environment: Environment, guildID: string, @@ -416,6 +445,73 @@ router.post("/", async (request: Request, environment: Environment) => { } } + case APPEAL_COMMAND.name.toLowerCase(): { + try { + const appealMessage = interaction.data.options?.[0] + ?.value as string; + + if (!appealMessage) + return new JSONResponse({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "❌ Please provide a message for your appeal.", + flags: 64, + }, + }); + + const appellant = { + username: + interaction.member?.user?.username || + interaction.user?.username || + "Unknown", + id: + interaction.member?.user?.id || interaction.user?.id || "Unknown", + avatar: + interaction.member?.user?.avatar || interaction.user?.avatar, + }; + const isDM = !interaction.guild_id; + const appealEmbed = createAppealEmbed( + appealMessage, + appellant, + Date.now(), + isDM, + ); + const success = await sendAppealToChannel( + environment, + appealEmbed, + ); + + if (success) { + return new JSONResponse({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "✅ Your appeal has been submitted successfully! A moderator will follow up with you soon.", + flags: 64, + }, + }); + } else { + return new JSONResponse({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: + "❌ Failed to submit your appeal. Please try again later.", + flags: 64, + }, + }); + } + } catch (error) { + console.error("Error in appeal command:", error); + + return new JSONResponse({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "❌ An error occurred while processing your appeal.", + flags: 64, + }, + }); + } + } + case COLOURS_COMMAND.name.toLowerCase(): { try { if (!interaction.guild_id) -- cgit v1.2.3