From 58bb3844ada9de5afdb37fd667017c8e7fac479b Mon Sep 17 00:00:00 2001 From: shnimlz Date: Thu, 18 Sep 2025 11:43:47 -0500 Subject: [PATCH] Elimino los embedBuilder y los cambio por Components Message V2, algo tardado. --- prisma/dev.db | Bin 69632 -> 0 bytes .../20250917001309_modals/migration.sql | 62 -- .../20250917002041_modals/migration.sql | 33 - .../20250917044008_modals/migration.sql | 13 - prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 26 +- .../createEmbed.backup.ts} | 226 +++++- .../editEmbed.backup.ts} | 4 +- .../messages/alliaces/createEmbedv2.ts | 729 +++++++++++++++++ src/commands/messages/alliaces/editEmbedv2.ts | 732 ++++++++++++++++++ src/commands/messages/alliaces/embedDelete.ts | 2 +- src/commands/messages/alliaces/embedList.ts | 2 +- src/core/lib/vars.ts | 22 +- src/events/extras/alliace.ts | 0 src/events/messageCreate.ts | 13 +- 15 files changed, 1730 insertions(+), 137 deletions(-) delete mode 100644 prisma/dev.db delete mode 100644 prisma/migrations/20250917001309_modals/migration.sql delete mode 100644 prisma/migrations/20250917002041_modals/migration.sql delete mode 100644 prisma/migrations/20250917044008_modals/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml rename src/{commands/messages/alliaces/createEmbed.ts => .backup/createEmbed.backup.ts} (56%) rename src/{commands/messages/alliaces/editEmbed.ts => .backup/editEmbed.backup.ts} (98%) create mode 100644 src/commands/messages/alliaces/createEmbedv2.ts create mode 100644 src/commands/messages/alliaces/editEmbedv2.ts create mode 100644 src/events/extras/alliace.ts diff --git a/prisma/dev.db b/prisma/dev.db deleted file mode 100644 index e160da733616ffb3d704e863390088f02b32aadb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69632 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 ({ + flags: 32768, + components: [ + { + type: 17, + components: [ + { + type: 10, + content: "﹒⌒    Options    ╰୧﹒" + }, + { + type: 14, + divider: true + }, + { + type: 1, + components: [ + { + style: 2, + type: 2, + label: "Titulo", + disabled: disabled, // 👈 aquí ya funciona + custom_id: "edit_title" + }, + { + style: 2, + type: 2, + label: "Descripción", + disabled: disabled, + custom_id: "edit_description" + }, + { + style: 2, + type: 2, + label: "Color", + disabled: disabled, + custom_id: "edit_color" + }, + { + style: 2, + type: 2, + label: "Imagen", + disabled: disabled, + custom_id: 'edit_imageurl' + }, + { + style: 2, + type: 2, + label: "Thumbnail", + disabled: disabled, + custom_id: 'edit_thumbnail' + } + ] + }, + { + type: 1, + components: [ + { + style: 2, + type: 2, + label: "Footer", + disabled: disabled, + custom_id: "edit_footer" + } + ] + }, + { + type: 1, + components: [ + { + style: 3, + type: 2, + label: "Guardar", + disabled: disabled, + custom_id: "save_embed" + }, + { + style: 4, + type: 2, + label: "Eliminar", + disabled: disabled, + custom_id: "cancel_embed" + } + ] + } + ], + accent_color: null + } + ] + }); + + + if (message.channel.type === ChannelType.GuildText) { const channel = message.channel as TextChannel; const editorMessage = await channel.send({ embeds: [await renderPreview()], - components: generateButtonRows(), + //components: generateButtonRows(), }); const collector = editorMessage.createMessageComponentCollector({ @@ -203,32 +381,51 @@ export const command: CommandMessage = { // Edición let promptContent = ""; - let fieldToEdit: "title" | "description" | "color" | "footer" | null = + let variableContent; + let fieldToEdit: "title" | "description" | "color" | "footer" | "image" | "thumbnail" | null = null; switch (i.customId) { case "edit_title": promptContent = - "Escribe el nuevo **título** (puedes usar variables como `{user.name}`)."; + "Escribe el nuevo **título** (puedes usar variables como `(guild.name)`)."; + variableContent = variables_text fieldToEdit = "title"; break; case "edit_description": promptContent = "Escribe la nueva **descripción** (puedes usar variables)."; + variableContent = variables_text fieldToEdit = "description"; break; case "edit_color": promptContent = "Escribe el nuevo **color** en formato hexadecimal (ej: `#FF0000`)."; + variableContent = variables_text fieldToEdit = "color"; break; case "edit_footer": promptContent = "Escribe el nuevo **texto del footer** (puedes usar variables)."; + variableContent = variables_text fieldToEdit = "footer"; break; + case "edit_imageurl": + promptContent = + "Pega el url **de la imagen** (puedes usar variables)."; + variableContent = variables_text + fieldToEdit = "image"; + break; + case "edit_thumbnail": + promptContent = + "Pega el url **del thumbnail** (puedes usar variables)."; + variableContent = variables_text + fieldToEdit = "thumbnail"; + break; } + //@ts-ignore + const variableMessage = await i.channel.send(variableContent) //@ts-ignore const promptMessage = await i.channel.send(promptContent); @@ -246,6 +443,9 @@ export const command: CommandMessage = { if (fieldToEdit === "title") embedState.title = newValue; if (fieldToEdit === "description") embedState.description = newValue; if (fieldToEdit === "footer") embedState.footer = newValue; + // added v0.0.1.1 + if (fieldToEdit === "image") embedState.imageUrl = newValue; + if (fieldToEdit === "thumbnail") embedState.thumbnail = newValue; if (fieldToEdit === "color") { try { @@ -258,6 +458,7 @@ export const command: CommandMessage = { await collectedMessage.delete(); await promptMessage.delete(); + await variableMessage.delete(); await editorMessage.edit({ embeds: [await renderPreview()], @@ -268,6 +469,7 @@ export const command: CommandMessage = { messageCollector.on("end", async (collected) => { if (collected.size === 0) { await promptMessage.delete(); + await variableMessage.delete(); await editorMessage.edit({ components: generateButtonRows(false), }); diff --git a/src/commands/messages/alliaces/editEmbed.ts b/src/.backup/editEmbed.backup.ts similarity index 98% rename from src/commands/messages/alliaces/editEmbed.ts rename to src/.backup/editEmbed.backup.ts index ea2d5d2..3b2e3af 100644 --- a/src/commands/messages/alliaces/editEmbed.ts +++ b/src/.backup/editEmbed.backup.ts @@ -1,9 +1,9 @@ -import { CommandMessage } from "../../../core/types/commands"; +import { CommandMessage } from "../core/types/commands"; // @ts-ignore import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, TextChannel, ChannelType } from "discord.js"; //@ts-ignore import { ButtonStyle, ComponentType } from "discord.js"; -import { replaceVars } from "../../../core/lib/vars"; +import { replaceVars } from "../core/lib/vars"; export const command: CommandMessage = { name: "editembed", diff --git a/src/commands/messages/alliaces/createEmbedv2.ts b/src/commands/messages/alliaces/createEmbedv2.ts new file mode 100644 index 0000000..e88370b --- /dev/null +++ b/src/commands/messages/alliaces/createEmbedv2.ts @@ -0,0 +1,729 @@ +import { CommandMessage } from "../../../core/types/commands"; +// @ts-ignore +import { ComponentType, ButtonStyle } from "discord.js"; +import { replaceVars } from "../../../core/lib/vars"; + +/** + * Botones de edición + */ +const btns = (disabled = false) => ([ + { + type: 1, + components: [ + { style: ButtonStyle.Secondary, type: 2, label: "Editar Título", disabled, custom_id: "edit_title" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Descripción", disabled, custom_id: "edit_description" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Color", disabled, custom_id: "edit_color" }, + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Contenido", disabled, custom_id: "add_content" }, + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Separador", disabled, custom_id: "add_separator" } + ] + }, + { + type: 1, + components: [ + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Imagen", disabled, custom_id: "add_image" }, + { style: ButtonStyle.Secondary, type: 2, label: "Imagen Portada", disabled, custom_id: "cover_image" }, + { style: ButtonStyle.Primary, type: 2, label: "Mover Bloque", disabled, custom_id: "move_block" }, + { style: ButtonStyle.Danger, type: 2, label: "Eliminar Bloque", disabled, custom_id: "delete_block" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Thumbnail", disabled, custom_id: "edit_thumbnail" } + ] + }, + { + type: 1, + components: [ + { style: ButtonStyle.Success, type: 2, label: "Guardar", disabled, custom_id: "save_block" }, + { style: ButtonStyle.Danger, type: 2, label: "Cancelar", disabled, custom_id: "cancel_block" } + ] + } +]); + +/** + * Validar si una URL es válida + */ +const isValidUrl = (url: string): boolean => { + if (!url || typeof url !== 'string') return false; + try { + new URL(url); + return url.startsWith('http://') || url.startsWith('https://'); + } catch { + return false; + } +}; + +/** + * Generar vista previa + */ +const renderPreview = async (blockState: any, member: any, guild: any) => { + const previewComponents = []; + + // Añadir imagen de portada primero si existe + if (blockState.coverImage && isValidUrl(blockState.coverImage)) { + //@ts-ignore + const processedCoverUrl = await replaceVars(blockState.coverImage, member, guild); + if (isValidUrl(processedCoverUrl)) { + previewComponents.push({ + type: 12, + items: [{ media: { url: processedCoverUrl } }] + }); + } + } + + // Añadir título después de la portada + previewComponents.push({ + type: 10, + //@ts-ignore + content: await replaceVars(blockState.title ?? "Sin título", member, guild) + }); + + // Procesar componentes en orden + for (const c of blockState.components) { + if (c.type === 10) { + // Componente de texto con thumbnail opcional + //@ts-ignore + const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, member, guild) : null; + + if (processedThumbnail && isValidUrl(processedThumbnail)) { + // Si tiene thumbnail válido, usar contenedor tipo 9 con accessory + previewComponents.push({ + type: 9, + components: [ + { + type: 10, + //@ts-ignore + content: await replaceVars(c.content || " ", member, guild) + } + ], + accessory: { + type: 11, + media: { url: processedThumbnail } + } + }); + } else { + // Sin thumbnail o thumbnail inválido, componente normal + previewComponents.push({ + type: 10, + //@ts-ignore + content: await replaceVars(c.content || " ", member, guild) + }); + } + } else if (c.type === 14) { + // Separador + previewComponents.push({ + type: 14, + divider: c.divider ?? true, + spacing: c.spacing ?? 1 + }); + } else if (c.type === 12) { + // Imagen - validar URL también + //@ts-ignore + const processedImageUrl = await replaceVars(c.url, member, guild); + + if (isValidUrl(processedImageUrl)) { + previewComponents.push({ + type: 12, + items: [{ media: { url: processedImageUrl } }] + }); + } + } + } + + return { + type: 17, + accent_color: blockState.color ?? null, + components: previewComponents + }; +}; + +export const command: CommandMessage = { + name: "blockcreatev2", + type: "message", + cooldown: 20, + run: async (message, args, client) => { + if (!message.member?.permissions.has("Administrator")) { + return message.reply("❌ No tienes permisos de Administrador."); + } + + const blockName: string | null = args[0] ?? null; + if (!blockName) { + return message.reply("Debes proporcionar un nombre. Uso: `!blockcreatev2 `"); + } + + const nameIsValid = await client.prisma.blockV2Config.findFirst({ + where: { guildId: message.guild!.id, name: blockName } + }); + if (nameIsValid) return message.reply("❌ Nombre ya usado!"); + + // Estado inicial + let blockState: any = { + title: `Editor de Block: ${blockName}`, + color: null, + coverImage: null, // Nueva propiedad para imagen de portada + components: [ + { type: 14, divider: false }, + { type: 10, content: "Usa los botones para configurar.", thumbnail: null } + ] + }; + + //@ts-ignore + const editorMessage = await message.channel.send({ + flags: 32768, + components: [ + await renderPreview(blockState, message.member, message.guild), + ...btns(false) + ] + }); + + const collector = editorMessage.createMessageComponentCollector({ + time: 300000 + }); + + collector.on("collect", async (i) => { + if (i.user.id !== message.author.id) { + await i.reply({ content: "No puedes usar este menú.", ephemeral: true }); + return; + } + + // --- BOTONES --- + if (i.isButton()) { + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(true)] + }); + await i.deferUpdate(); + + switch (i.customId) { + case "save_block": { + await client.prisma.blockV2Config.upsert({ + where: { guildId_name: { guildId: message.guildId!, name: blockName } }, + update: { config: blockState }, + create: { + name: blockName, + config: blockState, + guild: { + connectOrCreate: { + where: { id: message.guildId! }, + create: { id: message.guildId!, name: message.guild!.name } + } + } + } + }); + await editorMessage.edit({ + components: [ + { + type: 17, + accent_color: blockState.color ?? null, + components: [ + { type: 10, content: `✅ Guardado: ${blockName}` }, + { type: 10, content: "Configuración guardada en la base de datos (JSON)." } + ] + } + ] + }); + collector.stop(); + return; + } + case "cancel_block": { + await editorMessage.delete(); + collector.stop(); + return; + } + case "edit_title": { + const prompt = await message.channel.send("Escribe el nuevo **título**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.title = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "cover_image": { + if (blockState.coverImage) { + // Si ya tiene portada, preguntar si editar o eliminar + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Ya tienes una imagen de portada. ¿Qué quieres hacer?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: "✏️ Editar", custom_id: "edit_cover" }, + { type: 2, style: ButtonStyle.Danger, label: "🗑️ Eliminar", custom_id: "delete_cover" } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const coverCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + coverCollector.on("collect", async (b: any) => { + if (b.customId === "edit_cover") { + await b.update({ content: "Escribe la nueva **URL de la imagen de portada**:", components: [] }); + + const prompt = await message.channel.send("Nueva URL de portada:"); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + + mc.on("collect", async (collected) => { + blockState.coverImage = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } else if (b.customId === "delete_cover") { + blockState.coverImage = null; + await b.update({ content: "✅ Imagen de portada eliminada.", components: [] }); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + } + coverCollector.stop(); + }); + } else { + // No tiene portada, añadir nueva + const prompt = await message.channel.send("Escribe la **URL de la imagen de portada**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.coverImage = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } + break; + } + case "edit_description": { + const prompt = await message.channel.send("Escribe la nueva **descripción**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + const descComp = blockState.components.find((c: any) => c.type === 10); + if (descComp) descComp.content = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "edit_color": { + const prompt = await message.channel.send("Escribe el nuevo **color** en HEX (#RRGGBB)."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + const newValue = collected.content; + let parsed: number | null = null; + if (/^#?[0-9A-Fa-f]{6}$/.test(newValue)) { + parsed = parseInt(newValue.replace("#", ""), 16); + } + blockState.color = parsed; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "add_content": { + const prompt = await message.channel.send("Escribe el nuevo **contenido**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.components.push({ type: 10, content: collected.content, thumbnail: null }); + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "add_separator": { + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "¿El separador debe ser visible?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Success, label: "✅ Visible", custom_id: "separator_visible" }, + { type: 2, style: ButtonStyle.Secondary, label: "❌ Invisible", custom_id: "separator_invisible" } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const sepCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + sepCollector.on("collect", async (b: any) => { + const isVisible = b.customId === "separator_visible"; + blockState.components.push({ type: 14, divider: isVisible, spacing: 1 }); + + await b.update({ + content: `✅ Separador ${isVisible ? 'visible' : 'invisible'} añadido.`, + components: [] + }); + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + sepCollector.stop(); + }); + break; + } + case "add_image": { + const prompt = await message.channel.send("Escribe la **URL de la imagen**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.components.push({ type: 12, url: collected.content }); + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "edit_thumbnail": { + // Buscar componentes de texto para seleccionar cuál editar + const textComponents = blockState.components + .map((c: any, idx: number) => ({ component: c, index: idx })) + .filter(({ component }) => component.type === 10); + + if (textComponents.length === 0) { + //@ts-ignore + await i.followUp({ + content: "❌ No hay componentes de texto para añadir thumbnail.", + ephemeral: true + }); + break; + } + + if (textComponents.length === 1) { + // Solo un componente de texto, editarlo directamente + const prompt = await message.channel.send("Escribe la **URL del thumbnail**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + textComponents[0].component.thumbnail = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } else { + // Múltiples componentes de texto, mostrar selector + const options = textComponents.map(({ component, index }) => ({ + label: `Texto: ${component.content?.slice(0, 30) || "..."}`, + value: index.toString(), + description: component.thumbnail ? "Ya tiene thumbnail" : "Sin thumbnail" + })); + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el texto al que quieres añadir/editar thumbnail:", + components: [ + { + type: 1, + components: [ + { + type: 3, + custom_id: "select_text_for_thumbnail", + placeholder: "Elige un texto", + options + } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (sel: any) => sel.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const selectedIndex = parseInt(sel.values[0]); + + await sel.update({ + content: "Escribe la **URL del thumbnail**:", + components: [] + }); + + const prompt = await message.channel.send("URL del thumbnail:"); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + + mc.on("collect", async (collected) => { + blockState.components[selectedIndex].thumbnail = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + }); + } + break; + } + case "move_block": { + const options = blockState.components.map((c: any, idx: number) => ({ + label: + c.type === 10 + ? `Texto: ${c.content?.slice(0, 30) || "..."}` + : c.type === 14 + ? "Separador" + : c.type === 12 + ? `Imagen: ${c.url?.slice(-30) || "..."}` + : `Componente ${c.type}`, + value: idx.toString(), + description: + c.type === 10 && c.thumbnail + ? "Con thumbnail" + : undefined + })); + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el bloque que quieres mover:", + components: [ + { + type: 1, + components: [ + { type: 3, custom_id: "move_block_select", placeholder: "Elige un bloque", options } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (it: any) => it.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const idx = parseInt(sel.values[0]); + + await sel.update({ + content: "¿Quieres mover este bloque?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Secondary, label: "⬆️ Subir", custom_id: `move_up_${idx}`, disabled: idx === 0 }, + { type: 2, style: ButtonStyle.Secondary, label: "⬇️ Bajar", custom_id: `move_down_${idx}`, disabled: idx === blockState.components.length - 1 } + ] + } + ] + }); + + //@ts-ignore + const btnCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + btnCollector.on("collect", async (b: any) => { + if (b.customId.startsWith("move_up_")) { + const i2 = parseInt(b.customId.replace("move_up_", "")); + if (i2 > 0) { + const item = blockState.components[i2]; + blockState.components.splice(i2, 1); + blockState.components.splice(i2 - 1, 0, item); + } + await b.update({ content: "✅ Bloque movido arriba.", components: [] }); + } else if (b.customId.startsWith("move_down_")) { + const i2 = parseInt(b.customId.replace("move_down_", "")); + if (i2 < blockState.components.length - 1) { + const item = blockState.components[i2]; + blockState.components.splice(i2, 1); + blockState.components.splice(i2 + 1, 0, item); + } + await b.update({ content: "✅ Bloque movido abajo.", components: [] }); + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + btnCollector.stop(); + selCollector.stop(); + }); + }); + + break; + } + case "delete_block": { + // Incluir portada en las opciones si existe + const options = []; + + // Añadir portada como opción si existe + if (blockState.coverImage) { + options.push({ + label: "🖼️ Imagen de Portada", + value: "cover_image", + description: "Imagen principal del bloque" + }); + } + + // Añadir componentes regulares + blockState.components.forEach((c: any, idx: number) => { + options.push({ + label: + c.type === 10 + ? `Texto: ${c.content?.slice(0, 30) || "..."}` + : c.type === 14 + ? `Separador ${c.divider ? '(Visible)' : '(Invisible)'}` + : c.type === 12 + ? `Imagen: ${c.url?.slice(-30) || "..."}` + : `Componente ${c.type}`, + value: idx.toString(), + description: + c.type === 10 && c.thumbnail + ? "Con thumbnail" + : undefined + }); + }); + + if (options.length === 0) { + //@ts-ignore + await i.followUp({ + content: "❌ No hay elementos para eliminar.", + ephemeral: true + }); + break; + } + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el elemento que quieres eliminar:", + components: [ + { + type: 1, + components: [ + { type: 3, custom_id: "delete_block_select", placeholder: "Elige un elemento", options } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (it: any) => it.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const selectedValue = sel.values[0]; + + if (selectedValue === "cover_image") { + blockState.coverImage = null; + await sel.update({ content: "✅ Imagen de portada eliminada.", components: [] }); + } else { + const idx = parseInt(selectedValue); + blockState.components.splice(idx, 1); + await sel.update({ content: "✅ Elemento eliminado.", components: [] }); + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + selCollector.stop(); + }); + + break; + } + default: + break; + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + } + }); + + //@ts-ignore + collector.on("end", async (_, reason) => { + if (reason === "time") { + await editorMessage.edit({ + components: [ + { type: 17, components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }] } + ] + }); + } + }); + } +}; \ No newline at end of file diff --git a/src/commands/messages/alliaces/editEmbedv2.ts b/src/commands/messages/alliaces/editEmbedv2.ts new file mode 100644 index 0000000..091ea22 --- /dev/null +++ b/src/commands/messages/alliaces/editEmbedv2.ts @@ -0,0 +1,732 @@ +import { CommandMessage } from "../../../core/types/commands"; +// @ts-ignore +import { ComponentType, ButtonStyle } from "discord.js"; +import { replaceVars } from "../../../core/lib/vars"; + +/** + * Validar si una URL es válida + */ +const isValidUrl = (url: string): boolean => { + if (!url || typeof url !== 'string') return false; + try { + new URL(url); + return url.startsWith('http://') || url.startsWith('https://'); + } catch { + return false; + } +}; + +/** + * Botones de edición + */ +const btns = (disabled = false) => ([ + { + type: 1, + components: [ + { style: ButtonStyle.Secondary, type: 2, label: "Editar Título", disabled, custom_id: "edit_title" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Descripción", disabled, custom_id: "edit_description" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Color", disabled, custom_id: "edit_color" }, + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Contenido", disabled, custom_id: "add_content" }, + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Separador", disabled, custom_id: "add_separator" } + ] + }, + { + type: 1, + components: [ + { style: ButtonStyle.Secondary, type: 2, label: "Añadir Imagen", disabled, custom_id: "add_image" }, + { style: ButtonStyle.Secondary, type: 2, label: "Imagen Portada", disabled, custom_id: "cover_image" }, + { style: ButtonStyle.Primary, type: 2, label: "Mover Bloque", disabled, custom_id: "move_block" }, + { style: ButtonStyle.Danger, type: 2, label: "Eliminar Bloque", disabled, custom_id: "delete_block" }, + { style: ButtonStyle.Secondary, type: 2, label: "Editar Thumbnail", disabled, custom_id: "edit_thumbnail" } + ] + }, + { + type: 1, + components: [ + { style: ButtonStyle.Success, type: 2, label: "Guardar", disabled, custom_id: "save_block" }, + { style: ButtonStyle.Danger, type: 2, label: "Cancelar", disabled, custom_id: "cancel_block" } + ] + } +]); + +/** + * Generar vista previa + */ +const renderPreview = async (blockState: any, member: any, guild: any) => { + const previewComponents = []; + + // Añadir imagen de portada primero si existe + if (blockState.coverImage && isValidUrl(blockState.coverImage)) { + //@ts-ignore + const processedCoverUrl = await replaceVars(blockState.coverImage, member, guild); + if (isValidUrl(processedCoverUrl)) { + previewComponents.push({ + type: 12, + items: [{ media: { url: processedCoverUrl } }] + }); + } + } + + // Añadir título después de la portada + previewComponents.push({ + type: 10, + //@ts-ignore + content: await replaceVars(blockState.title ?? "Sin título", member, guild) + }); + + // Procesar componentes en orden + for (const c of blockState.components) { + if (c.type === 10) { + // Componente de texto con thumbnail opcional + //@ts-ignore + const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, member, guild) : null; + + if (processedThumbnail && isValidUrl(processedThumbnail)) { + // Si tiene thumbnail válido, usar contenedor tipo 9 con accessory + previewComponents.push({ + type: 9, + components: [ + { + type: 10, + //@ts-ignore + content: await replaceVars(c.content || " ", member, guild) + } + ], + accessory: { + type: 11, + media: { url: processedThumbnail } + } + }); + } else { + // Sin thumbnail o thumbnail inválido, componente normal + previewComponents.push({ + type: 10, + //@ts-ignore + content: await replaceVars(c.content || " ", member, guild) + }); + } + } else if (c.type === 14) { + // Separador + previewComponents.push({ + type: 14, + divider: c.divider ?? true, + spacing: c.spacing ?? 1 + }); + } else if (c.type === 12) { + // Imagen - validar URL también + //@ts-ignore + const processedImageUrl = await replaceVars(c.url, member, guild); + + if (isValidUrl(processedImageUrl)) { + previewComponents.push({ + type: 12, + items: [{ media: { url: processedImageUrl } }] + }); + } + } + } + + return { + type: 17, + accent_color: blockState.color ?? null, + components: previewComponents + }; +}; + +export const command: CommandMessage = { + name: "blockedit", + type: "message", + cooldown: 20, + run: async (message, args, client) => { + if (!message.member?.permissions.has("Administrator")) { + return message.reply("❌ No tienes permisos de Administrador."); + } + + const blockName: string | null = args[0] ?? null; + if (!blockName) { + return message.reply("Debes proporcionar un nombre. Uso: `!blockedit `"); + } + + // Buscar el block existente + const existingBlock = await client.prisma.blockV2Config.findFirst({ + where: { guildId: message.guild!.id, name: blockName } + }); + + if (!existingBlock) { + return message.reply(`❌ No se encontró un block con el nombre: **${blockName}**`); + } + + // Cargar configuración existente + let blockState: any = { + ...existingBlock.config, + // Asegurar que las propiedades necesarias existan + title: existingBlock.config.title || `Block: ${blockName}`, + color: existingBlock.config.color || null, + coverImage: existingBlock.config.coverImage || null, + components: existingBlock.config.components || [] + }; + + //@ts-ignore + const editorMessage = await message.channel.send({ + flags: 32768, + components: [ + await renderPreview(blockState, message.member, message.guild), + ...btns(false) + ] + }); + + const collector = editorMessage.createMessageComponentCollector({ + time: 300000 + }); + + collector.on("collect", async (i) => { + if (i.user.id !== message.author.id) { + await i.reply({ content: "No puedes usar este menú.", ephemeral: true }); + return; + } + + // --- BOTONES --- + if (i.isButton()) { + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(true)] + }); + await i.deferUpdate(); + + switch (i.customId) { + case "save_block": { + await client.prisma.blockV2Config.update({ + where: { guildId_name: { guildId: message.guildId!, name: blockName } }, + data: { config: blockState } + }); + await editorMessage.edit({ + components: [ + { + type: 17, + accent_color: blockState.color ?? null, + components: [ + { type: 10, content: `✅ Block actualizado: ${blockName}` }, + { type: 10, content: "Configuración guardada exitosamente." } + ] + } + ] + }); + collector.stop(); + return; + } + case "cancel_block": { + await editorMessage.edit({ + components: [ + { + type: 17, + components: [ + { type: 10, content: "❌ Edición cancelada." }, + { type: 10, content: "No se guardaron los cambios." } + ] + } + ] + }); + collector.stop(); + return; + } + case "edit_title": { + const prompt = await message.channel.send("Escribe el nuevo **título**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.title = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "edit_description": { + const prompt = await message.channel.send("Escribe la nueva **descripción**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + const descComp = blockState.components.find((c: any) => c.type === 10); + if (descComp) descComp.content = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "edit_color": { + const prompt = await message.channel.send("Escribe el nuevo **color** en HEX (#RRGGBB)."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + const newValue = collected.content; + let parsed: number | null = null; + if (/^#?[0-9A-Fa-f]{6}$/.test(newValue)) { + parsed = parseInt(newValue.replace("#", ""), 16); + } + blockState.color = parsed; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "add_content": { + const prompt = await message.channel.send("Escribe el nuevo **contenido**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.components.push({ type: 10, content: collected.content, thumbnail: null }); + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "add_separator": { + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "¿El separador debe ser visible?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Success, label: "✅ Visible", custom_id: "separator_visible" }, + { type: 2, style: ButtonStyle.Secondary, label: "❌ Invisible", custom_id: "separator_invisible" } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const sepCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + sepCollector.on("collect", async (b: any) => { + const isVisible = b.customId === "separator_visible"; + blockState.components.push({ type: 14, divider: isVisible, spacing: 1 }); + + await b.update({ + content: `✅ Separador ${isVisible ? 'visible' : 'invisible'} añadido.`, + components: [] + }); + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + sepCollector.stop(); + }); + break; + } + case "add_image": { + const prompt = await message.channel.send("Escribe la **URL de la imagen**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.components.push({ type: 12, url: collected.content }); + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + break; + } + case "cover_image": { + if (blockState.coverImage) { + // Si ya tiene portada, preguntar si editar o eliminar + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Ya tienes una imagen de portada. ¿Qué quieres hacer?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: "✏️ Editar", custom_id: "edit_cover" }, + { type: 2, style: ButtonStyle.Danger, label: "🗑️ Eliminar", custom_id: "delete_cover" } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const coverCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + coverCollector.on("collect", async (b: any) => { + if (b.customId === "edit_cover") { + await b.update({ content: "Escribe la nueva **URL de la imagen de portada**:", components: [] }); + + const prompt = await message.channel.send("Nueva URL de portada:"); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + + mc.on("collect", async (collected) => { + blockState.coverImage = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } else if (b.customId === "delete_cover") { + blockState.coverImage = null; + await b.update({ content: "✅ Imagen de portada eliminada.", components: [] }); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + } + coverCollector.stop(); + }); + } else { + // No tiene portada, añadir nueva + const prompt = await message.channel.send("Escribe la **URL de la imagen de portada**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + blockState.coverImage = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } + break; + } + case "edit_thumbnail": { + // Buscar componentes de texto para seleccionar cuál editar + const textComponents = blockState.components + .map((c: any, idx: number) => ({ component: c, index: idx })) + .filter(({ component }) => component.type === 10); + + if (textComponents.length === 0) { + //@ts-ignore + await i.followUp({ + content: "❌ No hay componentes de texto para añadir thumbnail.", + ephemeral: true + }); + break; + } + + if (textComponents.length === 1) { + // Solo un componente de texto, editarlo directamente + const prompt = await message.channel.send("Escribe la **URL del thumbnail**."); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + mc.on("collect", async (collected) => { + textComponents[0].component.thumbnail = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + } else { + // Múltiples componentes de texto, mostrar selector + const options = textComponents.map(({ component, index }) => ({ + label: `Texto: ${component.content?.slice(0, 30) || "..."}`, + value: index.toString(), + description: component.thumbnail ? "Ya tiene thumbnail" : "Sin thumbnail" + })); + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el texto al que quieres añadir/editar thumbnail:", + components: [ + { + type: 1, + components: [ + { + type: 3, + custom_id: "select_text_for_thumbnail", + placeholder: "Elige un texto", + options + } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (sel: any) => sel.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const selectedIndex = parseInt(sel.values[0]); + + await sel.update({ + content: "Escribe la **URL del thumbnail**:", + components: [] + }); + + const prompt = await message.channel.send("URL del thumbnail:"); + const mc = message.channel.createMessageCollector({ + filter: (m) => m.author.id === message.author.id, + max: 1, + time: 60000 + }); + + mc.on("collect", async (collected) => { + blockState.components[selectedIndex].thumbnail = collected.content; + await collected.delete(); + await prompt.delete(); + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + }); + }); + } + break; + } + case "move_block": { + const options = blockState.components.map((c: any, idx: number) => ({ + label: + c.type === 10 + ? `Texto: ${c.content?.slice(0, 30) || "..."}` + : c.type === 14 + ? `Separador ${c.divider ? '(Visible)' : '(Invisible)'}` + : c.type === 12 + ? `Imagen: ${c.url?.slice(-30) || "..."}` + : `Componente ${c.type}`, + value: idx.toString(), + description: + c.type === 10 && c.thumbnail + ? "Con thumbnail" + : undefined + })); + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el bloque que quieres mover:", + components: [ + { + type: 1, + components: [ + { type: 3, custom_id: "move_block_select", placeholder: "Elige un bloque", options } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (it: any) => it.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const idx = parseInt(sel.values[0]); + + await sel.update({ + content: "¿Quieres mover este bloque?", + components: [ + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Secondary, label: "⬆️ Subir", custom_id: `move_up_${idx}`, disabled: idx === 0 }, + { type: 2, style: ButtonStyle.Secondary, label: "⬇️ Bajar", custom_id: `move_down_${idx}`, disabled: idx === blockState.components.length - 1 } + ] + } + ] + }); + + //@ts-ignore + const btnCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + time: 60000, + filter: (b: any) => b.user.id === message.author.id + }); + + btnCollector.on("collect", async (b: any) => { + if (b.customId.startsWith("move_up_")) { + const i2 = parseInt(b.customId.replace("move_up_", "")); + if (i2 > 0) { + const item = blockState.components[i2]; + blockState.components.splice(i2, 1); + blockState.components.splice(i2 - 1, 0, item); + } + await b.update({ content: "✅ Bloque movido arriba.", components: [] }); + } else if (b.customId.startsWith("move_down_")) { + const i2 = parseInt(b.customId.replace("move_down_", "")); + if (i2 < blockState.components.length - 1) { + const item = blockState.components[i2]; + blockState.components.splice(i2, 1); + blockState.components.splice(i2 + 1, 0, item); + } + await b.update({ content: "✅ Bloque movido abajo.", components: [] }); + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + btnCollector.stop(); + selCollector.stop(); + }); + }); + + break; + } + case "delete_block": { + // Incluir portada en las opciones si existe + const options = []; + + // Añadir portada como opción si existe + if (blockState.coverImage) { + options.push({ + label: "🖼️ Imagen de Portada", + value: "cover_image", + description: "Imagen principal del bloque" + }); + } + + // Añadir componentes regulares + blockState.components.forEach((c: any, idx: number) => { + options.push({ + label: + c.type === 10 + ? `Texto: ${c.content?.slice(0, 30) || "..."}` + : c.type === 14 + ? `Separador ${c.divider ? '(Visible)' : '(Invisible)'}` + : c.type === 12 + ? `Imagen: ${c.url?.slice(-30) || "..."}` + : `Componente ${c.type}`, + value: idx.toString(), + description: + c.type === 10 && c.thumbnail + ? "Con thumbnail" + : undefined + }); + }); + + if (options.length === 0) { + //@ts-ignore + await i.followUp({ + content: "❌ No hay elementos para eliminar.", + ephemeral: true + }); + break; + } + + //@ts-ignore + const reply = await i.followUp({ + ephemeral: true, + content: "Selecciona el elemento que quieres eliminar:", + components: [ + { + type: 1, + components: [ + { type: 3, custom_id: "delete_block_select", placeholder: "Elige un elemento", options } + ] + } + ], + fetchReply: true + }); + + //@ts-ignore + const selCollector = reply.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + max: 1, + time: 60000, + filter: (it: any) => it.user.id === message.author.id + }); + + selCollector.on("collect", async (sel: any) => { + const selectedValue = sel.values[0]; + + if (selectedValue === "cover_image") { + blockState.coverImage = null; + await sel.update({ content: "✅ Imagen de portada eliminada.", components: [] }); + } else { + const idx = parseInt(selectedValue); + blockState.components.splice(idx, 1); + await sel.update({ content: "✅ Elemento eliminado.", components: [] }); + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + + selCollector.stop(); + }); + + break; + } + default: + break; + } + + await editorMessage.edit({ + components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] + }); + } + }); + + //@ts-ignore + collector.on("end", async (_, reason) => { + if (reason === "time") { + await editorMessage.edit({ + components: [ + { type: 17, components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }] } + ] + }); + } + }); + } +}; \ No newline at end of file diff --git a/src/commands/messages/alliaces/embedDelete.ts b/src/commands/messages/alliaces/embedDelete.ts index c033abd..772edf2 100644 --- a/src/commands/messages/alliaces/embedDelete.ts +++ b/src/commands/messages/alliaces/embedDelete.ts @@ -17,7 +17,7 @@ export const command: CommandMessage = { } try { - await client.prisma.embedConfig.delete({ + await client.prisma.blockV2Config.delete({ where: { guildId_name: { guildId: message.guildId!, diff --git a/src/commands/messages/alliaces/embedList.ts b/src/commands/messages/alliaces/embedList.ts index e4d13cb..4e5ef7a 100644 --- a/src/commands/messages/alliaces/embedList.ts +++ b/src/commands/messages/alliaces/embedList.ts @@ -24,7 +24,7 @@ export const command: CommandMessage = { return message.reply("❌ No tienes permisos de Administrador."); } - const embeds = await client.prisma.embedConfig.findMany({ + const embeds = await client.prisma.blockV2Config.findMany({ where: { guildId: message.guildId! }, }); diff --git a/src/core/lib/vars.ts b/src/core/lib/vars.ts index 7578e36..ab0cc69 100644 --- a/src/core/lib/vars.ts +++ b/src/core/lib/vars.ts @@ -1,12 +1,20 @@ import {Guild, User} from "discord.js"; -export async function replaceVars(text: string, user: User | undefined, guild: -Guild | undefined, stats: any) { - if(!text) return; +export async function replaceVars(text: string, user: User | undefined, guild: Guild | undefined, stats?: any): Promise { + if(!text) return ''; return text - .replace(/(user.name)/g, user!.username ?? '') - .replace(/(user.id)/g, user!.id ?? '') - .replace(/(user.mention)/g, `<@${user!.id}>`) - .replace(/(user.avatar)/g, user!.displayAvatarURL({ forceStatic: false })) + /** + * USER INFO + */ + .replace(/(user\.name)/g, user?.username ?? '') + .replace(/(user\.id)/g, user?.id ?? '') + .replace(/(user\.mention)/g, user ? `<@${user.id}>` : '') + .replace(/(user\.avatar)/g, user?.displayAvatarURL({ forceStatic: false }) ?? '') + + /** + * GUILD INFO + */ + .replace(/(guild\.name)/g, guild?.name ?? '') + .replace(/(guild\.icon)/g, guild?.iconURL({ forceStatic: false }) ?? ''); } \ No newline at end of file diff --git a/src/events/extras/alliace.ts b/src/events/extras/alliace.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index f64d63f..4311b64 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -6,8 +6,17 @@ import {commands} from "../core/loader"; bot.on(Events.MessageCreate, async (message) => { if (message.author.bot) return; - const server = await bot.prisma.guild.findFirst({ where: { id: message.guild!.id } }) || "!"; - const PREFIX = server.prefix + const server = await bot.prisma.guild.upsert({ + where: { + id: message.guildId + }, + create: { + id: message.guildId, + name: message.guild!.name + }, + update: {} + }) + const PREFIX = server.prefix || "!" if (!message.content.startsWith(PREFIX)) return; const [cmdName, ...args] = message.content.slice(PREFIX.length).trim().split(/\s+/);