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 {
|
import {
|
||||||
ActionRowBuilder,
|
ChannelSelectMenuBuilder,
|
||||||
|
ChannelType,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
EmbedBuilder,
|
EmbedBuilder,
|
||||||
|
LabelBuilder,
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
ModalBuilder,
|
ModalBuilder,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
|
|
@ -20,6 +22,7 @@ const ANNOUNCE_COLOR = 0xf59e0b;
|
||||||
const ANNOUNCE_MODAL_PREFIX = "ptf-announce:";
|
const ANNOUNCE_MODAL_PREFIX = "ptf-announce:";
|
||||||
const TITLE_INPUT_ID = "ptf-announce-title";
|
const TITLE_INPUT_ID = "ptf-announce-title";
|
||||||
const BODY_INPUT_ID = "ptf-announce-body";
|
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. */
|
/** Modal paragraph inputs cap at 4000 chars; the embed description limit is 4096. */
|
||||||
const MAX_BODY_LENGTH = 4000;
|
const MAX_BODY_LENGTH = 4000;
|
||||||
|
|
||||||
|
|
@ -33,6 +36,12 @@ const modalText = (fields: ModalSubmitInteraction["fields"], customId: string):
|
||||||
return field?.type === ComponentType.TextInput ? field.value : "";
|
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 (
|
const postAnnouncement = async (
|
||||||
channel: TextBasedChannelFields,
|
channel: TextBasedChannelFields,
|
||||||
title: string,
|
title: string,
|
||||||
|
|
@ -75,33 +84,33 @@ export const announceCommand: BotCommand = {
|
||||||
|
|
||||||
const message = (interaction.options.getString("message") ?? "").trim();
|
const message = (interaction.options.getString("message") ?? "").trim();
|
||||||
const option = interaction.options.getChannel("channel");
|
const option = interaction.options.getChannel("channel");
|
||||||
const channelId = option?.id ?? botConfig.announceChannelId ?? botConfig.opsChannelId;
|
const channelId = option?.id ?? botConfig.announceChannelId;
|
||||||
if (!channelId) {
|
if (!channelId) {
|
||||||
await interaction.reply(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
// Compose in a modal. The resolved channel rides along in the custom id,
|
// customId carries the default channel as the fallback: modal submits
|
||||||
// because modal submit interactions do not carry the command's options.
|
// don't carry the command's options.
|
||||||
const modal = new ModalBuilder()
|
const modal = new ModalBuilder()
|
||||||
.setCustomId(`${ANNOUNCE_MODAL_PREFIX}${channelId}`)
|
.setCustomId(`${ANNOUNCE_MODAL_PREFIX}${channelId}`)
|
||||||
.setTitle("Post an announcement");
|
.setTitle("Post an announcement");
|
||||||
modal.addComponents(
|
modal.addLabelComponents(
|
||||||
new ActionRowBuilder<TextInputBuilder>().addComponents(
|
new LabelBuilder().setLabel("Title (optional)").setTextInputComponent(
|
||||||
new TextInputBuilder()
|
new TextInputBuilder()
|
||||||
.setCustomId(TITLE_INPUT_ID)
|
.setCustomId(TITLE_INPUT_ID)
|
||||||
.setLabel("Title (optional)")
|
|
||||||
.setStyle(TextInputStyle.Short)
|
.setStyle(TextInputStyle.Short)
|
||||||
.setMaxLength(256)
|
.setMaxLength(256)
|
||||||
.setRequired(false),
|
.setRequired(false),
|
||||||
),
|
),
|
||||||
new ActionRowBuilder<TextInputBuilder>().addComponents(
|
new LabelBuilder().setLabel("Announcement text").setTextInputComponent(
|
||||||
new TextInputBuilder()
|
new TextInputBuilder()
|
||||||
.setCustomId(BODY_INPUT_ID)
|
.setCustomId(BODY_INPUT_ID)
|
||||||
.setLabel("Announcement text")
|
|
||||||
.setStyle(TextInputStyle.Paragraph)
|
.setStyle(TextInputStyle.Paragraph)
|
||||||
.setMaxLength(MAX_BODY_LENGTH)
|
.setMaxLength(MAX_BODY_LENGTH)
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
|
|
@ -109,6 +118,16 @@ export const announceCommand: BotCommand = {
|
||||||
"Paste your announcement. Markdown (headings, lists, bold, code) is supported.",
|
"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);
|
await interaction.showModal(modal);
|
||||||
return;
|
return;
|
||||||
|
|
@ -145,9 +164,11 @@ export const handleAnnounceModal = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// The modal custom id is "ptf-announce:<channelId>", written when the modal
|
// 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 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) {
|
if (!resolvedId) {
|
||||||
await interaction.reply(ephemeral("No announce channel is configured."));
|
await interaction.reply(ephemeral("No announce channel is configured."));
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue