feat: Implement welcome messages for new enrollments using a new webhook utility and refactor the admin webhook command to utilize it.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, PermissionFlagsBits, TextChannel, NewsChannel, VoiceChannel, MessageFlags } from "discord.js";
|
import { SlashCommandBuilder, PermissionFlagsBits, TextChannel, NewsChannel, VoiceChannel, MessageFlags } from "discord.js";
|
||||||
import { createErrorEmbed } from "@/lib/embeds";
|
import { createErrorEmbed } from "@/lib/embeds";
|
||||||
|
import { sendWebhookMessage } from "@/lib/webhookUtils";
|
||||||
|
|
||||||
export const webhook = createCommand({
|
export const webhook = createCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
@@ -36,37 +37,17 @@ export const webhook = createCommand({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let webhook;
|
|
||||||
try {
|
try {
|
||||||
webhook = await channel.createWebhook({
|
await sendWebhookMessage(
|
||||||
name: `${interaction.client.user.username} - Proxy`,
|
channel,
|
||||||
avatar: interaction.client.user.displayAvatarURL(),
|
payload,
|
||||||
reason: `Proxy message requested by ${interaction.user.tag}`
|
interaction.client.user,
|
||||||
});
|
`Proxy message requested by ${interaction.user.tag}`
|
||||||
|
);
|
||||||
|
|
||||||
// Support snake_case keys for raw API compatibility
|
|
||||||
if (payload.avatar_url && !payload.avatarURL) {
|
|
||||||
payload.avatarURL = payload.avatar_url;
|
|
||||||
delete payload.avatar_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
await webhook.send(payload);
|
|
||||||
|
|
||||||
await webhook.delete("Proxy message sent");
|
|
||||||
|
|
||||||
await interaction.editReply({ content: "Message sent successfully!" });
|
await interaction.editReply({ content: "Message sent successfully!" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Webhook error:", error);
|
console.error("Webhook error:", error);
|
||||||
// Attempt cleanup if webhook was created but sending failed
|
|
||||||
if (webhook) {
|
|
||||||
try {
|
|
||||||
await webhook.delete("Cleanup after failure");
|
|
||||||
} catch (cleanupError) {
|
|
||||||
console.error("Failed to delete webhook during cleanup:", cleanupError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [createErrorEmbed("Failed to send message via webhook. Ensure the bot has 'Manage Webhooks' permission and the payload is valid.", "Delivery Failed")]
|
embeds: [createErrorEmbed("Failed to send message via webhook. Ensure the bot has 'Manage Webhooks' permission and the payload is valid.", "Delivery Failed")]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export interface GameConfigType {
|
|||||||
};
|
};
|
||||||
studentRole: string;
|
studentRole: string;
|
||||||
visitorRole: string;
|
visitorRole: string;
|
||||||
|
welcomeChannelId?: string;
|
||||||
|
welcomeMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial default config state
|
// Initial default config state
|
||||||
@@ -106,7 +108,9 @@ const configSchema = z.object({
|
|||||||
|
|
||||||
}),
|
}),
|
||||||
studentRole: z.string(),
|
studentRole: z.string(),
|
||||||
visitorRole: z.string()
|
visitorRole: z.string(),
|
||||||
|
welcomeChannelId: z.string().optional(),
|
||||||
|
welcomeMessage: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export function reloadConfig() {
|
export function reloadConfig() {
|
||||||
@@ -139,6 +143,8 @@ export function reloadConfig() {
|
|||||||
config.lootdrop = rawConfig.lootdrop;
|
config.lootdrop = rawConfig.lootdrop;
|
||||||
config.studentRole = rawConfig.studentRole;
|
config.studentRole = rawConfig.studentRole;
|
||||||
config.visitorRole = rawConfig.visitorRole;
|
config.visitorRole = rawConfig.visitorRole;
|
||||||
|
config.welcomeChannelId = rawConfig.welcomeChannelId;
|
||||||
|
config.welcomeMessage = rawConfig.welcomeMessage;
|
||||||
|
|
||||||
console.log("🔄 Config reloaded from disk.");
|
console.log("🔄 Config reloaded from disk.");
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/lib/webhookUtils.ts
Normal file
56
src/lib/webhookUtils.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { type TextBasedChannel, User, Client } from 'discord.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to a channel using a temporary webhook (imitating the bot or custom persona).
|
||||||
|
*
|
||||||
|
* @param channel The channel to send the message to (must support webhooks).
|
||||||
|
* @param payload The message payload (string content or JSON object for embeds/options).
|
||||||
|
* @param clientUser The client user (bot) to fallback for avatar/name if not specified in payload.
|
||||||
|
* @param reason The reason for creating the webhook (for audit logs).
|
||||||
|
*/
|
||||||
|
export async function sendWebhookMessage(
|
||||||
|
channel: TextBasedChannel,
|
||||||
|
payload: any,
|
||||||
|
clientUser: User,
|
||||||
|
reason: string
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
if (!('createWebhook' in channel)) {
|
||||||
|
throw new Error("Channel does not support webhooks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize payload if it's just a string, wrap it in content
|
||||||
|
if (typeof payload === 'string') {
|
||||||
|
payload = { content: payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
let webhook;
|
||||||
|
try {
|
||||||
|
webhook = await channel.createWebhook({
|
||||||
|
name: payload.username || `${clientUser.username}`, // Use payload name or bot name
|
||||||
|
avatar: payload.avatar_url || payload.avatarURL || clientUser.displayAvatarURL(),
|
||||||
|
reason: reason
|
||||||
|
});
|
||||||
|
|
||||||
|
// Support snake_case keys for raw API compatibility if passed from config
|
||||||
|
if (payload.avatar_url && !payload.avatarURL) {
|
||||||
|
payload.avatarURL = payload.avatar_url;
|
||||||
|
delete payload.avatar_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
await webhook.send(payload);
|
||||||
|
|
||||||
|
await webhook.delete(reason);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Attempt cleanup if webhook was created but sending failed
|
||||||
|
if (webhook) {
|
||||||
|
try {
|
||||||
|
await webhook.delete("Cleanup after failure");
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.error("Failed to delete webhook during cleanup:", cleanupError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { config } from "@/lib/config";
|
|||||||
import { createErrorEmbed } from "@/lib/embeds";
|
import { createErrorEmbed } from "@/lib/embeds";
|
||||||
import { classService } from "@modules/class/class.service";
|
import { classService } from "@modules/class/class.service";
|
||||||
import { userService } from "@modules/user/user.service";
|
import { userService } from "@modules/user/user.service";
|
||||||
|
import { sendWebhookMessage } from "@/lib/webhookUtils";
|
||||||
|
|
||||||
export async function handleEnrollmentInteraction(interaction: ButtonInteraction) {
|
export async function handleEnrollmentInteraction(interaction: ButtonInteraction) {
|
||||||
if (!interaction.inCachedGuild()) {
|
if (!interaction.inCachedGuild()) {
|
||||||
@@ -84,6 +85,31 @@ export async function handleEnrollmentInteraction(interaction: ButtonInteraction
|
|||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 5. Send Welcome Message (if configured)
|
||||||
|
if (config.welcomeChannelId) {
|
||||||
|
const welcomeChannel = interaction.guild.channels.cache.get(config.welcomeChannelId);
|
||||||
|
if (welcomeChannel && welcomeChannel.isTextBased()) {
|
||||||
|
const rawMessage = config.welcomeMessage || "Welcome to Aurora, {user}! You have been enrolled as a **{class}**.";
|
||||||
|
|
||||||
|
const processedMessage = rawMessage
|
||||||
|
.replace(/{user}/g, member.toString())
|
||||||
|
.replace(/{username}/g, member.user.username)
|
||||||
|
.replace(/{class}/g, selectedClass.name)
|
||||||
|
.replace(/{guild}/g, interaction.guild.name);
|
||||||
|
|
||||||
|
let payload;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(processedMessage);
|
||||||
|
} catch {
|
||||||
|
payload = processedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire and forget webhook
|
||||||
|
sendWebhookMessage(welcomeChannel, payload, interaction.client.user, "New Student Enrollment")
|
||||||
|
.catch((err: any) => console.error("Failed to send welcome message:", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Enrollment error:", error);
|
console.error("Enrollment error:", error);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
|
|||||||
Reference in New Issue
Block a user