add update announcement command to modify existing announcements with enhanced options and permissions
All checks were successful
CI / build (push) Successful in 11s

This commit is contained in:
Space-Banane
2026-02-22 15:45:48 +01:00
parent 96528bd842
commit 05adc62b81

View File

@@ -0,0 +1,222 @@
import { Client, SlashCommandBuilder } from "discord.js";
import { registerCommand } from "../lib/commandRegistry";
import { CHANNELS, ROLES } from "../config";
import { db } from "..";
export default async function (_client: Client) {
registerCommand({
data: new SlashCommandBuilder()
.setName("update-announcement")
.setDescription("Updates an existing announcement")
.addStringOption((option) =>
option
.setName("message_id")
.setDescription(
"The Discord message ID of the announcement to update",
)
.setRequired(true),
)
.addStringOption((option) =>
option
.setName("title")
.setDescription("New title for the announcement")
.setRequired(false),
)
.addStringOption((option) =>
option
.setName("content")
.setDescription("New content for the announcement")
.setRequired(false),
)
.addStringOption((option) =>
option
.setName("changes")
.setDescription(
"New comma-separated list of changes (replaces existing changes)",
)
.setRequired(false),
)
.addStringOption((option) =>
option
.setName("coming_when")
.setDescription(
"New release date: DD/MM/YYYY or an epoch timestamp",
)
.setRequired(false),
)
.addStringOption((option) =>
option
.setName("link")
.setDescription("New link for the announcement")
.setRequired(false),
) as SlashCommandBuilder,
async execute(interaction) {
const memberRoles = interaction.member?.roles;
let isMaintainer = false;
if (memberRoles) {
if (typeof memberRoles === "object" && "cache" in memberRoles) {
isMaintainer = memberRoles.cache.has(ROLES.MAINTAINERS);
} else if (Array.isArray(memberRoles)) {
isMaintainer = memberRoles.includes(ROLES.MAINTAINERS);
}
}
if (!isMaintainer) {
await interaction.reply({
ephemeral: true,
content: "You do not have permission to use this command.",
});
return;
}
const messageId = interaction.options.getString("message_id", true);
// Look up the original announcement in DB
const existing = await db
.collection("announcements")
.findOne({ messageId });
if (!existing) {
await interaction.reply({
ephemeral: true,
content:
"Announcement not found. Make sure the message ID is correct and was created with `/new-announcement`.",
});
return;
}
await interaction.deferReply({ ephemeral: true });
// Resolve updated values, falling back to existing ones
const newTitle =
interaction.options.getString("title", false) ?? existing.title;
let newContent =
interaction.options.getString("content", false) ??
existing.content;
const changesRaw =
interaction.options.getString("changes", false) ??
existing.changes;
const comingWhenRaw =
interaction.options.getString("coming_when", false) ??
existing.comingWhen;
let link =
interaction.options.getString("link", false) ??
existing.link ??
"https://github.com/Space-Banane/shsf";
if (!link || link === "")
link = "https://github.com/Space-Banane/shsf";
// Post Process Content
newContent = newContent.replace(/\\n/g, "\n"); // allow users to input \n for newlines
const fields = [];
if (changesRaw && changesRaw.trim() !== "") {
const changeLines = changesRaw
.split(",")
.map((c: string) => c.trim())
.filter((c: string) => c.length > 0)
.map((c: string) => `${c}`)
.join("\n");
fields.push({
name: "📋 Changes",
value: changeLines,
inline: false,
});
}
if (comingWhenRaw && comingWhenRaw.trim() !== "") {
let epoch: number | null = null;
const trimmed = comingWhenRaw.trim();
const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(trimmed);
if (ddmmyyyy) {
const [, dd, mm, yyyy] = ddmmyyyy;
const date = new Date(
Number(yyyy),
Number(mm) - 1,
Number(dd),
);
if (!isNaN(date.getTime()))
epoch = Math.floor(date.getTime() / 1000);
} else if (/^\d+$/.test(trimmed)) {
epoch = Number(trimmed);
}
if (epoch !== null) {
fields.push({
name: "📅 Coming On",
value: `<t:${epoch}:F> (<t:${epoch}:R>)`,
inline: false,
});
} else {
fields.push({
name: "📅 Coming On",
value: trimmed,
inline: false,
});
}
}
fields.push({
name: "🔗 More Info",
value: `[Click here](${link})`,
inline: false,
});
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(),
};
// Fetch and edit the original message
const announcementChannel = interaction.guild?.channels.cache.get(
CHANNELS.ANNOUNCEMENTS,
);
if (!announcementChannel?.isTextBased()) {
await interaction.editReply({
content: "Could not find the announcements channel.",
});
return;
}
let targetMessage;
try {
targetMessage =
await announcementChannel.messages.fetch(messageId);
} catch {
await interaction.editReply({
content:
"Could not fetch the message. Make sure the message ID is correct.",
});
return;
}
await targetMessage.edit({ embeds: [updatedEmbed] });
// Update DB
await db.collection("announcements").updateOne(
{ messageId },
{
$set: {
title: newTitle,
content: newContent,
changes: changesRaw ?? null,
comingWhen: comingWhenRaw ?? null,
link,
lastEditedBy: interaction.user.id,
lastEditedAt: new Date(),
},
},
);
await interaction.editReply({ content: "Announcement updated!" });
},
});
}