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:
parent
0bda8c56e3
commit
ecdbcbd15b
1 changed files with 33 additions and 12 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue