1
0
Fork 0

feat(bot): pick the target channel inside the announce modal

- The compose modal gains an optional channel select (text and
  announcement channels), preselected with the configured default
- /announce now posts only to the announce channel by default: the ops
  fallback is gone, so an unconfigured announce channel errors instead
  of silently posting to ops
- Roll call was already ops-only and is unchanged

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-10 23:58:03 -04:00
parent 0bda8c56e3
commit ecdbcbd15b

View file

@ -1,7 +1,9 @@
import {
ActionRowBuilder,
ChannelSelectMenuBuilder,
ChannelType,
ComponentType,
EmbedBuilder,
LabelBuilder,
MessageFlags,
ModalBuilder,
SlashCommandBuilder,
@ -20,6 +22,7 @@ const ANNOUNCE_COLOR = 0xf59e0b;
const ANNOUNCE_MODAL_PREFIX = "ptf-announce:";
const TITLE_INPUT_ID = "ptf-announce-title";
const BODY_INPUT_ID = "ptf-announce-body";
const CHANNEL_INPUT_ID = "ptf-announce-channel";
/** Modal paragraph inputs cap at 4000 chars; the embed description limit is 4096. */
const MAX_BODY_LENGTH = 4000;
@ -33,6 +36,12 @@ const modalText = (fields: ModalSubmitInteraction["fields"], customId: string):
return field?.type === ComponentType.TextInput ? field.value : "";
};
const modalChannelId = (fields: ModalSubmitInteraction["fields"]): string | null => {
const field = fields.fields.get(CHANNEL_INPUT_ID);
if (field?.type !== ComponentType.ChannelSelect) return null;
return field.channels?.first()?.id ?? null;
};
const postAnnouncement = async (
channel: TextBasedChannelFields,
title: string,
@ -75,33 +84,33 @@ export const announceCommand: BotCommand = {
const message = (interaction.options.getString("message") ?? "").trim();
const option = interaction.options.getChannel("channel");
const channelId = option?.id ?? botConfig.announceChannelId ?? botConfig.opsChannelId;
const channelId = option?.id ?? botConfig.announceChannelId;
if (!channelId) {
await interaction.reply(
ephemeral("No announce channel is configured and no channel was given."),
ephemeral(
"No announce channel is configured (set DISCORD_ANNOUNCE_CHANNEL_ID) and no channel was given.",
),
);
return;
}
if (!message) {
// Compose in a modal. The resolved channel rides along in the custom id,
// because modal submit interactions do not carry the command's options.
// customId carries the default channel as the fallback: modal submits
// don't carry the command's options.
const modal = new ModalBuilder()
.setCustomId(`${ANNOUNCE_MODAL_PREFIX}${channelId}`)
.setTitle("Post an announcement");
modal.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
modal.addLabelComponents(
new LabelBuilder().setLabel("Title (optional)").setTextInputComponent(
new TextInputBuilder()
.setCustomId(TITLE_INPUT_ID)
.setLabel("Title (optional)")
.setStyle(TextInputStyle.Short)
.setMaxLength(256)
.setRequired(false),
),
new ActionRowBuilder<TextInputBuilder>().addComponents(
new LabelBuilder().setLabel("Announcement text").setTextInputComponent(
new TextInputBuilder()
.setCustomId(BODY_INPUT_ID)
.setLabel("Announcement text")
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(MAX_BODY_LENGTH)
.setRequired(true)
@ -109,6 +118,16 @@ export const announceCommand: BotCommand = {
"Paste your announcement. Markdown (headings, lists, bold, code) is supported.",
),
),
new LabelBuilder()
.setLabel("Channel (optional)")
.setDescription("Defaults to the configured app updates channel.")
.setChannelSelectMenuComponent(
new ChannelSelectMenuBuilder()
.setCustomId(CHANNEL_INPUT_ID)
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
.setDefaultChannels(channelId)
.setRequired(false),
),
);
await interaction.showModal(modal);
return;
@ -145,9 +164,11 @@ export const handleAnnounceModal = async (
}
// The modal custom id is "ptf-announce:<channelId>", written when the modal
// was opened from the slash command. Fall back to the configured channel.
// was opened. A channel picked in the modal overrides it.
const customChannelId = interaction.customId.slice(ANNOUNCE_MODAL_PREFIX.length);
const resolvedId = customChannelId || botConfig.announceChannelId || botConfig.opsChannelId;
const resolvedId =
modalChannelId(interaction.fields) ??
(customChannelId || botConfig.announceChannelId);
if (!resolvedId) {
await interaction.reply(ephemeral("No announce channel is configured."));
return;