feat: integrate pino logger for improved error handling and logging consistency
This commit is contained in:
11
.env
11
.env
@@ -1,6 +1,10 @@
|
||||
# Configuración de ejemplo para optimización de memoria
|
||||
# Copia este archivo como .env.test y ajusta los valores según tus necesidades
|
||||
|
||||
# ===========================================
|
||||
# CONFIGURACIÓN DE APPWRITE
|
||||
# ===========================================
|
||||
|
||||
APPWRITE_PROJECT_ID="68d8c4b2001abc54d3cd"
|
||||
APPWRITE_PROJECT_NAME="amayo"
|
||||
APPWRITE_ENDPOINT="https://nyc.cloud.appwrite.io/v1"
|
||||
@@ -80,3 +84,10 @@ SWEEP_MESSAGES_LIFETIME_SECONDS=900 # 15 minutos
|
||||
# SWEEP_MESSAGES_INTERVAL_SECONDS=600
|
||||
# SWEEP_MESSAGES_LIFETIME_SECONDS=1800
|
||||
# MEMORY_LOG_INTERVAL_SECONDS=300
|
||||
|
||||
# ===========================================
|
||||
# NOTAS:
|
||||
# Mode es un cambio en cada estacion de mes, como algo festivo.
|
||||
# ===========================================
|
||||
|
||||
MODE='halloween' # normal, halloween, christmas, newyear
|
||||
129
package-lock.json
generated
129
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"discord-api-types": "0.38.24",
|
||||
"discord.js": "14.22.1",
|
||||
"node-appwrite": "19.1.0",
|
||||
"pino": "9.13.0",
|
||||
"prisma": "6.16.2",
|
||||
"redis": "5.8.2"
|
||||
},
|
||||
@@ -506,6 +507,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -1044,6 +1054,15 @@
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
@@ -1056,6 +1075,43 @@
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-9.13.0.tgz",
|
||||
"integrity": "sha512-SpTXQhkQXekIKEe7c887S3lk3v90Q+/HVRZVyNAhe98PQc++6I5ec/R0pciH8/CciXjCoVZIZfRNicbC6KZgnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^2.0.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^5.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"slow-redact": "^0.3.0",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
|
||||
"integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-std-serializers": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
@@ -1092,6 +1148,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/process-warning": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
|
||||
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||
@@ -1108,6 +1180,12 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
@@ -1131,6 +1209,15 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz",
|
||||
@@ -1167,6 +1254,48 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/slow-redact": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.0.tgz",
|
||||
"integrity": "sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
|
||||
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"discord-api-types": "0.38.24",
|
||||
"discord.js": "14.22.1",
|
||||
"node-appwrite": "19.1.0",
|
||||
"pino": "9.13.0",
|
||||
"prisma": "6.16.2",
|
||||
"redis": "5.8.2"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import {CommandMessage} from "../../../core/types/commands";
|
||||
import { TextChannel, DMChannel, NewsChannel, ThreadChannel } from "discord.js";
|
||||
@@ -256,7 +257,7 @@ ${userHistory.messages.slice(-3).join('\n')}`;
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error en comando AI:', error);
|
||||
logger.error('Error en comando AI:', error);
|
||||
|
||||
// Manejar errores específicos incluyendo límites de tokens
|
||||
let errorMessage = "❌ **Error:** Ocurrió un problema al comunicarse con la IA.";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
// @ts-ignore
|
||||
import {
|
||||
@@ -1259,17 +1260,17 @@ export const command: CommandMessage = {
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.code === 10008) {
|
||||
console.log('Mensaje del editor eliminado');
|
||||
logger.info('Mensaje del editor eliminado');
|
||||
} else if (error.code === 10062) {
|
||||
console.log('Interacción expirada');
|
||||
logger.info('Interacción expirada');
|
||||
} else {
|
||||
console.error('Error actualizando preview:', error.message || error);
|
||||
logger.error('Error actualizando preview:', error.message || error);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error en modal:', error);
|
||||
logger.error('Error en modal:', error);
|
||||
try {
|
||||
if (error.code !== 10062 && !interaction.replied && !interaction.deferred) {
|
||||
await interaction.reply({ content: '❌ Error procesando el modal.', flags: 64 });
|
||||
@@ -1301,7 +1302,7 @@ export const command: CommandMessage = {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('No se pudo actualizar el mensaje final');
|
||||
logger.info('No se pudo actualizar el mensaje final');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
// @ts-ignore
|
||||
import { EmbedBuilder, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
||||
@@ -209,7 +210,7 @@ export const command: CommandMessage = {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
logger.info(error)
|
||||
const errorEmbed = new EmbedBuilder()
|
||||
.setTitle("❌ Error de Eliminación")
|
||||
.setDescription(`💥 **Error al eliminar el canal:**\n\n📺 Canal: ${channelName}\n🧩 Configuración: \`${channelConfig?.blockConfigName}\`\n\n🔍 **Posibles causas:**\n• El canal ya fue eliminado\n• Error de base de datos\n• Permisos insuficientes\n\n🔄 Intenta nuevamente.`)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
// Comando para crear recordatorios con Appwrite: !recordar {texto} {fecha}
|
||||
// Ejemplos:
|
||||
// !recordar hacer esto el miércoles a las 5pm
|
||||
@@ -236,7 +237,7 @@ export const command: CommandMessage = {
|
||||
executeAt: iso
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error programando recordatorio:', e);
|
||||
logger.error('Error programando recordatorio:', e);
|
||||
await message.reply('❌ No pude guardar el recordatorio. Revisa la configuración de Appwrite.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
|
||||
export const command: CommandMessage = {
|
||||
@@ -254,7 +255,7 @@ export const command: CommandMessage = {
|
||||
}
|
||||
}).catch(async (error: any) => {
|
||||
// Modal timeout o cancelado
|
||||
console.log("Modal timeout o error:", error.message);
|
||||
logger.info("Modal timeout o error:", error.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ButtonInteraction, MessageFlags} from 'discord.js';
|
||||
import {clearGlobalCommands} from '../../core/api/discordAPI';
|
||||
import type { Button } from '../../core/types/components';
|
||||
@@ -21,7 +22,7 @@ export default {
|
||||
await clearGlobalCommands();
|
||||
await interaction.editReply('🧹 Comandos GLOBAL eliminados.');
|
||||
} catch (e: any) {
|
||||
console.error('Error limpiando comandos globales:', e);
|
||||
logger.error('Error limpiando comandos globales:', e);
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.editReply('❌ Error limpiando comandos globales.');
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ButtonInteraction, MessageFlags} from 'discord.js';
|
||||
import { clearAllCommands } from '../../core/api/discordAPI';
|
||||
import type { Button } from '../../core/types/components';
|
||||
@@ -21,7 +22,7 @@ export default {
|
||||
await clearAllCommands();
|
||||
await interaction.editReply('🧹 Comandos de GUILD eliminados.');
|
||||
} catch (e: any) {
|
||||
console.error('Error limpiando comandos guild:', e);
|
||||
logger.error('Error limpiando comandos guild:', e);
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.editReply('❌ Error limpiando comandos de guild.');
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ButtonInteraction, MessageFlags} from 'discord.js';
|
||||
import { buildAdminPanel } from '../../commands/messages/net/commandsAdmin';
|
||||
|
||||
@@ -15,7 +16,7 @@ export default {
|
||||
// Edita el mensaje original reemplazando componentes (solo el contenedor con filas internas)
|
||||
await interaction.message.edit({ components: [panel] });
|
||||
} catch (e) {
|
||||
console.error('Error refrescando panel de memoria:', e);
|
||||
logger.error('Error refrescando panel de memoria:', e);
|
||||
if (!interaction.deferred && !interaction.replied)
|
||||
await interaction.reply({ content: '❌ Error refrescando panel.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ButtonInteraction, MessageFlags} from 'discord.js';
|
||||
import { registeringGlobalCommands } from '../../core/api/discordAPI';
|
||||
|
||||
@@ -19,7 +20,7 @@ export default {
|
||||
await registeringGlobalCommands();
|
||||
await interaction.editReply('✅ Comandos GLOBAL registrados (propagación puede tardar).');
|
||||
} catch (e: any) {
|
||||
console.error('Error registrando comandos globales:', e);
|
||||
logger.error('Error registrando comandos globales:', e);
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.editReply('❌ Error registrando comandos globales.');
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ButtonInteraction, MessageFlags} from 'discord.js';
|
||||
import { registeringCommands } from '../../core/api/discordAPI';
|
||||
import type { Button } from '../../core/types/components';
|
||||
@@ -21,7 +22,7 @@ export default {
|
||||
await registeringCommands();
|
||||
await interaction.editReply('✅ Comandos de GUILD registrados correctamente.');
|
||||
} catch (e: any) {
|
||||
console.error('Error registrando comandos guild:', e);
|
||||
logger.error('Error registrando comandos guild:', e);
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.editReply('❌ Error registrando comandos de guild. Revisa logs.');
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import { ButtonInteraction, MessageFlags } from 'discord.js';
|
||||
import { buildLeaderboardPanel } from '../../commands/messages/alliaces/leaderboard';
|
||||
|
||||
@@ -14,7 +15,7 @@ export default {
|
||||
const panel = await buildLeaderboardPanel(fakeMessage);
|
||||
await interaction.message.edit({ components: [panel] });
|
||||
} catch (e) {
|
||||
console.error('Error refrescando leaderboard:', e);
|
||||
logger.error('Error refrescando leaderboard:', e);
|
||||
if (!interaction.deferred && !interaction.replied)
|
||||
await interaction.reply({ content: '❌ Error refrescando leaderboard.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {ModalSubmitInteraction, MessageFlags} from "discord.js";
|
||||
import type { Modal } from '../../core/types/components';
|
||||
import type Amayo from '../../core/client';
|
||||
@@ -22,7 +23,7 @@ export default {
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error cambiando prefix:', error);
|
||||
logger.error('Error cambiando prefix:', error);
|
||||
await interaction.reply({
|
||||
content: '❌ Error al cambiar el prefix.',
|
||||
flags: MessageFlags.Ephemeral
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
import { REST } from "discord.js";
|
||||
// @ts-ignore
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
@@ -19,12 +20,12 @@ export async function registeringCommands(): Promise<void> {
|
||||
options: cmd.options ?? []
|
||||
});
|
||||
|
||||
console.log(`✅ Preparado para registrar (guild): ${cmd.name}`);
|
||||
logger.info(`✅ Preparado para registrar (guild): ${cmd.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🧹 Limpiando comandos antiguos/residuales (guild)...`);
|
||||
logger.info(`🧹 Limpiando comandos antiguos/residuales (guild)...`);
|
||||
|
||||
// Primero eliminamos TODOS los comandos existentes
|
||||
await rest.put(
|
||||
@@ -35,11 +36,11 @@ export async function registeringCommands(): Promise<void> {
|
||||
{ body: [] } // Array vacío elimina todos los comandos
|
||||
);
|
||||
|
||||
console.log(`✅ Comandos antiguos de guild eliminados.`);
|
||||
logger.info(`✅ Comandos antiguos de guild eliminados.`);
|
||||
// Pequeña pausa para asegurar que Discord procese la eliminación
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
console.log(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`);
|
||||
logger.info(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`);
|
||||
|
||||
// Ahora registramos los comandos actuales
|
||||
const data: any = await rest.put(
|
||||
@@ -50,9 +51,9 @@ export async function registeringCommands(): Promise<void> {
|
||||
{ body: commandsToRegister }
|
||||
);
|
||||
|
||||
console.log(`✅ ${data.length} comandos de guild registrados.`);
|
||||
logger.info(`✅ ${data.length} comandos de guild registrados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error en el proceso de comandos de guild:", error);
|
||||
logger.error("❌ Error en el proceso de comandos de guild:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,31 +67,31 @@ export async function registeringGlobalCommands(): Promise<void> {
|
||||
type: 1,
|
||||
options: cmd.options ?? []
|
||||
});
|
||||
console.log(`🌍 Preparado para registrar global: ${cmd.name}`);
|
||||
logger.info(`🌍 Preparado para registrar global: ${cmd.name}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
console.log(`🧹 Limpiando comandos globales existentes...`);
|
||||
logger.info(`🧹 Limpiando comandos globales existentes...`);
|
||||
await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Comandos globales previos eliminados.`);
|
||||
logger.info(`✅ Comandos globales previos eliminados.`);
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
console.log(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`);
|
||||
logger.info(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`);
|
||||
const data: any = await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: commandsToRegister }
|
||||
);
|
||||
console.log(`✅ ${data.length} comandos globales enviados a la API.`);
|
||||
logger.info(`✅ ${data.length} comandos globales enviados a la API.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error registrando comandos globales:", error);
|
||||
logger.error("❌ Error registrando comandos globales:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearAllCommands(): Promise<void> {
|
||||
try {
|
||||
console.log(`🧹 Eliminando TODOS los comandos slash (guild)...`);
|
||||
logger.info(`🧹 Eliminando TODOS los comandos slash (guild)...`);
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
process.env.CLIENT!,
|
||||
@@ -98,21 +99,21 @@ export async function clearAllCommands(): Promise<void> {
|
||||
),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Todos los comandos de guild eliminados.`);
|
||||
logger.info(`✅ Todos los comandos de guild eliminados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error eliminando comandos de guild:", error);
|
||||
logger.error("❌ Error eliminando comandos de guild:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearGlobalCommands(): Promise<void> {
|
||||
try {
|
||||
console.log(`🌍 Eliminando comandos globales...`);
|
||||
logger.info(`🌍 Eliminando comandos globales...`);
|
||||
await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Comandos globales eliminados.`);
|
||||
logger.info(`✅ Comandos globales eliminados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error eliminando comandos globales:", error);
|
||||
logger.error("❌ Error eliminando comandos globales:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sdk: any = require('node-appwrite');
|
||||
import type Amayo from '../client';
|
||||
@@ -62,7 +63,7 @@ async function fetchDueReminders(limit = 25): Promise<ReminderRow[]> {
|
||||
]) as unknown as { documents?: ReminderRow[] };
|
||||
return (list.documents || []) as ReminderRow[];
|
||||
} catch (e) {
|
||||
console.error('Error listando recordatorios vencidos:', e);
|
||||
logger.error('Error listando recordatorios vencidos:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
delivered = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('No se pudo enviar al canal original:', e);
|
||||
logger.warn('No se pudo enviar al canal original:', e);
|
||||
}
|
||||
}
|
||||
// 2) Fallback: DM al usuario
|
||||
@@ -92,7 +93,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
await user.send({ content: `⏰ Recordatorio: ${message}` });
|
||||
delivered = true;
|
||||
} catch (e) {
|
||||
console.warn('No se pudo enviar DM al usuario:', e);
|
||||
logger.warn('No se pudo enviar DM al usuario:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +102,12 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
|
||||
export function startReminderPoller(bot: Amayo) {
|
||||
if (!isAppwriteConfigured()) {
|
||||
console.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.');
|
||||
logger.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const intervalSec = parseInt(process.env.REMINDERS_POLL_INTERVAL_SECONDS || '30', 10);
|
||||
console.log(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`);
|
||||
logger.info(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`);
|
||||
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
@@ -119,11 +120,11 @@ export function startReminderPoller(bot: Amayo) {
|
||||
const db = getDatabases();
|
||||
if (db) await db.deleteDocument(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, d.$id);
|
||||
} catch (e) {
|
||||
console.warn('No se pudo eliminar recordatorio entregado:', e);
|
||||
logger.warn('No se pudo eliminar recordatorio entregado:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error en ciclo de recordatorios:', e);
|
||||
logger.error('Error en ciclo de recordatorios:', e);
|
||||
}
|
||||
}, Math.max(10, intervalSec) * 1000);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sdk: any = require('node-appwrite');
|
||||
import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite';
|
||||
@@ -20,7 +21,7 @@ export async function ensureRemindersSchema() {
|
||||
]);
|
||||
// Nota: No añadimos permisos de lectura pública para evitar fuga de datos
|
||||
} catch (e) {
|
||||
console.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e);
|
||||
logger.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +31,7 @@ export async function ensureRemindersSchema() {
|
||||
const msg = String(e?.message || e);
|
||||
if (!/already exists|attribute_already_exists/i.test(msg)) {
|
||||
// Otros errores se muestran
|
||||
console.warn('No se pudo crear atributo:', msg);
|
||||
logger.warn('No se pudo crear atributo:', msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -49,7 +50,7 @@ export async function ensureRemindersSchema() {
|
||||
} catch (e: any) {
|
||||
const msg = String(e?.message || e);
|
||||
if (!/already exists|index_already_exists/i.test(msg)) {
|
||||
console.warn('No se pudo crear índice executeAt:', msg);
|
||||
logger.warn('No se pudo crear índice executeAt:', msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Client, GatewayIntentBits, Options, Partials } from 'discord.js';
|
||||
import { prisma, ensurePrismaConnection } from './database/prisma';
|
||||
import logger from './lib/logger';
|
||||
|
||||
// Verificar si process.loadEnvFile existe (Node.js 20.6+)
|
||||
if (typeof process.loadEnvFile === 'function') {
|
||||
@@ -9,6 +10,7 @@ if (typeof process.loadEnvFile === 'function') {
|
||||
class Amayo extends Client {
|
||||
public key: string;
|
||||
public prisma = prisma;
|
||||
public mode: string;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@@ -44,19 +46,20 @@ class Amayo extends Client {
|
||||
});
|
||||
|
||||
this.key = process.env.TOKEN ?? '';
|
||||
this.mode = process.env.MODE ?? 'Normal';
|
||||
}
|
||||
|
||||
async play() {
|
||||
if (!this.key) {
|
||||
console.error('No key provided');
|
||||
logger.error('No key provided');
|
||||
throw new Error('Missing DISCORD TOKEN');
|
||||
} else {
|
||||
try {
|
||||
await ensurePrismaConnection();
|
||||
console.log('Successfully connected to the database (singleton).');
|
||||
logger.info('Successfully connected to the database (singleton).');
|
||||
await this.login(this.key);
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to DB or login to Discord:', error);
|
||||
logger.error({ err: error }, 'Failed to connect to DB or login to Discord');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createClient } from "redis";
|
||||
import logger from "../lib/logger";
|
||||
|
||||
export const redis = createClient({
|
||||
username: 'default',
|
||||
@@ -9,9 +10,9 @@ export const redis = createClient({
|
||||
}
|
||||
});
|
||||
|
||||
redis.on("error", (err: any) => console.error("Redis error:", err));
|
||||
redis.on("connect", () => console.log("✅ Conectado a Redis"));
|
||||
redis.on("reconnecting", () => console.warn("♻️ Reintentando conexión Redis"));
|
||||
redis.on("error", (err: any) => logger.error({ err }, "Redis error"));
|
||||
redis.on("connect", () => logger.info("✅ Conectado a Redis"));
|
||||
redis.on("reconnecting", () => logger.warn("♻️ Reintentando conexión Redis"));
|
||||
|
||||
export async function redisConnect () {
|
||||
if (!redis.isOpen) await redis.connect();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "./logger";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { Collection } from "discord.js";
|
||||
@@ -11,7 +12,7 @@ export const contextmenus: Collection<string, ContextMenu> = new Collection<stri
|
||||
export function loadComponents(dir: string = path.resolve(__dirname, "../../components")) {
|
||||
// Evitar fallo si el directorio no existe en el entorno
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de componentes no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de componentes no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,28 +35,28 @@ export function loadComponents(dir: string = path.resolve(__dirname, "../../comp
|
||||
const component = imported.default ?? imported;
|
||||
|
||||
if (!component?.customId) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
logger.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detectamos el tipo según la carpeta en la que está
|
||||
if (fullPath.includes("buttons")) {
|
||||
buttons.set(component.customId, component as Button);
|
||||
console.log(`🔘 Botón cargado: ${component.customId}`);
|
||||
logger.info(`🔘 Botón cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("modals")) {
|
||||
modals.set(component.customId, component as Modal);
|
||||
console.log(`📄 Modal cargado: ${component.customId}`);
|
||||
logger.info(`📄 Modal cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("selectmenus")) {
|
||||
selectmenus.set(component.customId, component as SelectMenu);
|
||||
console.log(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
logger.info(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("contextmenu")) {
|
||||
contextmenus.set(component.customId, component as ContextMenu);
|
||||
console.log(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
logger.info(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
} else {
|
||||
console.log(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
logger.info(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error cargando componente ${file}:`, error);
|
||||
logger.error(`❌ Error cargando componente ${file}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
src/core/lib/logger.ts
Normal file
22
src/core/lib/logger.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import pino from 'pino';
|
||||
|
||||
// Configurar pino con opciones optimizadas para producción
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transport: process.env.NODE_ENV !== 'production' ? {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
}
|
||||
} : undefined,
|
||||
formatters: {
|
||||
level: (label) => {
|
||||
return { level: label };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default logger;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { Collection } from "discord.js";
|
||||
@@ -7,7 +8,7 @@ export const commands = new Collection<string, any>();
|
||||
export function loadCommands(dir: string = path.resolve(__dirname, "../../commands")) {
|
||||
// Evitar fallo si el directorio no existe en el entorno
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,12 +30,12 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman
|
||||
const command = imported.command ?? imported.default ?? imported;
|
||||
|
||||
if (!command?.data?.name && !command?.name) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`);
|
||||
logger.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = command.data?.name ?? command.name;
|
||||
console.log(`📦 Loading command: ${name}`);
|
||||
logger.info(`📦 Loading command: ${name}`);
|
||||
|
||||
// @ts-ignore
|
||||
commands.set(name, command);
|
||||
@@ -45,6 +46,6 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Cargado comando: ${name}`);
|
||||
logger.info(`✅ Cargado comando: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { bot } from "../../main";
|
||||
import path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import logger from "../lib/logger";
|
||||
|
||||
export function loadEvents(dir: string = path.resolve(__dirname, "../../events")) {
|
||||
// Evitar fallo si el directorio no existe
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,6 +34,6 @@ export function loadEvents(dir: string = path.resolve(__dirname, "../../events")
|
||||
bot.on(event.name, (...args: any[]) => event.execute(...args));
|
||||
}
|
||||
|
||||
console.log(`Evento cargado: ${event.name}`);
|
||||
logger.info(`Evento cargado: ${event.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// Monitor ligero de memoria y event loop.
|
||||
// Se activa si defines MEMORY_LOG_INTERVAL_SECONDS.
|
||||
import { monitorEventLoopDelay } from 'node:perf_hooks';
|
||||
@@ -41,7 +42,7 @@ export function startMemoryMonitor(opts: MemoryMonitorOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`);
|
||||
logger.info(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`);
|
||||
|
||||
// Resetear métricas de event loop delay para la siguiente ventana
|
||||
h.reset();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// Sistema adicional de optimización de memoria para complementar el monitor existente
|
||||
|
||||
export interface MemoryOptimizerOptions {
|
||||
@@ -21,7 +22,7 @@ export class MemoryOptimizer {
|
||||
start() {
|
||||
// Solo habilitar si está disponible el GC manual
|
||||
if (typeof global.gc !== 'function') {
|
||||
console.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
|
||||
logger.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ export class MemoryOptimizer {
|
||||
this.gcTimer.unref(); // No bloquear el cierre del proceso
|
||||
}
|
||||
|
||||
console.log(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
|
||||
logger.info(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
|
||||
}
|
||||
|
||||
stop() {
|
||||
@@ -69,7 +70,7 @@ export class MemoryOptimizer {
|
||||
const duration = Date.now() - startTime;
|
||||
const heapFreed = (before.heapUsed - after.heapUsed) / 1024 / 1024;
|
||||
|
||||
console.log(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
|
||||
logger.info(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
// Reemplaza instancia local -> usa singleton
|
||||
import { prisma } from "../../core/database/prisma";
|
||||
import { replaceVars } from "../../core/lib/vars";
|
||||
import logger from "../../core/lib/logger";
|
||||
|
||||
|
||||
// Regex para detectar URLs válidas (corregido)
|
||||
@@ -69,7 +70,7 @@ export async function alliance(message: Message) {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error en función alliance:', error);
|
||||
logger.error({ err: error }, 'Error en función alliance');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +131,10 @@ async function processValidLink(message: Message, allianceChannel: any, link: st
|
||||
// Enviar el bloque configurado usando Display Components
|
||||
await sendBlockConfigV2(message, allianceChannel.blockConfigName, message.guild!.id, link, userStats, inviteData);
|
||||
|
||||
console.log(`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`);
|
||||
logger.info(`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error procesando enlace válido:', error);
|
||||
logger.error({ err: error }, 'Error procesando enlace válido');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ async function validateDiscordInvite(link: string): Promise<any> {
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error validando invitación de Discord:', error);
|
||||
logger.error({ err: error }, 'Error validando invitación de Discord');
|
||||
return null; // En caso de error, considerar como inválido
|
||||
}
|
||||
}
|
||||
@@ -247,7 +248,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil
|
||||
});
|
||||
|
||||
if (!blockConfig) {
|
||||
console.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`);
|
||||
logger.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,8 +267,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error enviando bloque de configuración V2:', error);
|
||||
console.log('Detalles del error:', error);
|
||||
logger.error({ err: error }, '❌ Error enviando bloque de configuración V2');
|
||||
|
||||
// Fallback: usar mensaje simple
|
||||
try {
|
||||
@@ -275,7 +275,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil
|
||||
content: '✅ ¡Enlace de alianza procesado correctamente!'
|
||||
});
|
||||
} catch (fallbackError) {
|
||||
console.error('❌ Error en fallback:', fallbackError);
|
||||
logger.error({ err: fallbackError }, '❌ Error en fallback');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
|
||||
return { type: 17, accent_color: config.color ?? null, components: previewComponents };
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error convirtiendo configuración a Display Component:', error);
|
||||
logger.error({ err: error }, 'Error convirtiendo configuración a Display Component');
|
||||
return { type: 17, accent_color: null, components: [ { type: 10, content: 'Error al procesar la configuración del bloque.' } ] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Events } from "discord.js";
|
||||
import { redis } from "../core/database/redis";
|
||||
import { commands } from "../core/loaders/loader";
|
||||
import { buttons, modals, selectmenus } from "../core/lib/components";
|
||||
import logger from "../core/lib/logger";
|
||||
|
||||
bot.on(Events.InteractionCreate, async (interaction: BaseInteraction) => {
|
||||
try {
|
||||
@@ -45,7 +46,7 @@ bot.on(Events.InteractionCreate, async (interaction: BaseInteraction) => {
|
||||
if (modal) await modal.run(interaction, bot);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ err: error }, "Error ejecutando interacción");
|
||||
if (interaction.isRepliable()) {
|
||||
await interaction.reply({ content: "❌ Hubo un error ejecutando la interacción.", ephemeral: true });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Events} from "discord.js";
|
||||
import {redis} from "../core/database/redis";
|
||||
import {commands} from "../core/loaders/loader";
|
||||
import {alliance} from "./extras/alliace";
|
||||
import logger from "../core/lib/logger";
|
||||
|
||||
|
||||
bot.on(Events.MessageCreate, async (message) => {
|
||||
@@ -30,7 +31,7 @@ bot.on(Events.MessageCreate, async (message) => {
|
||||
if (cooldown > 0) {
|
||||
const key = `cooldown:${command.name}:${message.author.id}`;
|
||||
const ttl = await redis.ttl(key);
|
||||
console.log(`Key: ${key}, TTL: ${ttl}`);
|
||||
logger.debug(`Key: ${key}, TTL: ${ttl}`);
|
||||
|
||||
if (ttl > 0) {
|
||||
return message.reply(`⏳ Espera ${ttl}s antes de volver a usar **${command.name}**.`);
|
||||
@@ -44,7 +45,7 @@ bot.on(Events.MessageCreate, async (message) => {
|
||||
try {
|
||||
await command.run(message, args, message.client);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ err: error }, "Error ejecutando comando");
|
||||
await message.reply("❌ Hubo un error ejecutando el comando.");
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import {bot} from "../main";
|
||||
import {Events} from "discord.js";
|
||||
import logger from "../core/lib/logger";
|
||||
|
||||
bot.on(Events.ClientReady, () => {
|
||||
console.log("Ready!");
|
||||
logger.info("Ready!");
|
||||
})
|
||||
43
src/main.ts
43
src/main.ts
@@ -8,6 +8,7 @@ import { startMemoryMonitor } from "./core/memory/memoryMonitor";
|
||||
import {memoryOptimizer} from "./core/memory/memoryOptimizer";
|
||||
import { startReminderPoller } from "./core/api/reminders";
|
||||
import { ensureRemindersSchema } from "./core/api/remindersSchema";
|
||||
import logger from "./core/lib/logger";
|
||||
|
||||
// Activar monitor de memoria si se define la variable
|
||||
const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10);
|
||||
@@ -23,8 +24,8 @@ if (process.env.ENABLE_MEMORY_OPTIMIZER === 'true') {
|
||||
export const bot = new Amayo();
|
||||
|
||||
// Listeners de robustez del cliente Discord
|
||||
bot.on('error', (e) => console.error('🐞 Discord client error:', e));
|
||||
bot.on('warn', (m) => console.warn('⚠️ Discord warn:', m));
|
||||
bot.on('error', (e) => logger.error({ err: e }, '🐞 Discord client error'));
|
||||
bot.on('warn', (m) => logger.warn('⚠️ Discord warn: %s', m));
|
||||
|
||||
// Evitar reintentos de re-login simultáneos
|
||||
let relogging = false;
|
||||
@@ -32,10 +33,10 @@ let relogging = false;
|
||||
bot.on('invalidated', () => {
|
||||
if (relogging) return;
|
||||
relogging = true;
|
||||
console.error('🔄 Sesión de Discord invalidada. Reintentando login...');
|
||||
logger.error('🔄 Sesión de Discord invalidada. Reintentando login...');
|
||||
withRetry('Re-login tras invalidated', () => bot.play(), { minDelayMs: 2000, maxDelayMs: 60_000 })
|
||||
.catch(() => {
|
||||
console.error('No se pudo reloguear tras invalidated, se seguirá intentando en el bucle general.');
|
||||
logger.error('No se pudo reloguear tras invalidated, se seguirá intentando en el bucle general.');
|
||||
})
|
||||
.finally(() => { relogging = false; });
|
||||
});
|
||||
@@ -68,10 +69,10 @@ async function withRetry<T>(name: string, fn: () => Promise<T>, opts?: {
|
||||
} catch (err) {
|
||||
attempt++;
|
||||
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
||||
console.error(`❌ ${name} falló (intento ${attempt}) =>`, errMsg);
|
||||
logger.error(`❌ ${name} falló (intento ${attempt}) => %s`, errMsg);
|
||||
|
||||
if (!isRetryable(err, attempt)) {
|
||||
console.error(`⛔ ${name}: error no recuperable, deteniendo reintentos.`);
|
||||
logger.error(`⛔ ${name}: error no recuperable, deteniendo reintentos.`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ async function withRetry<T>(name: string, fn: () => Promise<T>, opts?: {
|
||||
} else {
|
||||
wait = Math.min(maxDelayMs, delay);
|
||||
}
|
||||
console.warn(`⏳ Reintentando ${name} en ${wait}ms...`);
|
||||
logger.warn(`⏳ Reintentando ${name} en ${wait}ms...`);
|
||||
await new Promise((r) => setTimeout(r, wait));
|
||||
delay = Math.min(maxDelayMs, Math.floor(delay * factor));
|
||||
}
|
||||
@@ -94,11 +95,11 @@ async function withRetry<T>(name: string, fn: () => Promise<T>, opts?: {
|
||||
|
||||
// Handlers globales para robustez
|
||||
process.on('unhandledRejection', (reason: any, p) => {
|
||||
console.error('🚨 UnhandledRejection en Promise:', p, 'razón:', reason);
|
||||
logger.error({ promise: p, reason }, '🚨 UnhandledRejection en Promise');
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('🚨 UncaughtException:', err);
|
||||
logger.error({ err }, '🚨 UncaughtException');
|
||||
// No salimos; dejamos que el bot continúe vivo
|
||||
});
|
||||
|
||||
@@ -115,14 +116,14 @@ process.on('multipleResolves', (type, promise, reason: any) => {
|
||||
// Ruido benigno de reconexiones del WS de Discord: ignorar
|
||||
return;
|
||||
}
|
||||
console.warn('⚠️ multipleResolves:', type, msg);
|
||||
logger.warn('⚠️ multipleResolves: %s %s', type, msg);
|
||||
});
|
||||
|
||||
let shuttingDown = false;
|
||||
async function gracefulShutdown() {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
console.log('🛑 Apagado controlado iniciado...');
|
||||
logger.info('🛑 Apagado controlado iniciado...');
|
||||
try {
|
||||
// Detener optimizador de memoria
|
||||
memoryOptimizer.stop();
|
||||
@@ -131,10 +132,10 @@ async function gracefulShutdown() {
|
||||
try {
|
||||
if (redis?.isOpen) {
|
||||
await redis.quit();
|
||||
console.log('🔌 Redis cerrado');
|
||||
logger.info('🔌 Redis cerrado');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('No se pudo cerrar Redis limpiamente:', e);
|
||||
logger.warn({ err: e }, 'No se pudo cerrar Redis limpiamente');
|
||||
}
|
||||
// Cerrar Prisma y Discord
|
||||
try {
|
||||
@@ -144,7 +145,7 @@ async function gracefulShutdown() {
|
||||
await bot.destroy();
|
||||
} catch {}
|
||||
} finally {
|
||||
console.log('✅ Apagado controlado completo');
|
||||
logger.info('✅ Apagado controlado completo');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,17 +153,17 @@ process.on('SIGINT', gracefulShutdown);
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
|
||||
async function bootstrap() {
|
||||
console.log("🚀 Iniciando bot...");
|
||||
logger.info("🚀 Iniciando bot...");
|
||||
|
||||
// Cargar recursos locales (no deberían tirar el proceso si fallan)
|
||||
try { loadCommands(); } catch (e) { console.error('Error cargando comandos:', e); }
|
||||
try { loadComponents(); } catch (e) { console.error('Error cargando componentes:', e); }
|
||||
try { loadEvents(); } catch (e) { console.error('Error cargando eventos:', e); }
|
||||
try { loadCommands(); } catch (e) { logger.error({ err: e }, 'Error cargando comandos'); }
|
||||
try { loadComponents(); } catch (e) { logger.error({ err: e }, 'Error cargando componentes'); }
|
||||
try { loadEvents(); } catch (e) { logger.error({ err: e }, 'Error cargando eventos'); }
|
||||
|
||||
// Registrar comandos en segundo plano con reintentos; no bloquea el arranque del bot
|
||||
withRetry('Registrar slash commands', async () => {
|
||||
await registeringCommands();
|
||||
}).catch((e) => console.error('Registro de comandos agotó reintentos:', e));
|
||||
}).catch((e) => logger.error({ err: e }, 'Registro de comandos agotó reintentos'));
|
||||
|
||||
// Conectar Redis con reintentos
|
||||
await withRetry('Conectar a Redis', async () => {
|
||||
@@ -181,12 +182,12 @@ async function bootstrap() {
|
||||
});
|
||||
|
||||
// Asegurar esquema de Appwrite para recordatorios (colección + atributos + índice)
|
||||
try { await ensureRemindersSchema(); } catch (e) { console.warn('No se pudo asegurar el esquema de recordatorios:', e); }
|
||||
try { await ensureRemindersSchema(); } catch (e) { logger.warn({ err: e }, 'No se pudo asegurar el esquema de recordatorios'); }
|
||||
|
||||
// Iniciar poller de recordatorios si Appwrite está configurado
|
||||
startReminderPoller(bot);
|
||||
|
||||
console.log("✅ Bot conectado a Discord");
|
||||
logger.info("✅ Bot conectado a Discord");
|
||||
}
|
||||
|
||||
// Bucle de arranque resiliente: si bootstrap completo falla, reintenta sin matar el proceso
|
||||
|
||||
Reference in New Issue
Block a user