From 77279c778d01416cd8d92a6f5c8c6e90d1c75e95 Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Sun, 22 Feb 2026 17:20:29 +0100 Subject: [PATCH] Switch to Components v2 --- src/commands/newAnnouncement.ts | 165 +++++++++++++++++++++-------- src/commands/updateAnnouncement.ts | 144 +++++++++++++++++-------- src/web/gitCommit.ts | 133 +++++++++++++++++------ 3 files changed, 321 insertions(+), 121 deletions(-) diff --git a/src/commands/newAnnouncement.ts b/src/commands/newAnnouncement.ts index b514d00..797a6c5 100644 --- a/src/commands/newAnnouncement.ts +++ b/src/commands/newAnnouncement.ts @@ -1,4 +1,18 @@ -import { Client, SlashCommandBuilder } from "discord.js"; +import { + ButtonBuilder, + ButtonStyle, + Client, + ContainerBuilder, + MediaGalleryBuilder, + MediaGalleryItemBuilder, + MessageFlags, + SectionBuilder, + SeparatorBuilder, + SeparatorSpacingSize, + SlashCommandBuilder, + TextDisplayBuilder, + ThumbnailBuilder, +} from "discord.js"; import { registerCommand } from "../lib/commandRegistry"; import { CHANNELS, ROLES } from "../config"; import { db } from ".."; @@ -89,8 +103,41 @@ export default async function (_client: Client) { // Post Process Content content = content.replace(/\\n/g, "\n"); // allow users to input \n for newlines - const fields = []; + // Build Components V2 container + const container = new ContainerBuilder().setAccentColor( + 0x5865f2, + ); + // Optional image as media gallery at the top + if (image) { + container.addMediaGalleryComponents( + new MediaGalleryBuilder().addItems( + new MediaGalleryItemBuilder().setURL(image.url), + ), + ); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + } + + // Title + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent(`## πŸ“’ ${title}`), + ); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + + // Content + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent(content), + ); + + // Changes if (changesRaw && changesRaw.trim() !== "") { const changeLines = changesRaw .split(",") @@ -98,22 +145,25 @@ export default async function (_client: Client) { .filter((c) => c.length > 0) .map((c) => `β€’ ${c}`) .join("\n"); - fields.push({ - name: "πŸ“‹ Changes", - value: changeLines, - inline: false, - }); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**πŸ“‹ Changes**\n${changeLines}`, + ), + ); } + // Releasing if (comingWhenRaw && comingWhenRaw.trim() !== "") { let epoch: number | null = null; const trimmed = comingWhenRaw.trim(); + let releasingValue: string; if (trimmed.toLowerCase() === "soon") { - fields.push({ - name: "πŸ“… Releasing", - value: "Soonβ„’", - inline: false, - }); + releasingValue = "Soonβ„’"; } else { // Try DD/MM/YYYY const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec( @@ -129,42 +179,67 @@ export default async function (_client: Client) { if (!isNaN(date.getTime())) epoch = Math.floor(date.getTime() / 1000); } else if (/^\d+$/.test(trimmed)) { - // Raw epoch epoch = Number(trimmed); } - if (epoch !== null) { - fields.push({ - name: "πŸ“… Releasing", - value: ` ()`, - inline: false, - }); - } else { - fields.push({ - name: "πŸ“… Releasing", - value: trimmed, - inline: false, - }); - } + releasingValue = + epoch !== null + ? ` ()` + : trimmed; } + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**πŸ“… Releasing**\n${releasingValue}`, + ), + ); } - fields.push({ - name: "πŸ”— More Info", - value: `[Click here](${link})`, - inline: false, - }); + // More Info button section + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent( + "πŸ”— **More Info**", + ), + ) + .setButtonAccessory( + new ButtonBuilder() + .setLabel("Click here") + .setURL(link) + .setStyle(ButtonStyle.Link), + ), + ); - const announcementEmbed = { - title: `πŸ“’ ${title}`, - description: content, - color: 0x5865f2, - fields, - footer: { - text: `Announced by ${interaction.user.username}`, - icon_url: interaction.user.displayAvatarURL(), - }, - timestamp: new Date().toISOString(), - }; + // Footer + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `-# Announced by ${interaction.user.username}`, + ), + ) + .setThumbnailAccessory( + new ThumbnailBuilder({ + media: { + url: interaction.user.displayAvatarURL(), + }, + }), + ), + ); // Send the announcement to a specific channel const announcementChannel = @@ -172,11 +247,9 @@ export default async function (_client: Client) { CHANNELS.ANNOUNCEMENTS, ); if (announcementChannel?.isTextBased()) { - if (image) { - await announcementChannel.send({ files: [image.url] }); - } const message = await announcementChannel.send({ - embeds: [announcementEmbed], + flags: MessageFlags.IsComponentsV2, + components: [container], }); // Reactions for engagement diff --git a/src/commands/updateAnnouncement.ts b/src/commands/updateAnnouncement.ts index 6a83205..5454290 100644 --- a/src/commands/updateAnnouncement.ts +++ b/src/commands/updateAnnouncement.ts @@ -1,4 +1,16 @@ -import { Client, SlashCommandBuilder } from "discord.js"; +import { + ButtonBuilder, + ButtonStyle, + Client, + ContainerBuilder, + MessageFlags, + SectionBuilder, + SeparatorBuilder, + SeparatorSpacingSize, + SlashCommandBuilder, + TextDisplayBuilder, + ThumbnailBuilder, +} from "discord.js"; import { registerCommand } from "../lib/commandRegistry"; import { CHANNELS, ROLES } from "../config"; import { db } from ".."; @@ -109,8 +121,25 @@ export default async function (_client: Client) { // Post Process Content newContent = newContent.replace(/\\n/g, "\n"); // allow users to input \n for newlines - const fields = []; + // Build Components V2 container + const container = new ContainerBuilder().setAccentColor(0x5865f2); + // Title + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent(`## πŸ“’ ${newTitle}`), + ); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + + // Content + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent(newContent), + ); + + // Changes if (changesRaw && changesRaw.trim() !== "") { const changeLines = changesRaw .split(",") @@ -118,22 +147,25 @@ export default async function (_client: Client) { .filter((c: string) => c.length > 0) .map((c: string) => `β€’ ${c}`) .join("\n"); - fields.push({ - name: "πŸ“‹ Changes", - value: changeLines, - inline: false, - }); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**πŸ“‹ Changes**\n${changeLines}`, + ), + ); } + // Releasing if (comingWhenRaw && comingWhenRaw.trim() !== "") { let epoch: number | null = null; const trimmed = comingWhenRaw.trim(); + let releasingValue: string; if (trimmed.toLowerCase() === "soon") { - fields.push({ - name: "πŸ“… Releasing", - value: "Soonβ„’", - inline: false, - }); + releasingValue = "Soonβ„’"; } else { const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec( trimmed, @@ -150,39 +182,64 @@ export default async function (_client: Client) { } else if (/^\d+$/.test(trimmed)) { epoch = Number(trimmed); } - if (epoch !== null) { - fields.push({ - name: "πŸ“… Releasing", - value: ` ()`, - inline: false, - }); - } else { - fields.push({ - name: "πŸ“… Releasing", - value: trimmed, - inline: false, - }); - } + releasingValue = + epoch !== null + ? ` ()` + : trimmed; } + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**πŸ“… Releasing**\n${releasingValue}`, + ), + ); } - fields.push({ - name: "πŸ”— More Info", - value: `[Click here](${link})`, - inline: false, - }); + // More Info button section + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent("πŸ”— **More Info**"), + ) + .setButtonAccessory( + new ButtonBuilder() + .setLabel("Click here") + .setURL(link) + .setStyle(ButtonStyle.Link), + ), + ); - const updatedEmbed = { - title: `πŸ“’ ${newTitle}`, - description: newContent, - color: 0x5865f2, - fields, - footer: { - text: `Announced by ${existing.announcedByUsername ?? interaction.user.username} β€’ Last edited by ${interaction.user.username}`, - icon_url: interaction.user.displayAvatarURL(), - }, - timestamp: new Date().toISOString(), - }; + // Footer + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `-# Announced by ${ + existing.announcedByUsername ?? + interaction.user.username + } β€’ Last edited by ${interaction.user.username}`, + ), + ) + .setThumbnailAccessory( + new ThumbnailBuilder({ + media: { url: interaction.user.displayAvatarURL() }, + }), + ), + ); // Fetch and edit the original message const announcementChannel = interaction.guild?.channels.cache.get( @@ -208,7 +265,10 @@ export default async function (_client: Client) { return; } - await targetMessage.edit({ embeds: [updatedEmbed] }); + await targetMessage.edit({ + flags: MessageFlags.IsComponentsV2, + components: [container], + }); // Update DB await db.collection("announcements").updateOne( diff --git a/src/web/gitCommit.ts b/src/web/gitCommit.ts index fe2e706..a3c6f4a 100644 --- a/src/web/gitCommit.ts +++ b/src/web/gitCommit.ts @@ -1,5 +1,16 @@ import { Express, Request, Response } from "express"; -import { EmbedBuilder, TextChannel } from "discord.js"; +import { + ButtonBuilder, + ButtonStyle, + ContainerBuilder, + MessageFlags, + SectionBuilder, + SeparatorBuilder, + SeparatorSpacingSize, + TextChannel, + TextDisplayBuilder, + ThumbnailBuilder, +} from "discord.js"; import { CHANNELS } from "../config"; import { client, db } from "../index"; @@ -17,12 +28,10 @@ export default async function gitCommitPOST(app: Express) { } if (event !== "push") { - return res - .status(200) - .json({ - success: true, - message: `Event '${event}' ignored`, - }); + return res.status(200).json({ + success: true, + message: `Event '${event}' ignored`, + }); } const body = req.body; @@ -51,32 +60,87 @@ export default async function gitCommitPOST(app: Express) { commitLines.push(`...and ${commits.length - SHOW_MAX} more`); } - const embed = new EmbedBuilder() - .setColor(forced ? 0xff4444 : 0x2ea44f) - .setTitle( - `${forced ? "⚠️ Force Push" : "πŸ“¦ New Push"} to \`${branch}\``, - ) - .setURL(compareUrl) - .setAuthor({ - name: pusher.name, - iconURL: `https://github.com/${pusher.name}.png`, - url: `https://github.com/${pusher.name}`, - }) - .addFields( - { name: "🌿 Branch", value: `\`${branch}\``, inline: true }, - { - name: `πŸ“ Commits (${commits.length})`, - value: commitLines.join("\n") || "_No commits_", - }, - ) - .setFooter({ - text: `Delivery: ${req.headers["x-github-delivery"] ?? "unknown"}`, - }) - .setTimestamp( - headCommit.timestamp - ? new Date(headCommit.timestamp) - : new Date(), + const container = new ContainerBuilder().setAccentColor( + forced ? 0xff4444 : 0x2ea44f, + ); + + // Author + title section + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `## ${forced ? "⚠️ Force Push" : "πŸ“¦ New Push"} to \`${branch}\``, + ), + ) + .setThumbnailAccessory( + new ThumbnailBuilder({ + media: { + url: `https://github.com/${pusher.name}.png`, + }, + }), + ), + ); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), + ); + + // Branch & commits + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**🌿 Branch:** \`${branch}\``, + ), + ); + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `**πŸ“ Commits (${commits.length})**\n${ + commitLines.join("\n") || "_No commits_" + }`, + ), + ); + + // Compare link button (only if a URL is available) + if (compareUrl) { + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(true) + .setSpacing(SeparatorSpacingSize.Small), ); + container.addSectionComponents( + new SectionBuilder() + .addTextDisplayComponents( + new TextDisplayBuilder().setContent( + "πŸ”— **View Changes**", + ), + ) + .setButtonAccessory( + new ButtonBuilder() + .setLabel("Compare") + .setURL(compareUrl) + .setStyle(ButtonStyle.Link), + ), + ); + } + + // Footer + container.addSeparatorComponents( + new SeparatorBuilder() + .setDivider(false) + .setSpacing(SeparatorSpacingSize.Small), + ); + container.addTextDisplayComponents( + new TextDisplayBuilder().setContent( + `-# Delivery: ${ + req.headers["x-github-delivery"] ?? "unknown" + }`, + ), + ); const channel = (await client.channels.fetch( configured_channel, @@ -90,7 +154,10 @@ export default async function gitCommitPOST(app: Express) { .json({ error: "Discord channel unavailable" }); } - const message = await channel.send({ embeds: [embed] }); + const message = await channel.send({ + flags: MessageFlags.IsComponentsV2, + components: [container], + }); console.log( `[WEB-gitCommit] Push event sent to configured channel (${commits.length} commits on ${branch})`, );