From 4b91fbd1f48b2c34b5c31b35c4cac9bcc881e7fb Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 25 May 2026 13:11:06 -0300 Subject: [PATCH 01/12] Update bot.js --- src/config/bot.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/bot.js b/src/config/bot.js index 36e588cd4..ca1dfe358 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -25,7 +25,7 @@ export const botConfig = { activities: [ { // Text users will see (example: "Playing /help | Titan Bot"). - name: "Made with ❤️", + name: "00Y4n Comunidad SWFL", // Activity type number (0 = Playing). type: 0, }, @@ -88,7 +88,7 @@ export const botConfig = { embeds: { colors: { // Main brand colors. - primary: "#336699", + primary: "#ff6600", secondary: "#2F3136", // Standard status colors for success/error/warning/info messages. From b3596be3dcc1b3963cb73943aaf7588fa66ee686 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 25 May 2026 13:17:37 -0300 Subject: [PATCH 02/12] Update bot.js --- src/config/bot.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/config/bot.js b/src/config/bot.js index ca1dfe358..870d6ae00 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -89,13 +89,13 @@ export const botConfig = { colors: { // Main brand colors. primary: "#ff6600", - secondary: "#2F3136", + secondary: "#ff6600", // Standard status colors for success/error/warning/info messages. - success: "#57F287", + success: "#ff6600", error: "#ED4245", - warning: "#FEE75C", - info: "#3498DB", + warning: "#ff6600", + info: "#ff6600", // Neutral utility colors. light: "#FFFFFF", @@ -112,18 +112,18 @@ export const botConfig = { // Feature-specific colors. giveaway: { - active: "#57F287", - ended: "#ED4245", + active: "#ff6600", + ended: "#ff6600", }, ticket: { - open: "#57F287", - claimed: "#FAA61A", - closed: "#ED4245", - pending: "#99AAB5", + open: "#ff6600", + claimed: "#ff6600", + closed: "#ff6600", + pending: "#ff6600", }, - economy: "#F1C40F", - birthday: "#E91E63", - moderation: "#9B59B6", + economy: "#ff6600", + birthday: "#ff6600", + moderation: "#ff6600", // Ticket priority color mapping. priority: { From 558c3f67559ded9ad01d1fca1d37f311dc395960 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:08:49 -0300 Subject: [PATCH 03/12] Create sessiones.js --- src/commands/sessiones.js | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/commands/sessiones.js diff --git a/src/commands/sessiones.js b/src/commands/sessiones.js new file mode 100644 index 000000000..686f8c0f4 --- /dev/null +++ b/src/commands/sessiones.js @@ -0,0 +1,119 @@ +import { ApplicationCommandOptionType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; + +// Mapa global en memoria que compartiremos con los eventos +if (!global.mapaVotos) { + global.mapaVotos = new Map(); +} + +export const data = { + name: 'sesiones_00y4n', + description: 'Gestión de sesiones de Roleplay y Car Meets', + // Aquí registramos los subcomandos para que queden prolijos + options: [ + { + name: 'startup_rp', + description: 'Lanza un inicio de sesión de Roleplay convencional.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'reacciones', description: 'Reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'limite', description: 'Ejemplo: 80 MPH', type: ApplicationCommandOptionType.String, required: false }, + { name: 'peacetime', description: '¿Peacetime activo? (On / Off)', type: ApplicationCommandOptionType.String, required: false } + ] + }, + { + name: 'startup_meet', + description: 'Lanza un inicio de sesión para un Car Meet.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'reacciones', description: 'Reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'tematica', description: 'Ejemplo: JDM, Exóticos', type: ApplicationCommandOptionType.String, required: true }, + { name: 'spots', description: 'Ejemplo: 2-3 SPOTS + BOTM', type: ApplicationCommandOptionType.String, required: true }, + { name: 'ubicacion', description: 'Lugar de inicio (Ej: Spawn)', type: ApplicationCommandOptionType.String, required: true } + ] + }, + { + name: 'release', + description: 'Lanza el botón del link vinculándolo al inicio.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'id_inicio', description: 'Copia el ID del mensaje de Startup.', type: ApplicationCommandOptionType.String, required: true }, + { name: 'tipo', description: '¿RP o Meet?', type: ApplicationCommandOptionType.String, required: true, choices: [{ name: 'Roleplay', value: 'rp' }, { name: 'Car Meet', value: 'meet' }] } + ] + } + ] +}; + +export async function execute(interaction) { + const sub = interaction.options.getSubcommand(); + + if (sub === 'startup_rp') { + const reacciones = interaction.options.getInteger('reacciones'); + const limite = interaction.options.getString('limite') || '80 MPH'; + const peacetime = interaction.options.getString('peacetime') || 'Off'; + + const embedRP = new EmbedBuilder() + .setTitle('__SWFL RP Startup__') + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*Asegúrate de haber leído las normativas en el canal correspondiente y tener tu vehículo registrado antes de ingresar a la sesión.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) + .addFields( + { name: '› Límite de Velocidad (FRP)', value: `${limite}`, inline: true }, + { name: '› Estado de Peacetime', value: `${peacetime}`, inline: true } + ) + .setColor('#1E90FF'); + + await interaction.reply({ content: 'Lanzando Startup...', ephemeral: true }); + const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedRP] }); + await msg.react('✅'); + + global.mapaVotos.set(msg.id, new Set()); + } + + if (sub === 'startup_meet') { + const reacciones = interaction.options.getInteger('reacciones'); + const tematica = interaction.options.getString('tematica'); + const spots = interaction.options.getString('spots'); + const ubicacion = interaction.options.getString('ubicacion'); + + const embedMeet = new EmbedBuilder() + .setTitle('__SWFL Meet Startup__') + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*¡Atención amantes de los fierros! Se viene una juntada oficial.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) + .addFields( + { name: '❗ Temática del Meet', value: `${tematica}`, inline: false }, + { name: '❗ Duración / Spots', value: `${spots}`, inline: true }, + { name: '❗ Lugar de Inicio', value: `${ubicacion}`, inline: true } + ) + .setColor('#00FF7F'); + + await interaction.reply({ content: 'Lanzando Car Meet...', ephemeral: true }); + const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedMeet] }); + await msg.react('✅'); + + global.mapaVotos.set(msg.id, new Set()); + } + + if (sub === 'release') { + const idInicio = interaction.options.getString('id_inicio'); + const tipo = interaction.options.getString('tipo'); + + if (!global.mapaVotos || !global.mapaVotos.has(idInicio)) { + return interaction.reply({ content: '❌ El ID de mensaje no es válido o el bot se reinició borrando la memoria.', ephemeral: true }); + } + + const titulo = tipo === 'rp' ? '__SWFL Roleplay Release__' : '__SWFL Meet Release__'; + const color = tipo === 'rp' ? '#1E90FF' : '#00FF7F'; + + const embedRelease = new EmbedBuilder() + .setTitle(titulo) + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\nLa sesión fue oficialmente lanzada. Si aportaste tu reacción en el mensaje de inicio, toca el botón de abajo para obtener el acceso.\n\n⚠️ *Filtrar el enlace directo es motivo de ban.*`) + .setColor(color); + + const fila = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`link_session_${idInicio}`) + .setLabel('Link de la Sesión') + .setStyle(ButtonStyle.Primary) + ); + + await interaction.reply({ content: 'Lanzando release...', ephemeral: true }); + await interaction.channel.send({ content: '@everyone', embeds: [embedRelease], components: [fila] }); + } +} From 7f35bb2a058a1a6bfdc2adf67a27b00448672776 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:12:31 -0300 Subject: [PATCH 04/12] Create reactionHandler.js donde el bot escucha lo que pasa en Discord en tiempo real. --- src/events/reactionHandler.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/events/reactionHandler.js diff --git a/src/events/reactionHandler.js b/src/events/reactionHandler.js new file mode 100644 index 000000000..15fc63556 --- /dev/null +++ b/src/events/reactionHandler.js @@ -0,0 +1,28 @@ +export const name = 'messageReactionAdd'; +export const displayName = 'Reaction Handler para Sesiones'; + +export async function execute(reaction, user) { + if (user.bot) return; + if (reaction.partial) { + try { await reaction.fetch(); } catch (error) { return; } + } + + if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { + global.mapaVotos.get(reaction.message.id).add(user.id); + console.log(`[00Y4n Bot] ${user.username} guardado en lista de acceso.`); + } +} + +// También manejamos cuando sacan la reacción +export const additionalEvents = { + messageReactionRemove: async (reaction, user) => { + if (user.bot) return; + if (reaction.partial) { + try { await reaction.fetch(); } catch (error) { return; } + } + if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { + global.mapaVotos.get(reaction.message.id).delete(user.id); + console.log(`[00Y4n Bot] ${user.username} eliminado de la lista de acceso.`); + } + } +}; From 52fc3d240c70bf4f94e67fddc2f15403eb460fa1 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:14:53 -0300 Subject: [PATCH 05/12] Create sessionButtons.js --- src/interactions/sessionButtons.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/interactions/sessionButtons.js diff --git a/src/interactions/sessionButtons.js b/src/interactions/sessionButtons.js new file mode 100644 index 000000000..e4ce61cc7 --- /dev/null +++ b/src/interactions/sessionButtons.js @@ -0,0 +1,25 @@ +const LINK_ROBLOX = "https://www.roblox.com/games/XXXXXX/Southwest-Florida"; // Pega acá tu link real + +export async function execute(interaction) { + if (!interaction.isButton()) return; + + if (interaction.customId.startsWith('link_session_')) { + const idStartupAsociado = interaction.customId.replace('link_session_', ''); + const userId = interaction.user.id; + + const conjuntoVotos = global.mapaVotos ? global.mapaVotos.get(idStartupAsociado) : null; + + // VERIFICACIÓN ESTRICTA DE VOTO + if (conjuntoVotos && conjuntoVotos.has(userId)) { + await interaction.reply({ + content: `🎉 **¡Voto verificado!** Acá tenés el acceso a la sesión de **00Y4n**:\n🔗 ${LINK_ROBLOX}\n\n*Respetá las reglas y evitá compartir el link.*`, + ephemeral: true + }); + } else { + await interaction.reply({ + content: `❌ **No podés obtener el link.**\nNo reaccionaste con el \`✅\` en el mensaje de inicio de esta sesión.`, + ephemeral: true + }); + } + } +} From 935af7106929940bf5a228745f7826ea1a131fc1 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:26:28 -0300 Subject: [PATCH 06/12] Update sessiones.js --- src/commands/sessiones.js | 154 ++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 72 deletions(-) diff --git a/src/commands/sessiones.js b/src/commands/sessiones.js index 686f8c0f4..99ed5a61d 100644 --- a/src/commands/sessiones.js +++ b/src/commands/sessiones.js @@ -1,23 +1,22 @@ import { ApplicationCommandOptionType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -// Mapa global en memoria que compartiremos con los eventos if (!global.mapaVotos) { global.mapaVotos = new Map(); } -export const data = { +const command = { name: 'sesiones_00y4n', description: 'Gestión de sesiones de Roleplay y Car Meets', - // Aquí registramos los subcomandos para que queden prolijos options: [ { name: 'startup_rp', description: 'Lanza un inicio de sesión de Roleplay convencional.', type: ApplicationCommandOptionType.Subcommand, options: [ - { name: 'reacciones', description: 'Reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, { name: 'limite', description: 'Ejemplo: 80 MPH', type: ApplicationCommandOptionType.String, required: false }, - { name: 'peacetime', description: '¿Peacetime activo? (On / Off)', type: ApplicationCommandOptionType.String, required: false } + { name: 'peacetime', description: '¿Peacetime activo? (On / Off)', type: ApplicationCommandOptionType.String, required: false }, + { name: 'imagen', description: 'Link de la foto/banner para el Roleplay (opcional).', type: ApplicationCommandOptionType.String, required: false } ] }, { @@ -25,10 +24,11 @@ export const data = { description: 'Lanza un inicio de sesión para un Car Meet.', type: ApplicationCommandOptionType.Subcommand, options: [ - { name: 'reacciones', description: 'Reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, { name: 'tematica', description: 'Ejemplo: JDM, Exóticos', type: ApplicationCommandOptionType.String, required: true }, { name: 'spots', description: 'Ejemplo: 2-3 SPOTS + BOTM', type: ApplicationCommandOptionType.String, required: true }, - { name: 'ubicacion', description: 'Lugar de inicio (Ej: Spawn)', type: ApplicationCommandOptionType.String, required: true } + { name: 'ubicacion', description: 'Lugar de inicio (Ej: Spawn)', type: ApplicationCommandOptionType.String, required: true }, + { name: 'imagen', description: 'Link de la foto/banner para el Car Meet (opcional).', type: ApplicationCommandOptionType.String, required: false } ] }, { @@ -37,83 +37,93 @@ export const data = { type: ApplicationCommandOptionType.Subcommand, options: [ { name: 'id_inicio', description: 'Copia el ID del mensaje de Startup.', type: ApplicationCommandOptionType.String, required: true }, - { name: 'tipo', description: '¿RP o Meet?', type: ApplicationCommandOptionType.String, required: true, choices: [{ name: 'Roleplay', value: 'rp' }, { name: 'Car Meet', value: 'meet' }] } + { name: 'tipo', description: '¿RP o Meet?', type: ApplicationCommandOptionType.String, required: true, choices: [{ name: 'Roleplay', value: 'rp' }, { name: 'Car Meet', value: 'meet' }] }, + { name: 'imagen', description: 'Link de la foto/banner para la apertura (opcional).', type: ApplicationCommandOptionType.String, required: false } ] } - ] -}; - -export async function execute(interaction) { - const sub = interaction.options.getSubcommand(); + ], - if (sub === 'startup_rp') { - const reacciones = interaction.options.getInteger('reacciones'); - const limite = interaction.options.getString('limite') || '80 MPH'; - const peacetime = interaction.options.getString('peacetime') || 'Off'; + async execute(interaction) { + const sub = interaction.options.getSubcommand(); + const urlImagen = interaction.options.getString('imagen'); - const embedRP = new EmbedBuilder() - .setTitle('__SWFL RP Startup__') - .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*Asegúrate de haber leído las normativas en el canal correspondiente y tener tu vehículo registrado antes de ingresar a la sesión.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) - .addFields( - { name: '› Límite de Velocidad (FRP)', value: `${limite}`, inline: true }, - { name: '› Estado de Peacetime', value: `${peacetime}`, inline: true } - ) - .setColor('#1E90FF'); + if (sub === 'startup_rp') { + const reacciones = interaction.options.getInteger('reacciones'); + const limite = interaction.options.getString('limite') || '80 MPH'; + const peacetime = interaction.options.getString('peacetime') || 'Off'; - await interaction.reply({ content: 'Lanzando Startup...', ephemeral: true }); - const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedRP] }); - await msg.react('✅'); + const embedRP = new EmbedBuilder() + .setTitle('__SWFL RP Startup__') + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*Asegúrate de haber leído las normativas en el canal correspondiente y tener tu vehículo registrado antes de ingresar a la sesión.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) + .addFields( + { name: '› Límite de Velocidad (FRP)', value: `${limite}`, inline: true }, + { name: '› Estado de Peacetime', value: `${peacetime}`, inline: true } + ) + .setColor('#1E90FF'); - global.mapaVotos.set(msg.id, new Set()); - } + if (urlImagen) embedRP.setImage(urlImagen); - if (sub === 'startup_meet') { - const reacciones = interaction.options.getInteger('reacciones'); - const tematica = interaction.options.getString('tematica'); - const spots = interaction.options.getString('spots'); - const ubicacion = interaction.options.getString('ubicacion'); - - const embedMeet = new EmbedBuilder() - .setTitle('__SWFL Meet Startup__') - .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*¡Atención amantes de los fierros! Se viene una juntada oficial.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) - .addFields( - { name: '❗ Temática del Meet', value: `${tematica}`, inline: false }, - { name: '❗ Duración / Spots', value: `${spots}`, inline: true }, - { name: '❗ Lugar de Inicio', value: `${ubicacion}`, inline: true } - ) - .setColor('#00FF7F'); - - await interaction.reply({ content: 'Lanzando Car Meet...', ephemeral: true }); - const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedMeet] }); - await msg.react('✅'); - - global.mapaVotos.set(msg.id, new Set()); - } + await interaction.reply({ content: 'Lanzando Startup...', ephemeral: true }); + const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedRP] }); + await msg.react('✅'); - if (sub === 'release') { - const idInicio = interaction.options.getString('id_inicio'); - const tipo = interaction.options.getString('tipo'); + global.mapaVotos.set(msg.id, new Set()); + } - if (!global.mapaVotos || !global.mapaVotos.has(idInicio)) { - return interaction.reply({ content: '❌ El ID de mensaje no es válido o el bot se reinició borrando la memoria.', ephemeral: true }); + if (sub === 'startup_meet') { + const reacciones = interaction.options.getInteger('reacciones'); + const tematica = interaction.options.getString('tematica'); + const spots = interaction.options.getString('spots'); + const ubicacion = interaction.options.getString('ubicacion'); + + const embedMeet = new EmbedBuilder() + .setTitle('__SWFL Meet Startup__') + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\n*¡Atención amantes de los fierros! Se viene una juntada oficial.*\n\n**¡Necesitamos ${reacciones} reacciones para iniciar!**`) + .addFields( + { name: '❗ Temática del Meet', value: `${tematica}`, inline: false }, + { name: '❗ Duración / Spots', value: `${spots}`, inline: true }, + { name: '❗ Lugar de Inicio', value: `${ubicacion}`, inline: true } + ) + .setColor('#00FF7F'); + + if (urlImagen) embedMeet.setImage(urlImagen); + + await interaction.reply({ content: 'Lanzando Car Meet...', ephemeral: true }); + const msg = await interaction.channel.send({ content: '@everyone', embeds: [embedMeet] }); + await msg.react('✅'); + + global.mapaVotos.set(msg.id, new Set()); } - const titulo = tipo === 'rp' ? '__SWFL Roleplay Release__' : '__SWFL Meet Release__'; - const color = tipo === 'rp' ? '#1E90FF' : '#00FF7F'; + if (sub === 'release') { + const idInicio = interaction.options.getString('id_inicio'); + const tipo = interaction.options.getString('tipo'); + + if (!global.mapaVotos || !global.mapaVotos.has(idInicio)) { + return interaction.reply({ content: '❌ El ID de mensaje no es válido o el bot se reinició borrando la memoria.', ephemeral: true }); + } - const embedRelease = new EmbedBuilder() - .setTitle(titulo) - .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\nLa sesión fue oficialmente lanzada. Si aportaste tu reacción en el mensaje de inicio, toca el botón de abajo para obtener el acceso.\n\n⚠️ *Filtrar el enlace directo es motivo de ban.*`) - .setColor(color); + const titulo = tipo === 'rp' ? '__SWFL Roleplay Release__' : '__SWFL Meet Release__'; + const color = tipo === 'rp' ? '#1E90FF' : '#00FF7F'; - const fila = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`link_session_${idInicio}`) - .setLabel('Link de la Sesión') - .setStyle(ButtonStyle.Primary) - ); + const embedRelease = new EmbedBuilder() + .setTitle(titulo) + .setDescription(`> **Anfitrión:** <@${interaction.user.id}>\n\nLa sesión fue oficialmente lanzada. Si aportaste tu reacción en el mensaje de inicio, toca el botón de abajo para obtener el acceso.\n\n⚠️ *Filtrar el enlace directo es motivo de ban.*`) + .setColor(color); - await interaction.reply({ content: 'Lanzando release...', ephemeral: true }); - await interaction.channel.send({ content: '@everyone', embeds: [embedRelease], components: [fila] }); + if (urlImagen) embedRelease.setImage(urlImagen); + + const fila = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`link_session_${idInicio}`) + .setLabel('Link de la Sesión') + .setStyle(ButtonStyle.Primary) + ); + + await interaction.reply({ content: 'Lanzando release...', ephemeral: true }); + await interaction.channel.send({ content: '@everyone', embeds: [embedRelease], components: [fila] }); + } } -} +}; + +export default command; From 9113f0a26f4f4dcc8f5a42bcd080e87244b163bc Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:26:46 -0300 Subject: [PATCH 07/12] Update reactionHandler.js --- src/events/reactionHandler.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/events/reactionHandler.js b/src/events/reactionHandler.js index 15fc63556..d89cff5a5 100644 --- a/src/events/reactionHandler.js +++ b/src/events/reactionHandler.js @@ -1,5 +1,4 @@ export const name = 'messageReactionAdd'; -export const displayName = 'Reaction Handler para Sesiones'; export async function execute(reaction, user) { if (user.bot) return; @@ -9,20 +8,6 @@ export async function execute(reaction, user) { if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { global.mapaVotos.get(reaction.message.id).add(user.id); - console.log(`[00Y4n Bot] ${user.username} guardado en lista de acceso.`); + console.log(`[00Y4n Votos] Voto registrado para ${user.username}`); } } - -// También manejamos cuando sacan la reacción -export const additionalEvents = { - messageReactionRemove: async (reaction, user) => { - if (user.bot) return; - if (reaction.partial) { - try { await reaction.fetch(); } catch (error) { return; } - } - if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { - global.mapaVotos.get(reaction.message.id).delete(user.id); - console.log(`[00Y4n Bot] ${user.username} eliminado de la lista de acceso.`); - } - } -}; From 2c8a06a844a1df3bff12cb66e19a25d8488704c4 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:27:47 -0300 Subject: [PATCH 08/12] Create reactionRemoveHandler.js --- src/events/reactionRemoveHandler.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/events/reactionRemoveHandler.js diff --git a/src/events/reactionRemoveHandler.js b/src/events/reactionRemoveHandler.js new file mode 100644 index 000000000..ff3f8779b --- /dev/null +++ b/src/events/reactionRemoveHandler.js @@ -0,0 +1,13 @@ +export const name = 'messageReactionRemove'; + +export async function execute(reaction, user) { + if (user.bot) return; + if (reaction.partial) { + try { await reaction.fetch(); } catch (error) { return; } + } + + if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { + global.mapaVotos.get(reaction.message.id).delete(user.id); + console.log(`[00Y4n Votos] Voto removido para ${user.username}`); + } +} From 611f228b6f4f7d568fed6a1acee1299b9718b716 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:32:04 -0300 Subject: [PATCH 09/12] Update sessiones.js --- src/commands/sessiones.js | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/commands/sessiones.js b/src/commands/sessiones.js index 99ed5a61d..a5f2d9856 100644 --- a/src/commands/sessiones.js +++ b/src/commands/sessiones.js @@ -4,44 +4,46 @@ if (!global.mapaVotos) { global.mapaVotos = new Map(); } -const command = { - name: 'sesiones_00y4n', - description: 'Gestión de sesiones de Roleplay y Car Meets', - options: [ - { - name: 'startup_rp', - description: 'Lanza un inicio de sesión de Roleplay convencional.', - type: ApplicationCommandOptionType.Subcommand, - options: [ - { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, - { name: 'limite', description: 'Ejemplo: 80 MPH', type: ApplicationCommandOptionType.String, required: false }, - { name: 'peacetime', description: '¿Peacetime activo? (On / Off)', type: ApplicationCommandOptionType.String, required: false }, - { name: 'imagen', description: 'Link de la foto/banner para el Roleplay (opcional).', type: ApplicationCommandOptionType.String, required: false } - ] - }, - { - name: 'startup_meet', - description: 'Lanza un inicio de sesión para un Car Meet.', - type: ApplicationCommandOptionType.Subcommand, - options: [ - { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, - { name: 'tematica', description: 'Ejemplo: JDM, Exóticos', type: ApplicationCommandOptionType.String, required: true }, - { name: 'spots', description: 'Ejemplo: 2-3 SPOTS + BOTM', type: ApplicationCommandOptionType.String, required: true }, - { name: 'ubicacion', description: 'Lugar de inicio (Ej: Spawn)', type: ApplicationCommandOptionType.String, required: true }, - { name: 'imagen', description: 'Link de la foto/banner para el Car Meet (opcional).', type: ApplicationCommandOptionType.String, required: false } - ] - }, - { - name: 'release', - description: 'Lanza el botón del link vinculándolo al inicio.', - type: ApplicationCommandOptionType.Subcommand, - options: [ - { name: 'id_inicio', description: 'Copia el ID del mensaje de Startup.', type: ApplicationCommandOptionType.String, required: true }, - { name: 'tipo', description: '¿RP o Meet?', type: ApplicationCommandOptionType.String, required: true, choices: [{ name: 'Roleplay', value: 'rp' }, { name: 'Car Meet', value: 'meet' }] }, - { name: 'imagen', description: 'Link de la foto/banner para la apertura (opcional).', type: ApplicationCommandOptionType.String, required: false } - ] - } - ], +export default { + data: { + name: 'sesiones_00y4n', + description: 'Gestión de sesiones de Roleplay y Car Meets', + options: [ + { + name: 'startup_rp', + description: 'Lanza un inicio de sesión de Roleplay convencional.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'limite', description: 'Ejemplo: 80 MPH', type: ApplicationCommandOptionType.String, required: false }, + { name: 'peacetime', description: '¿Peacetime activo? (On / Off)', type: ApplicationCommandOptionType.String, required: false }, + { name: 'imagen', description: 'Link de la foto/banner para el Roleplay (opcional).', type: ApplicationCommandOptionType.String, required: false } + ] + }, + { + name: 'startup_meet', + description: 'Lanza un inicio de sesión para un Car Meet.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'reacciones', description: 'Cantidad de reacciones necesarias.', type: ApplicationCommandOptionType.Integer, required: true }, + { name: 'tematica', description: 'Ejemplo: JDM, Exóticos', type: ApplicationCommandOptionType.String, required: true }, + { name: 'spots', description: 'Ejemplo: 2-3 SPOTS + BOTM', type: ApplicationCommandOptionType.String, required: true }, + { name: 'ubicacion', description: 'Lugar de inicio (Ej: Spawn)', type: ApplicationCommandOptionType.String, required: true }, + { name: 'imagen', description: 'Link de la foto/banner para el Car Meet (opcional).', type: ApplicationCommandOptionType.String, required: false } + ] + }, + { + name: 'release', + description: 'Lanza el botón del link vinculándolo al inicio.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { name: 'id_inicio', description: 'Copia el ID del mensaje de Startup.', type: ApplicationCommandOptionType.String, required: true }, + { name: 'tipo', description: '¿RP o Meet?', type: ApplicationCommandOptionType.String, required: true, choices: [{ name: 'Roleplay', value: 'rp' }, { name: 'Car Meet', value: 'meet' }] }, + { name: 'imagen', description: 'Link de la foto/banner para la apertura (opcional).', type: ApplicationCommandOptionType.String, required: false } + ] + } + ] + }, async execute(interaction) { const sub = interaction.options.getSubcommand(); @@ -125,5 +127,3 @@ const command = { } } }; - -export default command; From bb653096b25e6ac6f4700c637a2c22e4b9434daf Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:32:28 -0300 Subject: [PATCH 10/12] Update reactionHandler.js --- src/events/reactionHandler.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/events/reactionHandler.js b/src/events/reactionHandler.js index d89cff5a5..a2d838f92 100644 --- a/src/events/reactionHandler.js +++ b/src/events/reactionHandler.js @@ -1,13 +1,14 @@ -export const name = 'messageReactionAdd'; +export default { + name: 'messageReactionAdd', + async execute(reaction, user) { + if (user.bot) return; + if (reaction.partial) { + try { await reaction.fetch(); } catch (error) { return; } + } -export async function execute(reaction, user) { - if (user.bot) return; - if (reaction.partial) { - try { await reaction.fetch(); } catch (error) { return; } + if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { + global.mapaVotos.get(reaction.message.id).add(user.id); + console.log(`[00Y4n Votos] Voto registrado para ${user.username}`); + } } - - if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { - global.mapaVotos.get(reaction.message.id).add(user.id); - console.log(`[00Y4n Votos] Voto registrado para ${user.username}`); - } -} +}; From 291c08833981b35d55f17b4d95ffcc59c67a2877 Mon Sep 17 00:00:00 2001 From: tatoamericano8-blip Date: Mon, 1 Jun 2026 20:32:43 -0300 Subject: [PATCH 11/12] Update reactionRemoveHandler.js --- src/events/reactionRemoveHandler.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/events/reactionRemoveHandler.js b/src/events/reactionRemoveHandler.js index ff3f8779b..f3613ceb0 100644 --- a/src/events/reactionRemoveHandler.js +++ b/src/events/reactionRemoveHandler.js @@ -1,13 +1,14 @@ -export const name = 'messageReactionRemove'; +export default { + name: 'messageReactionRemove', + async execute(reaction, user) { + if (user.bot) return; + if (reaction.partial) { + try { await reaction.fetch(); } catch (error) { return; } + } -export async function execute(reaction, user) { - if (user.bot) return; - if (reaction.partial) { - try { await reaction.fetch(); } catch (error) { return; } + if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { + global.mapaVotos.get(reaction.message.id).delete(user.id); + console.log(`[00Y4n Votos] Voto removido para ${user.username}`); + } } - - if (reaction.emoji.name === '✅' && global.mapaVotos && global.mapaVotos.has(reaction.message.id)) { - global.mapaVotos.get(reaction.message.id).delete(user.id); - console.log(`[00Y4n Votos] Voto removido para ${user.username}`); - } -} +}; From 0162ac00b5bcd16642d96924a9a5f982267c81c4 Mon Sep 17 00:00:00 2001 From: Railway Agent Date: Mon, 1 Jun 2026 23:37:36 +0000 Subject: [PATCH 12/12] Fix command loader toJSON error handling ## Problem\nThe command loader was crashing with because it assumed all objects were SlashCommandBuilder instances with a method.\n\nSome commands may have as plain objects or other structures that don't have this method, causing the bot to crash during startup.\n\n## Solution\nAdded a new helper function that safely handles both:\n- SlashCommandBuilder instances (calls )\n- Plain objects (returns them directly)\n- Invalid structures (returns null and logs a warning)\n\nThis function is now used in all places where was previously called directly:\n- Line 103: Loading commands\n- Line 117: Filtering commands with subcommands\n- Line 122: Counting subcommands\n- Line 155+: Command registration\n\n## Changes\n- Added helper function\n- Updated to use the helper\n- Updated to use the helper\n- Added proper null checks and error logging\n\nThe bot will now gracefully skip malformed commands instead of crashing. --- src/handlers/commandLoader.js | 59 ++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/handlers/commandLoader.js b/src/handlers/commandLoader.js index 5273f2934..e372e1d7d 100644 --- a/src/handlers/commandLoader.js +++ b/src/handlers/commandLoader.js @@ -10,8 +10,6 @@ const __dirname = path.dirname(__filename); - - function getSubcommandInfo(commandData) { const subcommands = []; @@ -37,6 +35,24 @@ if (subOption.type === 1) { +function getCommandDataJSON(command) { + if (!command.data) { + return null; + } + + // If it's a SlashCommandBuilder, call toJSON() + if (typeof command.data.toJSON === 'function') { + return command.data.toJSON(); + } + + // If it's already a plain object, return it + if (typeof command.data === 'object') { + return command.data; + } + + return null; +} + @@ -62,8 +78,6 @@ async function getAllFiles(directory, fileList = []) { - - export async function loadCommands(client) { client.commands = new Collection(); const commandsPath = path.join(__dirname, '../commands'); @@ -100,7 +114,13 @@ export async function loadCommands(client) { client.commands.set(primaryCommandName, command); } - const subcommands = getSubcommandInfo(command.data.toJSON()); + const commandDataJSON = getCommandDataJSON(command); + if (!commandDataJSON) { + logger.warn(`Command at ${filePath} has invalid data structure.`); + continue; + } + + const subcommands = getSubcommandInfo(commandDataJSON); logger.info(`Loaded command: ${primaryCommandName} from ${normalizedPath} (category: ${category})`); @@ -114,12 +134,16 @@ export async function loadCommands(client) { } const commandsWithSubcommands = Array.from(client.commands.values()).filter(cmd => { - const subcommands = getSubcommandInfo(cmd.data.toJSON()); + const commandDataJSON = getCommandDataJSON(cmd); + if (!commandDataJSON) return false; + const subcommands = getSubcommandInfo(commandDataJSON); return subcommands.length > 0; }); const totalSubcommands = commandsWithSubcommands.reduce((total, cmd) => { - return total + getSubcommandInfo(cmd.data.toJSON()).length; + const commandDataJSON = getCommandDataJSON(cmd); + if (!commandDataJSON) return total; + return total + getSubcommandInfo(commandDataJSON).length; }, 0); const uniqueCommands = new Set(); @@ -136,9 +160,6 @@ export async function loadCommands(client) { - - - export async function registerCommands(client, guildId) { try { const commands = []; @@ -146,17 +167,22 @@ export async function registerCommands(client, guildId) { const registeredNames = new Set(); for (const command of client.commands.values()) { - if (command.data && typeof command.data.toJSON === 'function') { + if (command.data) { + const commandDataJSON = getCommandDataJSON(command); + if (!commandDataJSON) { + logger.warn(`Command has invalid data structure: ${command}`); + continue; + } + const commandName = command.data.name; logger.debug(`Processing command for registration: ${commandName}`); if (!registeredNames.has(commandName)) { registeredNames.add(commandName); - const commandJson = command.data.toJSON(); - commands.push(commandJson); + commands.push(commandDataJSON); - const subcommands = getSubcommandInfo(commandJson); + const subcommands = getSubcommandInfo(commandDataJSON); totalSubcommands += subcommands.length; if (process.env.NODE_ENV !== 'production') { @@ -166,7 +192,7 @@ const registeredNames = new Set(); logger.debug(`Skipping duplicate command: ${commandName}`); } } else { - logger.warn(`Command missing data or toJSON method: ${command}`); + logger.warn(`Command missing data: ${command}`); } } @@ -299,9 +325,6 @@ const registeredNames = new Set(); - - - export async function reloadCommand(client, commandName) { const command = client.commands.get(commandName);