UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

793 lines (792 loc) 31 kB
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { A as Button, B as Separator, D as Modal, E as Label, F as MediaGallery, Ft as ButtonStyle, H as TextDisplay, Ht as MessageFlags, I as MentionableSelectMenu, K as parseCustomId, L as RoleSelectMenu, M as Container, N as File, O as RadioGroup, P as LinkButton, R as Row, T as CheckboxGroup, U as Thumbnail, V as StringSelectMenu, W as UserSelectMenu, j as ChannelSelectMenu, k as TextInput, qt as TextInputStyle, z as Section } from "./discord-DdqXEGEm.js"; import crypto from "node:crypto"; //#region extensions/discord/src/component-custom-id.ts const DISCORD_COMPONENT_CUSTOM_ID_KEY = "occomp"; const DISCORD_MODAL_CUSTOM_ID_KEY = "ocmodal"; const ENCODED_CUSTOM_ID_VERSION = "1"; function encodeCustomIdValue(value) { return value.replace(/%/g, "%25").replace(/;/g, "%3B"); } function needsCustomIdEncoding(value) { return /[%;]/.test(value); } function decodeCustomIdValue(value) { return value.replace(/%(25|3B)/gi, (match) => match.toLowerCase() === "%25" ? "%" : ";"); } function decodeParsedCustomIdData(data) { if (data.e !== ENCODED_CUSTOM_ID_VERSION) return data; return Object.fromEntries(Object.entries(data).map(([key, value]) => [key, typeof value === "string" ? decodeCustomIdValue(value) : value])); } function buildDiscordComponentCustomId(params) { const encoded = needsCustomIdEncoding(params.componentId) || needsCustomIdEncoding(params.modalId ?? ""); const componentId = encoded ? encodeCustomIdValue(params.componentId) : params.componentId; const base = encoded ? `${DISCORD_COMPONENT_CUSTOM_ID_KEY}:e=${ENCODED_CUSTOM_ID_VERSION};cid=${componentId}` : `${DISCORD_COMPONENT_CUSTOM_ID_KEY}:cid=${componentId}`; const modalId = params.modalId; if (!modalId) return base; return `${base};mid=${encoded ? encodeCustomIdValue(modalId) : modalId}`; } function buildDiscordModalCustomId(modalId) { return needsCustomIdEncoding(modalId) ? `${DISCORD_MODAL_CUSTOM_ID_KEY}:e=${ENCODED_CUSTOM_ID_VERSION};mid=${encodeCustomIdValue(modalId)}` : `${DISCORD_MODAL_CUSTOM_ID_KEY}:mid=${modalId}`; } function parseDiscordComponentCustomId(id) { const parsed = parseCustomId(id); if (parsed.key !== "occomp") return null; const data = decodeParsedCustomIdData(parsed.data); const componentId = data.cid; if (typeof componentId !== "string" || !componentId.trim()) return null; const modalId = data.mid; return { componentId, modalId: typeof modalId === "string" && modalId.trim() ? modalId : void 0 }; } function parseDiscordModalCustomId(id) { const parsed = parseCustomId(id); if (parsed.key !== "ocmodal") return null; const modalId = decodeParsedCustomIdData(parsed.data).mid; if (typeof modalId !== "string" || !modalId.trim()) return null; return modalId; } function isDiscordComponentWildcardRegistrationId(id) { return /^__openclaw_discord_component_[a-z_]+_wildcard__$/.test(id); } function parseDiscordComponentCustomIdForInteraction(id) { if (id === "*" || isDiscordComponentWildcardRegistrationId(id)) return { key: "*", data: {} }; const parsed = parseCustomId(id); if (parsed.key !== "occomp") return parsed; return { key: "*", data: decodeParsedCustomIdData(parsed.data) }; } function parseDiscordModalCustomIdForInteraction(id) { if (id === "*" || isDiscordComponentWildcardRegistrationId(id)) return { key: "*", data: {} }; const parsed = parseCustomId(id); if (parsed.key !== "ocmodal") return parsed; return { key: "*", data: decodeParsedCustomIdData(parsed.data) }; } //#endregion //#region extensions/discord/src/components.parse.ts const DISCORD_COMPONENT_ATTACHMENT_PREFIX = "attachment://"; const BLOCK_ALIASES = new Map([["row", "actions"], ["action-row", "actions"]]); function requireObject(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`); return value; } function readString(value, label, opts) { if (typeof value !== "string") throw new Error(`${label} must be a string`); const trimmed = value.trim(); if (!opts?.allowEmpty && !trimmed) throw new Error(`${label} cannot be empty`); return opts?.allowEmpty ? value : trimmed; } function readOptionalString(value) { if (typeof value !== "string") return; const trimmed = value.trim(); return trimmed ? trimmed : void 0; } function readOptionalCallbackDataKind(value, label) { const kind = readOptionalString(value); if (kind === void 0) return; if (kind === "command" || kind === "callback") return kind; throw new Error(`${label} must be one of command, callback`); } function readOptionalStringArray(value, label) { if (value === void 0) return; if (!Array.isArray(value)) throw new Error(`${label} must be an array`); if (value.length === 0) return; return value.map((entry, index) => readString(entry, `${label}[${index}]`)); } function readOptionalInteger(value, label, bounds) { if (value == null) return; if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) throw new Error(`${label} must be an integer`); if (bounds?.min !== void 0 && value < bounds.min) throw new Error(`${label} must be at least ${bounds.min}`); if (bounds?.max !== void 0 && value > bounds.max) throw new Error(`${label} must be at most ${bounds.max}`); return value; } function readOptionalEmoji(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const obj = value; return { name: readString(obj.name, `${label}.name`), id: readOptionalString(obj.id), animated: typeof obj.animated === "boolean" ? obj.animated : void 0 }; } function normalizeModalFieldName(value, index) { const trimmed = value?.trim(); if (trimmed) return trimmed; return `field_${index + 1}`; } function normalizeAttachmentRef(value, label) { const trimmed = value.trim(); if (!trimmed.startsWith("attachment://")) throw new Error(`${label} must start with "${DISCORD_COMPONENT_ATTACHMENT_PREFIX}"`); const attachmentName = trimmed.slice(13).trim(); if (!attachmentName) throw new Error(`${label} must include an attachment filename`); return `${DISCORD_COMPONENT_ATTACHMENT_PREFIX}${attachmentName}`; } function resolveDiscordComponentAttachmentName(value) { const trimmed = value.trim(); if (!trimmed.startsWith("attachment://")) throw new Error(`Attachment reference must start with "${DISCORD_COMPONENT_ATTACHMENT_PREFIX}"`); const attachmentName = trimmed.slice(13).trim(); if (!attachmentName) throw new Error("Attachment reference must include a filename"); return attachmentName; } function mapButtonStyle(style) { switch (normalizeLowercaseStringOrEmpty(style ?? "primary")) { case "secondary": return ButtonStyle.Secondary; case "success": return ButtonStyle.Success; case "danger": return ButtonStyle.Danger; case "link": return ButtonStyle.Link; default: return ButtonStyle.Primary; } } function mapTextInputStyle(style) { return style === "paragraph" ? TextInputStyle.Paragraph : TextInputStyle.Short; } function normalizeBlockType(raw) { const lowered = normalizeLowercaseStringOrEmpty(raw); return BLOCK_ALIASES.get(lowered) ?? lowered; } function parseSelectOptions(raw, label) { if (raw === void 0) return; if (!Array.isArray(raw)) throw new Error(`${label} must be an array`); return raw.map((entry, index) => { const obj = requireObject(entry, `${label}[${index}]`); return { label: readString(obj.label, `${label}[${index}].label`), value: readString(obj.value, `${label}[${index}].value`), description: readOptionalString(obj.description), emoji: readOptionalEmoji(obj.emoji, `${label}[${index}].emoji`), default: typeof obj.default === "boolean" ? obj.default : void 0 }; }); } function parseButtonSpec(raw, label) { const obj = requireObject(raw, label); const style = readOptionalString(obj.style); const url = readOptionalString(obj.url); if ((style === "link" || url) && !url) throw new Error(`${label}.url is required for link buttons`); return { label: readString(obj.label, `${label}.label`), style, url, callbackData: readOptionalString(obj.callbackData), callbackDataKind: readOptionalCallbackDataKind(obj.callbackDataKind, `${label}.callbackDataKind`), emoji: readOptionalEmoji(obj.emoji, `${label}.emoji`), disabled: typeof obj.disabled === "boolean" ? obj.disabled : void 0, allowedUsers: readOptionalStringArray(obj.allowedUsers, `${label}.allowedUsers`) }; } function parseSelectSpec(raw, label) { const obj = requireObject(raw, label); const type = readOptionalString(obj.type); const allowedTypes = [ "string", "user", "role", "mentionable", "channel" ]; if (type && !allowedTypes.includes(type)) throw new Error(`${label}.type must be one of ${allowedTypes.join(", ")}`); return { type, callbackData: readOptionalString(obj.callbackData), callbackDataKind: readOptionalCallbackDataKind(obj.callbackDataKind, `${label}.callbackDataKind`), placeholder: readOptionalString(obj.placeholder), minValues: readOptionalInteger(obj.minValues, `${label}.minValues`, { min: 0, max: 25 }), maxValues: readOptionalInteger(obj.maxValues, `${label}.maxValues`, { min: 1, max: 25 }), options: parseSelectOptions(obj.options, `${label}.options`), allowedUsers: readOptionalStringArray(obj.allowedUsers, `${label}.allowedUsers`) }; } function parseModalField(raw, label, index) { const obj = requireObject(raw, label); const type = normalizeLowercaseStringOrEmpty(readString(obj.type, `${label}.type`)); const supported = [ "text", "checkbox", "radio", "select", "role-select", "user-select" ]; if (!supported.includes(type)) throw new Error(`${label}.type must be one of ${supported.join(", ")}`); const options = parseSelectOptions(obj.options, `${label}.options`); if ([ "checkbox", "radio", "select" ].includes(type) && (!options || options.length === 0)) throw new Error(`${label}.options is required for ${type} fields`); if (type === "radio" && (obj.minValues != null || obj.maxValues != null)) throw new Error(`${label}.minValues/maxValues are not supported for radio fields`); const required = typeof obj.required === "boolean" ? obj.required : void 0; const maxValues = type === "checkbox" ? 10 : 25; return { type, name: normalizeModalFieldName(readOptionalString(obj.name), index), label: readString(obj.label, `${label}.label`), description: readOptionalString(obj.description), placeholder: readOptionalString(obj.placeholder), required, options, minValues: readOptionalInteger(obj.minValues, `${label}.minValues`, { min: required === false ? 0 : 1, max: maxValues }), maxValues: readOptionalInteger(obj.maxValues, `${label}.maxValues`, { min: 1, max: maxValues }), minLength: readOptionalInteger(obj.minLength, `${label}.minLength`, { min: 0, max: 4e3 }), maxLength: readOptionalInteger(obj.maxLength, `${label}.maxLength`, { min: 1, max: 4e3 }), style: readOptionalString(obj.style) }; } function parseComponentBlock(raw, label) { const obj = requireObject(raw, label); switch (normalizeBlockType(normalizeLowercaseStringOrEmpty(readString(obj.type, `${label}.type`)))) { case "text": return { type: "text", text: readString(obj.text, `${label}.text`) }; case "section": { const text = readOptionalString(obj.text); const textsRaw = obj.texts; const texts = Array.isArray(textsRaw) ? textsRaw.map((entry, idx) => readString(entry, `${label}.texts[${idx}]`)) : void 0; if (!text && (!texts || texts.length === 0)) throw new Error(`${label}.text or ${label}.texts is required for section blocks`); let accessory; if (obj.accessory !== void 0) { const accessoryObj = requireObject(obj.accessory, `${label}.accessory`); const accessoryType = normalizeLowercaseStringOrEmpty(readString(accessoryObj.type, `${label}.accessory.type`)); if (accessoryType === "thumbnail") accessory = { type: "thumbnail", url: readString(accessoryObj.url, `${label}.accessory.url`) }; else if (accessoryType === "button") accessory = { type: "button", button: parseButtonSpec(accessoryObj.button, `${label}.accessory.button`) }; else throw new Error(`${label}.accessory.type must be "thumbnail" or "button"`); } return { type: "section", text, texts, accessory }; } case "separator": { const spacingRaw = obj.spacing; let spacing; if (spacingRaw === "small" || spacingRaw === "large") spacing = spacingRaw; else if (spacingRaw === 1 || spacingRaw === 2) spacing = spacingRaw; else if (spacingRaw !== void 0) throw new Error(`${label}.spacing must be "small", "large", 1, or 2`); const divider = typeof obj.divider === "boolean" ? obj.divider : void 0; return { type: "separator", spacing, divider }; } case "actions": { const buttonsRaw = obj.buttons; const buttons = Array.isArray(buttonsRaw) ? buttonsRaw.map((entry, idx) => parseButtonSpec(entry, `${label}.buttons[${idx}]`)) : void 0; const select = obj.select ? parseSelectSpec(obj.select, `${label}.select`) : void 0; if ((!buttons || buttons.length === 0) && !select) throw new Error(`${label} requires buttons or select`); if (buttons && select) throw new Error(`${label} cannot include both buttons and select`); return { type: "actions", buttons, select }; } case "media-gallery": { const itemsRaw = obj.items; if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) throw new Error(`${label}.items must be a non-empty array`); return { type: "media-gallery", items: itemsRaw.map((entry, idx) => { const itemObj = requireObject(entry, `${label}.items[${idx}]`); return { url: readString(itemObj.url, `${label}.items[${idx}].url`), description: readOptionalString(itemObj.description), spoiler: typeof itemObj.spoiler === "boolean" ? itemObj.spoiler : void 0 }; }) }; } case "file": return { type: "file", file: normalizeAttachmentRef(readString(obj.file, `${label}.file`), `${label}.file`), spoiler: typeof obj.spoiler === "boolean" ? obj.spoiler : void 0 }; default: throw new Error(`${label}.type must be a supported component block`); } } function readDiscordComponentSpec(raw) { if (raw === void 0 || raw === null) return null; const obj = requireObject(raw, "components"); const blocksRaw = obj.blocks; const blocks = Array.isArray(blocksRaw) ? blocksRaw.map((entry, idx) => parseComponentBlock(entry, `components.blocks[${idx}]`)) : void 0; const modalRaw = obj.modal; const reusable = typeof obj.reusable === "boolean" ? obj.reusable : void 0; let modal; if (modalRaw !== void 0) { const modalObj = requireObject(modalRaw, "components.modal"); const fieldsRaw = modalObj.fields; if (!Array.isArray(fieldsRaw) || fieldsRaw.length === 0) throw new Error("components.modal.fields must be a non-empty array"); if (fieldsRaw.length > 5) throw new Error("components.modal.fields supports up to 5 inputs"); const fields = fieldsRaw.map((entry, idx) => parseModalField(entry, `components.modal.fields[${idx}]`, idx)); modal = { title: readString(modalObj.title, "components.modal.title"), callbackData: readOptionalString(modalObj.callbackData), triggerLabel: readOptionalString(modalObj.triggerLabel), triggerStyle: readOptionalString(modalObj.triggerStyle), allowedUsers: readOptionalStringArray(modalObj.allowedUsers, "components.modal.allowedUsers"), fields }; } return { text: readOptionalString(obj.text), reusable, container: typeof obj.container === "object" && obj.container && !Array.isArray(obj.container) ? { accentColor: obj.container.accentColor, spoiler: typeof obj.container.spoiler === "boolean" ? obj.container.spoiler : void 0 } : void 0, blocks, modal }; } //#endregion //#region extensions/discord/src/components.builders.ts function createShortId(prefix) { return `${prefix}${crypto.randomBytes(6).toString("base64url")}`; } function buildTextDisplays(text, texts) { if (texts && texts.length > 0) return texts.map((entry) => new TextDisplay(entry)); if (text) return [new TextDisplay(text)]; return []; } function createButtonComponent(params) { const style = mapButtonStyle(params.spec.style); if (style === ButtonStyle.Link || Boolean(params.spec.url)) { if (!params.spec.url) throw new Error("Link buttons require a url"); const linkUrl = params.spec.url; class DynamicLinkButton extends LinkButton { constructor(..._args) { super(..._args); this.label = params.spec.label; this.url = linkUrl; this.disabled = params.spec.disabled ?? false; } } return { component: new DynamicLinkButton() }; } const componentId = params.componentId ?? createShortId("btn_"); const internalCustomId = typeof params.spec.internalCustomId === "string" && params.spec.internalCustomId.trim() ? params.spec.internalCustomId.trim() : void 0; const customId = internalCustomId ?? buildDiscordComponentCustomId({ componentId, modalId: params.modalId }); class DynamicButton extends Button { constructor(..._args2) { super(..._args2); this.label = params.spec.label; this.customId = customId; this.style = style; this.emoji = params.spec.emoji; this.disabled = params.spec.disabled ?? false; } } if (internalCustomId) return { component: new DynamicButton() }; return { component: new DynamicButton(), entry: { id: componentId, kind: params.modalId ? "modal-trigger" : "button", label: params.spec.label, ...params.spec.callbackData !== void 0 ? { callbackData: params.spec.callbackData } : {}, ...params.spec.callbackDataKind !== void 0 ? { callbackDataKind: params.spec.callbackDataKind } : {}, ...params.modalId !== void 0 ? { modalId: params.modalId } : {}, ...params.spec.reusable !== void 0 ? { reusable: params.spec.reusable } : {}, ...params.spec.allowedUsers !== void 0 ? { allowedUsers: params.spec.allowedUsers } : {} } }; } function createSelectComponent(params) { const type = normalizeLowercaseStringOrEmpty(params.spec.type ?? "string"); const componentId = params.componentId ?? createShortId("sel_"); const customId = buildDiscordComponentCustomId({ componentId }); const createEntry = (selectType, label, options) => ({ id: componentId, kind: "select", label, ...params.spec.callbackData !== void 0 ? { callbackData: params.spec.callbackData } : {}, ...params.spec.callbackDataKind !== void 0 ? { callbackDataKind: params.spec.callbackDataKind } : {}, selectType, ...options ? { options } : {}, ...params.spec.allowedUsers !== void 0 ? { allowedUsers: params.spec.allowedUsers } : {} }); if (type === "string") { const options = params.spec.options ?? []; if (options.length === 0) throw new Error("String select menus require options"); class DynamicStringSelect extends StringSelectMenu { constructor(..._args3) { super(..._args3); this.customId = customId; this.options = options; this.minValues = params.spec.minValues; this.maxValues = params.spec.maxValues; this.placeholder = params.spec.placeholder; this.disabled = false; } } return { component: new DynamicStringSelect(), entry: createEntry("string", params.spec.placeholder ?? "select", options.map((option) => ({ value: option.value, label: option.label }))) }; } if (type === "user") { class DynamicUserSelect extends UserSelectMenu { constructor(..._args4) { super(..._args4); this.customId = customId; this.minValues = params.spec.minValues; this.maxValues = params.spec.maxValues; this.placeholder = params.spec.placeholder; this.disabled = false; } } return { component: new DynamicUserSelect(), entry: createEntry("user", params.spec.placeholder ?? "user select") }; } if (type === "role") { class DynamicRoleSelect extends RoleSelectMenu { constructor(..._args5) { super(..._args5); this.customId = customId; this.minValues = params.spec.minValues; this.maxValues = params.spec.maxValues; this.placeholder = params.spec.placeholder; this.disabled = false; } } return { component: new DynamicRoleSelect(), entry: createEntry("role", params.spec.placeholder ?? "role select") }; } if (type === "mentionable") { class DynamicMentionableSelect extends MentionableSelectMenu { constructor(..._args6) { super(..._args6); this.customId = customId; this.minValues = params.spec.minValues; this.maxValues = params.spec.maxValues; this.placeholder = params.spec.placeholder; this.disabled = false; } } return { component: new DynamicMentionableSelect(), entry: createEntry("mentionable", params.spec.placeholder ?? "mentionable select") }; } class DynamicChannelSelect extends ChannelSelectMenu { constructor(..._args7) { super(..._args7); this.customId = customId; this.minValues = params.spec.minValues; this.maxValues = params.spec.maxValues; this.placeholder = params.spec.placeholder; this.disabled = false; } } return { component: new DynamicChannelSelect(), entry: createEntry("channel", params.spec.placeholder ?? "channel select") }; } function isSelectComponent(component) { return component instanceof StringSelectMenu || component instanceof UserSelectMenu || component instanceof RoleSelectMenu || component instanceof MentionableSelectMenu || component instanceof ChannelSelectMenu; } function buildDiscordComponentMessage(params) { const entries = []; const consumptionGroupId = createShortId("grp_"); const modals = []; const components = []; const containerChildren = []; const addEntry = (entry) => { const reusable = entry.reusable ?? params.spec.reusable; entries.push({ ...entry, ...params.sessionKey !== void 0 ? { sessionKey: params.sessionKey } : {}, ...params.agentId !== void 0 ? { agentId: params.agentId } : {}, ...params.accountId !== void 0 ? { accountId: params.accountId } : {}, ...reusable !== void 0 ? { reusable } : {}, consumptionGroupId }); }; const text = params.spec.text ?? params.fallbackText; if (text) containerChildren.push(new TextDisplay(text)); for (const block of params.spec.blocks ?? []) { if (block.type === "text") { containerChildren.push(new TextDisplay(block.text)); continue; } if (block.type === "section") { const displays = buildTextDisplays(block.text, block.texts); if (displays.length > 3) throw new Error("Section blocks support up to 3 text displays"); let accessory; if (block.accessory?.type === "thumbnail") accessory = new Thumbnail(block.accessory.url); else if (block.accessory?.type === "button") { const { component, entry } = createButtonComponent({ spec: block.accessory.button }); accessory = component; if (entry) addEntry(entry); } containerChildren.push(new Section(displays, accessory)); continue; } if (block.type === "separator") { containerChildren.push(new Separator({ spacing: block.spacing, divider: block.divider })); continue; } if (block.type === "media-gallery") { containerChildren.push(new MediaGallery(block.items)); continue; } if (block.type === "file") { containerChildren.push(new File(block.file, block.spoiler)); continue; } if (block.type === "actions") { const rowComponents = []; if (block.buttons) { if (block.buttons.length > 5) throw new Error("Action rows support up to 5 buttons"); for (const button of block.buttons) { const { component, entry } = createButtonComponent({ spec: button }); rowComponents.push(component); if (entry) addEntry(entry); } } else if (block.select) { const { component, entry } = createSelectComponent({ spec: block.select }); rowComponents.push(component); addEntry(entry); } containerChildren.push(new Row(rowComponents)); } } if (params.spec.modal) { const modalId = createShortId("mdl_"); const fields = params.spec.modal.fields.map((field, index) => ({ id: createShortId("fld_"), name: normalizeModalFieldName(field.name, index), label: field.label, type: field.type, ...field.description !== void 0 ? { description: field.description } : {}, ...field.placeholder !== void 0 ? { placeholder: field.placeholder } : {}, ...field.required !== void 0 ? { required: field.required } : {}, ...field.options !== void 0 ? { options: field.options } : {}, ...field.minValues !== void 0 ? { minValues: field.minValues } : {}, ...field.maxValues !== void 0 ? { maxValues: field.maxValues } : {}, ...field.minLength !== void 0 ? { minLength: field.minLength } : {}, ...field.maxLength !== void 0 ? { maxLength: field.maxLength } : {}, ...field.style !== void 0 ? { style: field.style } : {} })); modals.push({ id: modalId, title: params.spec.modal.title, fields, ...params.spec.modal.callbackData !== void 0 ? { callbackData: params.spec.modal.callbackData } : {}, ...params.sessionKey !== void 0 ? { sessionKey: params.sessionKey } : {}, ...params.agentId !== void 0 ? { agentId: params.agentId } : {}, ...params.accountId !== void 0 ? { accountId: params.accountId } : {}, ...params.spec.reusable !== void 0 ? { reusable: params.spec.reusable } : {}, ...params.spec.modal.allowedUsers !== void 0 ? { allowedUsers: params.spec.modal.allowedUsers } : {} }); const { component, entry } = createButtonComponent({ spec: { label: params.spec.modal.triggerLabel ?? "Open form", style: params.spec.modal.triggerStyle ?? "primary", allowedUsers: params.spec.modal.allowedUsers }, modalId }); if (entry) addEntry(entry); const lastChild = containerChildren.at(-1); if (lastChild instanceof Row) { const row = lastChild; const hasSelect = row.components.some((entryLocal) => isSelectComponent(entryLocal)); if (row.components.length < 5 && !hasSelect) row.addComponent(component); else containerChildren.push(new Row([component])); } else containerChildren.push(new Row([component])); } if (containerChildren.length === 0) throw new Error("components must include at least one block, text, or modal trigger"); const container = new Container(containerChildren, params.spec.container); components.push(container); const consumptionGroupEntryIds = entries.map((entry) => entry.id); for (const entry of entries) entry.consumptionGroupEntryIds = consumptionGroupEntryIds; return { components, entries, modals }; } function buildDiscordComponentMessageFlags(components) { return components.some((component) => component.isV2) ? MessageFlags.IsComponentsV2 : void 0; } //#endregion //#region extensions/discord/src/components.modal.ts const ModalBase = Modal ?? function ModalFallback() {}; function createModalFieldComponent(field) { if (field.type === "text") { class DynamicTextInput extends TextInput { constructor(..._args) { super(..._args); this.customId = field.id; this.style = mapTextInputStyle(field.style); this.placeholder = field.placeholder; this.required = field.required; this.minLength = field.minLength; this.maxLength = field.maxLength; } } return new DynamicTextInput(); } if (field.type === "select") { const options = field.options ?? []; class DynamicModalSelect extends StringSelectMenu { constructor(..._args2) { super(..._args2); this.customId = field.id; this.options = options; this.required = field.required; this.minValues = field.minValues; this.maxValues = field.maxValues; this.placeholder = field.placeholder; } } return new DynamicModalSelect(); } if (field.type === "role-select") { class DynamicModalRoleSelect extends RoleSelectMenu { constructor(..._args3) { super(..._args3); this.customId = field.id; this.required = field.required; this.minValues = field.minValues; this.maxValues = field.maxValues; this.placeholder = field.placeholder; } } return new DynamicModalRoleSelect(); } if (field.type === "user-select") { class DynamicModalUserSelect extends UserSelectMenu { constructor(..._args4) { super(..._args4); this.customId = field.id; this.required = field.required; this.minValues = field.minValues; this.maxValues = field.maxValues; this.placeholder = field.placeholder; } } return new DynamicModalUserSelect(); } if (field.type === "checkbox") { const options = field.options ?? []; class DynamicCheckboxGroup extends CheckboxGroup { constructor(..._args5) { super(..._args5); this.customId = field.id; this.options = options; this.required = field.required; this.minValues = field.minValues; this.maxValues = field.maxValues; } } return new DynamicCheckboxGroup(); } const options = field.options ?? []; class DynamicRadioGroup extends RadioGroup { constructor(..._args6) { super(..._args6); this.customId = field.id; this.options = options; this.required = field.required; } } return new DynamicRadioGroup(); } var DiscordFormModal = class extends ModalBase { constructor(params) { super(); this.customIdParser = parseDiscordModalCustomIdForInteraction; this.title = params.title; this.customId = buildDiscordModalCustomId(params.modalId); this.components = params.fields.map((field) => { const component = createModalFieldComponent(field); class DynamicLabel extends Label { constructor(..._args7) { super(..._args7); this.label = field.label; this.description = field.description; this.component = component; this.customId = field.id; } } return new DynamicLabel(component); }); } async run() { throw new Error("Modal handler is not registered for dynamic forms"); } }; function createDiscordFormModal(entry) { return new DiscordFormModal({ modalId: entry.id, title: entry.title, fields: entry.fields }); } //#endregion //#region extensions/discord/src/components.ts function formatDiscordComponentEventText(params) { if (params.kind === "button") return `Clicked "${params.label}".`; const values = params.values ?? []; if (values.length === 0) return `Updated "${params.label}".`; return `Selected ${values.join(", ")} from "${params.label}".`; } //#endregion export { buildDiscordComponentMessageFlags as a, resolveDiscordComponentAttachmentName as c, buildDiscordComponentCustomId as d, buildDiscordModalCustomId as f, parseDiscordModalCustomIdForInteraction as g, parseDiscordModalCustomId as h, buildDiscordComponentMessage as i, DISCORD_COMPONENT_CUSTOM_ID_KEY as l, parseDiscordComponentCustomIdForInteraction as m, DiscordFormModal as n, DISCORD_COMPONENT_ATTACHMENT_PREFIX as o, parseDiscordComponentCustomId as p, createDiscordFormModal as r, readDiscordComponentSpec as s, formatDiscordComponentEventText as t, DISCORD_MODAL_CUSTOM_ID_KEY as u };