From 25771f2527d37273273dfe43e8857af377ef2038 Mon Sep 17 00:00:00 2001 From: seeyebe Date: Wed, 11 Jun 2025 15:30:06 +0300 Subject: [PATCH] feat: add embed support to welcome messages --- .../events/SendWelcomeMessageEvt.ts | 78 ++++++++++++++----- backend/src/plugins/WelcomeMessage/types.ts | 3 +- backend/src/utils/sendDM.ts | 27 +++++-- 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index df983f0e..0c718e57 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -1,6 +1,12 @@ -import { Snowflake, TextChannel } from "discord.js"; +import { MessageCreateOptions, PermissionsBitField, Snowflake, TextChannel } from "discord.js"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter.js"; -import { createChunkedMessage, verboseChannelMention, verboseUserMention } from "../../../utils.js"; +import { + createChunkedMessage, + renderRecursively, + verboseChannelMention, + verboseUserMention +} from "../../../utils.js"; +import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions.js"; import { sendDM } from "../../../utils/sendDM.js"; import { guildToTemplateSafeGuild, @@ -28,17 +34,20 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ pluginData.state.sentWelcomeMessages.add(member.id); - let formatted; + const templateValues = new TemplateSafeValueContainer({ + member: memberToTemplateSafeMember(member), + user: userToTemplateSafeUser(member.user), + guild: guildToTemplateSafeGuild(member.guild), + }); + + const renderMessageText = (str: string) => renderTemplate(str, templateValues); + + let formatted: string | MessageCreateOptions; try { - formatted = await renderTemplate( - config.message, - new TemplateSafeValueContainer({ - member: memberToTemplateSafeMember(member), - user: userToTemplateSafeUser(member.user), - guild: guildToTemplateSafeGuild(member.guild), - }), - ); + formatted = typeof config.message === "string" + ? await renderMessageText(config.message) + : ((await renderRecursively(config.message, renderMessageText)) as MessageCreateOptions); } catch (e) { if (e instanceof TemplateParseError) { pluginData.getPlugin(LogsPlugin).logBotAlert({ @@ -46,7 +55,6 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ }); return; } - throw e; } @@ -65,17 +73,49 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ const channel = meta.args.member.guild.channels.cache.get(config.send_to_channel as Snowflake); if (!channel || !(channel instanceof TextChannel)) return; - try { - await createChunkedMessage(channel, formatted, { - parse: ["users"], + if ( + !hasDiscordPermissions( + channel.permissionsFor(pluginData.client.user!.id), + PermissionsBitField.Flags.SendMessages | PermissionsBitField.Flags.ViewChannel, + ) + ) { + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Missing permissions to send welcome message in ${verboseChannelMention(channel)}`, }); + return; + } + + if ( + typeof formatted === "object" && formatted.embeds && formatted.embeds.length > 0 && + !hasDiscordPermissions( + channel.permissionsFor(pluginData.client.user!.id), + PermissionsBitField.Flags.EmbedLinks, + ) + ) { + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Missing permissions to send welcome message **with embeds** in ${verboseChannelMention(channel)}`, + }); + return; + } + + try { + if (typeof formatted === "string") { + await createChunkedMessage(channel, formatted, { + parse: ["users"], + }); + } else { + await channel.send({ + ...formatted, + allowedMentions: { + parse: ["users"], + }, + }); + } } catch { pluginData.getPlugin(LogsPlugin).logBotAlert({ - body: `Failed send a welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention( - channel, - )}`, + body: `Failed to send welcome message for ${verboseUserMention(member.user)} to ${verboseChannelMention(channel)}`, }); } } }, -}); +}); \ No newline at end of file diff --git a/backend/src/plugins/WelcomeMessage/types.ts b/backend/src/plugins/WelcomeMessage/types.ts index a75062e5..bec914c2 100644 --- a/backend/src/plugins/WelcomeMessage/types.ts +++ b/backend/src/plugins/WelcomeMessage/types.ts @@ -1,11 +1,12 @@ import { BasePluginType, guildPluginEventListener } from "knub"; import z from "zod/v4"; import { GuildLogs } from "../../data/GuildLogs.js"; +import { zMessageContent } from "../../utils.js"; export const zWelcomeMessageConfig = z.strictObject({ send_dm: z.boolean().default(false), send_to_channel: z.string().nullable().default(null), - message: z.string().nullable().default(null), + message: zMessageContent.nullable().default(null), }); export interface WelcomeMessagePluginType extends BasePluginType { diff --git a/backend/src/utils/sendDM.ts b/backend/src/utils/sendDM.ts index f1ca6fc6..fff1355c 100644 --- a/backend/src/utils/sendDM.ts +++ b/backend/src/utils/sendDM.ts @@ -1,4 +1,4 @@ -import { MessagePayload, User } from "discord.js"; +import { MessagePayload, User, MessageCreateOptions } from "discord.js"; import { logger } from "../logger.js"; import { HOURS, createChunkedMessage, isDiscordAPIError } from "../utils.js"; import Timeout = NodeJS.Timeout; @@ -16,7 +16,11 @@ export class DMError extends Error {} const error20026 = "The bot cannot currently send DMs"; -export async function sendDM(user: User, content: string | MessagePayload, source: string) { +export async function sendDM( + user: User, + content: string | MessagePayload | MessageCreateOptions, + source: string +) { if (dmsDisabled) { throw new DMError(error20026); } @@ -26,8 +30,22 @@ export async function sendDM(user: User, content: string | MessagePayload, sourc try { if (typeof content === "string") { await createChunkedMessage(user, content); - } else { + } else if (content instanceof MessagePayload) { await user.send(content); + } else { + if (content.embeds && content.embeds.length > 0) { + await user.send({ + ...content, + allowedMentions: { + parse: ["users"], + ...content.allowedMentions + } + }); + } else if (content.content) { + await createChunkedMessage(user, content.content, content.allowedMentions); + } else { + await user.send(content); + } } } catch (e) { if (isDiscordAPIError(e) && e.code === 20026) { @@ -36,7 +54,6 @@ export async function sendDM(user: User, content: string | MessagePayload, sourc disableDMs(1 * HOURS); throw new DMError(error20026); } - throw e; } -} +} \ No newline at end of file