feat: add emoji parsing and link accessory building for display components

This commit is contained in:
2025-09-20 18:05:19 -05:00
parent 62792633a6
commit 6f06b0b99e
2 changed files with 60 additions and 54 deletions

View File

@@ -416,7 +416,7 @@ export const command: CommandMessage = {
break; break;
} }
// @ts-ignore // @ts-ignore
const reply = await i.reply({ flags: 64, content: 'Selecciona el elemento que quieres duplicar:', components: [{ type: 1, components: [{ type: 3, custom_id: 'duplicate_select', placeholder: 'Elige un elemento', options }] }], fetchReply: true }); await i.reply({ flags: 64, content: 'Selecciona el elemento que quieres duplicar:', components: [{ type: 1, components: [{ type: 3, custom_id: 'duplicate_select', placeholder: 'Elige un elemento', options }] }] });
// @ts-ignore // @ts-ignore
const replyMsg = await i.fetchReply(); const replyMsg = await i.fetchReply();
// @ts-ignore // @ts-ignore

View File

@@ -282,19 +282,55 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil
} }
} }
// Helper: parsear emojis (unicode o personalizados <:name:id> / <a:name:id>)
function parseEmojiInput(input?: string): any | null {
if (!input) return null;
const trimmed = input.trim();
if (!trimmed) return null;
const match = trimmed.match(/^<(a?):(\w+):(\d+)>$/);
if (match) {
const animated = match[1] === 'a';
const name = match[2];
const id = match[3];
return { id, name, animated };
}
// Asumimos unicode si no es formato de emoji personalizado
return { name: trimmed };
}
// Helper: construir accessory de Link Button para Display Components
async function buildLinkAccessory(link: any, user: any, guild: any) {
try {
if (!link || !link.url) return null;
// @ts-ignore
const processedUrl = await replaceVars(link.url, user, guild);
if (!isValidUrl(processedUrl)) return null;
const accessory: any = { type: 2, style: 5, url: processedUrl };
if (link.label && typeof link.label === 'string' && link.label.trim()) {
accessory.label = link.label.trim().slice(0, 80);
}
if (link.emoji && typeof link.emoji === 'string') {
const parsed = parseEmojiInput(link.emoji);
if (parsed) accessory.emoji = parsed;
}
// Debe tener al menos label o emoji
if (!accessory.label && !accessory.emoji) return null;
return accessory;
} catch {
return null;
}
}
async function convertConfigToDisplayComponent(config: any, user: any, guild: any): Promise<any> { async function convertConfigToDisplayComponent(config: any, user: any, guild: any): Promise<any> {
try { try {
const previewComponents = []; const previewComponents: any[] = [];
// Añadir imagen de portada primero si existe // Añadir imagen de portada primero si existe
if (config.coverImage && isValidUrl(config.coverImage)) { if (config.coverImage && isValidUrl(config.coverImage)) {
// @ts-ignore // @ts-ignore
const processedCoverUrl = await replaceVars(config.coverImage, user, guild); const processedCoverUrl = await replaceVars(config.coverImage, user, guild);
if (isValidUrl(processedCoverUrl)) { if (isValidUrl(processedCoverUrl)) {
previewComponents.push({ previewComponents.push({ type: 12, items: [{ media: { url: processedCoverUrl } }] });
type: 12,
items: [{ media: { url: processedCoverUrl } }]
});
} }
} }
@@ -311,74 +347,44 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
if (config.components && Array.isArray(config.components)) { if (config.components && Array.isArray(config.components)) {
for (const c of config.components) { for (const c of config.components) {
if (c.type === 10) { if (c.type === 10) {
// Componente de texto con thumbnail opcional // Texto con accessory opcional: priorizar linkButton > thumbnail
// @ts-ignore
const processedContent = await replaceVars(c.content || " ", user, guild);
// @ts-ignore // @ts-ignore
const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, user, guild) : null; const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, user, guild) : null;
if (processedThumbnail && isValidUrl(processedThumbnail)) { let accessory: any = null;
// Si tiene thumbnail válido, usar contenedor tipo 9 con accessory if (c.linkButton) {
previewComponents.push({ accessory = await buildLinkAccessory(c.linkButton, user, guild);
type: 9,
components: [
{
type: 10,
// @ts-ignore
content: await replaceVars(c.content || " ", user, guild)
} }
], if (!accessory && processedThumbnail && isValidUrl(processedThumbnail)) {
accessory: { accessory = { type: 11, media: { url: processedThumbnail } };
type: 11,
media: { url: processedThumbnail }
} }
});
if (accessory) {
previewComponents.push({ type: 9, components: [{ type: 10, content: processedContent }], accessory });
} else { } else {
// Sin thumbnail o thumbnail inválido, componente normal previewComponents.push({ type: 10, content: processedContent });
previewComponents.push({
type: 10,
// @ts-ignore
content: await replaceVars(c.content || " ", user, guild)
});
} }
} else if (c.type === 14) { } else if (c.type === 14) {
// Separador previewComponents.push({ type: 14, divider: c.divider ?? true, spacing: c.spacing ?? 1 });
previewComponents.push({
type: 14,
divider: c.divider ?? true,
spacing: c.spacing ?? 1
});
} else if (c.type === 12) { } else if (c.type === 12) {
// Imagen - validar URL también // Imagen - validar URL también
// @ts-ignore // @ts-ignore
const processedImageUrl = await replaceVars(c.url, user, guild); const processedImageUrl = await replaceVars(c.url, user, guild);
if (isValidUrl(processedImageUrl)) { if (isValidUrl(processedImageUrl)) {
previewComponents.push({ previewComponents.push({ type: 12, items: [{ media: { url: processedImageUrl } }] });
type: 12,
items: [{ media: { url: processedImageUrl } }]
});
} }
} }
} }
} }
// Retornar la estructura exacta que usa el editor // Retornar la estructura exacta que usa el editor
return { return { type: 17, accent_color: config.color ?? null, components: previewComponents };
type: 17, // Container type
accent_color: config.color ?? null,
components: previewComponents
};
} catch (error) { } catch (error) {
console.error('Error convirtiendo configuración a Display Component:', error); console.error('Error convirtiendo configuración a Display Component:', error);
return { type: 17, accent_color: null, components: [ { type: 10, content: 'Error al procesar la configuración del bloque.' } ] };
// Fallback: crear un componente básico
return {
type: 17,
accent_color: null,
components: [
{ type: 10, content: 'Error al procesar la configuración del bloque.' }
]
};
} }
} }