Switch to Components v2
All checks were successful
CI / build (push) Successful in 11s

This commit is contained in:
Space-Banane
2026-02-22 17:20:29 +01:00
parent 7ea4a718cf
commit 77279c778d
3 changed files with 321 additions and 121 deletions

View File

@@ -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 { registerCommand } from "../lib/commandRegistry";
import { CHANNELS, ROLES } from "../config"; import { CHANNELS, ROLES } from "../config";
import { db } from ".."; import { db } from "..";
@@ -89,8 +103,41 @@ export default async function (_client: Client) {
// Post Process Content // Post Process Content
content = content.replace(/\\n/g, "\n"); // allow users to input \n for newlines 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() !== "") { if (changesRaw && changesRaw.trim() !== "") {
const changeLines = changesRaw const changeLines = changesRaw
.split(",") .split(",")
@@ -98,22 +145,25 @@ export default async function (_client: Client) {
.filter((c) => c.length > 0) .filter((c) => c.length > 0)
.map((c) => `${c}`) .map((c) => `${c}`)
.join("\n"); .join("\n");
fields.push({ container.addSeparatorComponents(
name: "📋 Changes", new SeparatorBuilder()
value: changeLines, .setDivider(false)
inline: false, .setSpacing(SeparatorSpacingSize.Small),
}); );
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`**📋 Changes**\n${changeLines}`,
),
);
} }
// Releasing
if (comingWhenRaw && comingWhenRaw.trim() !== "") { if (comingWhenRaw && comingWhenRaw.trim() !== "") {
let epoch: number | null = null; let epoch: number | null = null;
const trimmed = comingWhenRaw.trim(); const trimmed = comingWhenRaw.trim();
let releasingValue: string;
if (trimmed.toLowerCase() === "soon") { if (trimmed.toLowerCase() === "soon") {
fields.push({ releasingValue = "Soon™";
name: "📅 Releasing",
value: "Soon™",
inline: false,
});
} else { } else {
// Try DD/MM/YYYY // Try DD/MM/YYYY
const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec( const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(
@@ -129,42 +179,67 @@ export default async function (_client: Client) {
if (!isNaN(date.getTime())) if (!isNaN(date.getTime()))
epoch = Math.floor(date.getTime() / 1000); epoch = Math.floor(date.getTime() / 1000);
} else if (/^\d+$/.test(trimmed)) { } else if (/^\d+$/.test(trimmed)) {
// Raw epoch
epoch = Number(trimmed); epoch = Number(trimmed);
} }
if (epoch !== null) { releasingValue =
fields.push({ epoch !== null
name: "📅 Releasing", ? `<t:${epoch}:F> (<t:${epoch}:R>)`
value: `<t:${epoch}:F> (<t:${epoch}:R>)`, : trimmed;
inline: false,
});
} else {
fields.push({
name: "📅 Releasing",
value: trimmed,
inline: false,
});
}
} }
container.addSeparatorComponents(
new SeparatorBuilder()
.setDivider(false)
.setSpacing(SeparatorSpacingSize.Small),
);
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`**📅 Releasing**\n${releasingValue}`,
),
);
} }
fields.push({ // More Info button section
name: "🔗 More Info", container.addSeparatorComponents(
value: `[Click here](${link})`, new SeparatorBuilder()
inline: false, .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 = { // Footer
title: `📢 ${title}`, container.addSeparatorComponents(
description: content, new SeparatorBuilder()
color: 0x5865f2, .setDivider(false)
fields, .setSpacing(SeparatorSpacingSize.Small),
footer: { );
text: `Announced by ${interaction.user.username}`, container.addSectionComponents(
icon_url: interaction.user.displayAvatarURL(), new SectionBuilder()
}, .addTextDisplayComponents(
timestamp: new Date().toISOString(), new TextDisplayBuilder().setContent(
}; `-# Announced by ${interaction.user.username}`,
),
)
.setThumbnailAccessory(
new ThumbnailBuilder({
media: {
url: interaction.user.displayAvatarURL(),
},
}),
),
);
// Send the announcement to a specific channel // Send the announcement to a specific channel
const announcementChannel = const announcementChannel =
@@ -172,11 +247,9 @@ export default async function (_client: Client) {
CHANNELS.ANNOUNCEMENTS, CHANNELS.ANNOUNCEMENTS,
); );
if (announcementChannel?.isTextBased()) { if (announcementChannel?.isTextBased()) {
if (image) {
await announcementChannel.send({ files: [image.url] });
}
const message = await announcementChannel.send({ const message = await announcementChannel.send({
embeds: [announcementEmbed], flags: MessageFlags.IsComponentsV2,
components: [container],
}); });
// Reactions for engagement // Reactions for engagement

View File

@@ -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 { registerCommand } from "../lib/commandRegistry";
import { CHANNELS, ROLES } from "../config"; import { CHANNELS, ROLES } from "../config";
import { db } from ".."; import { db } from "..";
@@ -109,8 +121,25 @@ export default async function (_client: Client) {
// Post Process Content // Post Process Content
newContent = newContent.replace(/\\n/g, "\n"); // allow users to input \n for newlines 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() !== "") { if (changesRaw && changesRaw.trim() !== "") {
const changeLines = changesRaw const changeLines = changesRaw
.split(",") .split(",")
@@ -118,22 +147,25 @@ export default async function (_client: Client) {
.filter((c: string) => c.length > 0) .filter((c: string) => c.length > 0)
.map((c: string) => `${c}`) .map((c: string) => `${c}`)
.join("\n"); .join("\n");
fields.push({ container.addSeparatorComponents(
name: "📋 Changes", new SeparatorBuilder()
value: changeLines, .setDivider(false)
inline: false, .setSpacing(SeparatorSpacingSize.Small),
}); );
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`**📋 Changes**\n${changeLines}`,
),
);
} }
// Releasing
if (comingWhenRaw && comingWhenRaw.trim() !== "") { if (comingWhenRaw && comingWhenRaw.trim() !== "") {
let epoch: number | null = null; let epoch: number | null = null;
const trimmed = comingWhenRaw.trim(); const trimmed = comingWhenRaw.trim();
let releasingValue: string;
if (trimmed.toLowerCase() === "soon") { if (trimmed.toLowerCase() === "soon") {
fields.push({ releasingValue = "Soon™";
name: "📅 Releasing",
value: "Soon™",
inline: false,
});
} else { } else {
const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec( const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(
trimmed, trimmed,
@@ -150,39 +182,64 @@ export default async function (_client: Client) {
} else if (/^\d+$/.test(trimmed)) { } else if (/^\d+$/.test(trimmed)) {
epoch = Number(trimmed); epoch = Number(trimmed);
} }
if (epoch !== null) { releasingValue =
fields.push({ epoch !== null
name: "📅 Releasing", ? `<t:${epoch}:F> (<t:${epoch}:R>)`
value: `<t:${epoch}:F> (<t:${epoch}:R>)`, : trimmed;
inline: false,
});
} else {
fields.push({
name: "📅 Releasing",
value: trimmed,
inline: false,
});
}
} }
container.addSeparatorComponents(
new SeparatorBuilder()
.setDivider(false)
.setSpacing(SeparatorSpacingSize.Small),
);
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`**📅 Releasing**\n${releasingValue}`,
),
);
} }
fields.push({ // More Info button section
name: "🔗 More Info", container.addSeparatorComponents(
value: `[Click here](${link})`, new SeparatorBuilder()
inline: false, .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 = { // Footer
title: `📢 ${newTitle}`, container.addSeparatorComponents(
description: newContent, new SeparatorBuilder()
color: 0x5865f2, .setDivider(false)
fields, .setSpacing(SeparatorSpacingSize.Small),
footer: { );
text: `Announced by ${existing.announcedByUsername ?? interaction.user.username} • Last edited by ${interaction.user.username}`, container.addSectionComponents(
icon_url: interaction.user.displayAvatarURL(), new SectionBuilder()
}, .addTextDisplayComponents(
timestamp: new Date().toISOString(), 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 // Fetch and edit the original message
const announcementChannel = interaction.guild?.channels.cache.get( const announcementChannel = interaction.guild?.channels.cache.get(
@@ -208,7 +265,10 @@ export default async function (_client: Client) {
return; return;
} }
await targetMessage.edit({ embeds: [updatedEmbed] }); await targetMessage.edit({
flags: MessageFlags.IsComponentsV2,
components: [container],
});
// Update DB // Update DB
await db.collection("announcements").updateOne( await db.collection("announcements").updateOne(

View File

@@ -1,5 +1,16 @@
import { Express, Request, Response } from "express"; 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 { CHANNELS } from "../config";
import { client, db } from "../index"; import { client, db } from "../index";
@@ -17,12 +28,10 @@ export default async function gitCommitPOST(app: Express) {
} }
if (event !== "push") { if (event !== "push") {
return res return res.status(200).json({
.status(200) success: true,
.json({ message: `Event '${event}' ignored`,
success: true, });
message: `Event '${event}' ignored`,
});
} }
const body = req.body; const body = req.body;
@@ -51,32 +60,87 @@ export default async function gitCommitPOST(app: Express) {
commitLines.push(`...and ${commits.length - SHOW_MAX} more`); commitLines.push(`...and ${commits.length - SHOW_MAX} more`);
} }
const embed = new EmbedBuilder() const container = new ContainerBuilder().setAccentColor(
.setColor(forced ? 0xff4444 : 0x2ea44f) forced ? 0xff4444 : 0x2ea44f,
.setTitle( );
`${forced ? "⚠️ Force Push" : "📦 New Push"} to \`${branch}\``,
) // Author + title section
.setURL(compareUrl) container.addSectionComponents(
.setAuthor({ new SectionBuilder()
name: pusher.name, .addTextDisplayComponents(
iconURL: `https://github.com/${pusher.name}.png`, new TextDisplayBuilder().setContent(
url: `https://github.com/${pusher.name}`, `## ${forced ? "⚠️ Force Push" : "📦 New Push"} to \`${branch}\``,
}) ),
.addFields( )
{ name: "🌿 Branch", value: `\`${branch}\``, inline: true }, .setThumbnailAccessory(
{ new ThumbnailBuilder({
name: `📝 Commits (${commits.length})`, media: {
value: commitLines.join("\n") || "_No commits_", url: `https://github.com/${pusher.name}.png`,
}, },
) }),
.setFooter({ ),
text: `Delivery: ${req.headers["x-github-delivery"] ?? "unknown"}`, );
}) container.addSeparatorComponents(
.setTimestamp( new SeparatorBuilder()
headCommit.timestamp .setDivider(true)
? new Date(headCommit.timestamp) .setSpacing(SeparatorSpacingSize.Small),
: new Date(), );
// 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( const channel = (await client.channels.fetch(
configured_channel, configured_channel,
@@ -90,7 +154,10 @@ export default async function gitCommitPOST(app: Express) {
.json({ error: "Discord channel unavailable" }); .json({ error: "Discord channel unavailable" });
} }
const message = await channel.send({ embeds: [embed] }); const message = await channel.send({
flags: MessageFlags.IsComponentsV2,
components: [container],
});
console.log( console.log(
`[WEB-gitCommit] Push event sent to configured channel (${commits.length} commits on ${branch})`, `[WEB-gitCommit] Push event sent to configured channel (${commits.length} commits on ${branch})`,
); );