v3.13.0-dev.1

This commit is contained in:
Shnimlz
2025-11-24 16:16:01 -06:00
parent 5d1b2b2f53
commit a5fbc573d0
503 changed files with 111351 additions and 104656 deletions

View File

@@ -1,347 +1,391 @@
import logger from "../../core/lib/logger";
import {
ModalSubmitInteraction,
MessageFlags,
EmbedBuilder,
User,
Collection,
Snowflake
} from 'discord.js';
import { prisma } from '../../core/database/prisma';
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
interface UserSelectComponent {
custom_id: string;
type: number;
values: string[];
}
interface ComponentData {
components?: ComponentData[];
component?: ComponentData;
custom_id?: string;
type?: number;
values?: string[];
}
export default {
customId: 'ld_points_modal',
run: async (interaction: ModalSubmitInteraction) => {
logger.info(`🔍 Modal ldPointsModal ejecutado. CustomId: ${interaction.customId}`);
if (!interaction.guild) {
return interaction.reply({
content: '❌ Solo disponible en servidores.',
flags: MessageFlags.Ephemeral
});
}
// Verificar permisos (ManageGuild o rol staff)
const member = await interaction.guild.members.fetch(interaction.user.id);
const allowed = await hasManageGuildOrStaff(member, interaction.guild.id, prisma);
if (!allowed) {
return interaction.reply({
content: '❌ Solo admins o staff pueden gestionar puntos.',
flags: MessageFlags.Ephemeral
});
}
try {
// Obtener valores del modal con manejo seguro de errores
let totalInput: string = '';
let selectedUsers: ReturnType<typeof interaction.components.getSelectedUsers> = null;
let userId: string | undefined = undefined;
let userName: string | undefined = undefined;
try {
totalInput = interaction.components.getTextInputValue('points_input').trim();
} catch (error) {
// @ts-ignore
logger.error('Error obteniendo points_input:', String(error));
return interaction.reply({
content: '❌ Error al obtener el valor de puntos del modal.',
flags: MessageFlags.Ephemeral
});
}
// Manejo seguro del UserSelect con fallback
try {
selectedUsers = interaction.components.getSelectedUsers('user_select');
if (!selectedUsers || selectedUsers.size === 0) {
// Fallback: intentar obtener los IDs directamente de los datos raw
const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
if (rawData) {
const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
userId = userSelectComponent.values[0];
logger.info(`🔄 Fallback: UserId extraído de datos raw: ${userId}`);
}
}
if (!userId) {
return interaction.reply({
content: '❌ Debes seleccionar un usuario del leaderboard.',
flags: MessageFlags.Ephemeral
});
}
} else {
const selectedUser = Array.from(selectedUsers.values())[0] as User;
if (selectedUser) {
userId = selectedUser.id;
userName = selectedUser.tag ?? selectedUser.username ?? userId;
}
}
} catch (error) {
// @ts-ignore
logger.error('Error procesando UserSelect, intentando fallback:', String(error));
// Fallback más agresivo: obtener directamente de los datos raw
try {
const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
userId = userSelectComponent.values[0];
logger.info(`🔄 Fallback agresivo: UserId extraído: ${userId}`);
} else {
throw new Error('No se pudo extraer userId de los datos raw');
}
} catch (fallbackError) {
// @ts-ignore
logger.error('Falló el fallback:', String(fallbackError));
return interaction.reply({
content: '❌ Error procesando la selección de usuario. Inténtalo de nuevo.',
flags: MessageFlags.Ephemeral
});
}
}
logger.info(`🔍 Input recibido: ${totalInput}`);
logger.info(`🔍 UserId extraído: ${userId}`);
if (!totalInput) {
return interaction.reply({
content: '❌ Debes ingresar un valor para modificar.',
flags: MessageFlags.Ephemeral
});
}
if (!userId) {
return interaction.reply({
content: '❌ Error al identificar el usuario seleccionado.',
flags: MessageFlags.Ephemeral
});
}
// Si no tenemos userName, intentar obtenerlo del servidor
if (!userName) {
try {
const targetMember = await interaction.guild.members.fetch(userId);
userName = targetMember.displayName || targetMember.user.username;
} catch (error) {
// @ts-ignore
logger.warn(`No se pudo obtener info del usuario ${userId}:`, String(error));
userName = `Usuario ${userId}`;
}
}
// ✅ ARREGLO: Asegurar que el User exista en la base de datos antes de crear PartnershipStats
await prisma.user.upsert({
where: { id: userId },
update: {}, // No actualizar nada si ya existe
create: { id: userId } // Crear si no existe
});
logger.info(`✅ User verificado/creado en BD: ${userId}`);
// Obtener o crear el registro de stats del usuario
let stats = await prisma.partnershipStats.findUnique({
where: {
userId_guildId: {
userId,
guildId: interaction.guild.id
}
}
});
if (!stats) {
logger.info(`🔍 Creando nuevo registro de stats para userId: ${userId}`);
// Crear nuevo registro si no existe
stats = await prisma.partnershipStats.create({
data: {
userId,
guildId: interaction.guild.id,
totalPoints: 0,
weeklyPoints: 0,
monthlyPoints: 0
}
});
}
logger.info(`🔍 Stats actuales - Total: ${stats.totalPoints}, Semanal: ${stats.weeklyPoints}, Mensual: ${stats.monthlyPoints}`);
// Función para parsear el input y calcular el nuevo valor
const calculateNewValue = (input: string, currentValue: number): number => {
const firstChar = input[0];
if (firstChar === '+') {
// Añadir puntos
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, currentValue + numValue);
} else if (firstChar === '-') {
// Quitar puntos (los últimos N puntos añadidos)
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, currentValue - numValue);
} else if (firstChar === '=') {
// Establecer valor absoluto
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, numValue);
} else {
// Si no tiene símbolo, tratar como valor absoluto
const parsedValue = parseInt(input);
return isNaN(parsedValue) ? currentValue : Math.max(0, parsedValue);
}
};
// Calcular nuevo valor de puntos totales
const newTotalPoints = calculateNewValue(totalInput, stats.totalPoints);
const totalDifference = newTotalPoints - stats.totalPoints;
logger.info(`🔍 Nuevo total calculado: ${newTotalPoints} (diferencia: ${totalDifference})`);
// Calcular nuevos puntos semanales y mensuales
let newWeeklyPoints = stats.weeklyPoints;
let newMonthlyPoints = stats.monthlyPoints;
if (totalInput[0] === '+') {
// Si añadimos puntos, sumar a semanal y mensual también
const pointsToAdd = parseInt(totalInput.substring(1)) || 0;
newWeeklyPoints = stats.weeklyPoints + pointsToAdd;
newMonthlyPoints = stats.monthlyPoints + pointsToAdd;
logger.info(` Añadiendo ${pointsToAdd} puntos a todas las categorías`);
} else if (totalInput[0] === '-') {
// Si quitamos puntos, restar proporcionalmente de semanal y mensual
const pointsToRemove = parseInt(totalInput.substring(1)) || 0;
newWeeklyPoints = Math.max(0, stats.weeklyPoints - pointsToRemove);
newMonthlyPoints = Math.max(0, stats.monthlyPoints - pointsToRemove);
logger.info(` Quitando ${pointsToRemove} puntos de todas las categorías`);
} else if (totalInput[0] === '=') {
// Si establecemos un valor absoluto, ajustar semanal y mensual proporcionalmente
const targetTotal = parseInt(totalInput.substring(1)) || 0;
if (stats.totalPoints > 0) {
// Calcular el ratio y aplicarlo
const ratio = targetTotal / stats.totalPoints;
newWeeklyPoints = Math.round(stats.weeklyPoints * ratio);
newMonthlyPoints = Math.round(stats.monthlyPoints * ratio);
} else {
// Si no había puntos antes, establecer todo a 0
newWeeklyPoints = 0;
newMonthlyPoints = 0;
}
logger.info(`🎯 Estableciendo total a ${targetTotal} y ajustando proporcionalmente`);
}
// Asegurar que semanal no exceda mensual, y mensual no exceda total
newWeeklyPoints = Math.min(newWeeklyPoints, newMonthlyPoints, newTotalPoints);
newMonthlyPoints = Math.min(newMonthlyPoints, newTotalPoints);
logger.info(`🔍 Nuevos valores calculados - Total: ${newTotalPoints}, Semanal: ${newWeeklyPoints}, Mensual: ${newMonthlyPoints}`);
// Actualizar en base de datos (todos los puntos)
await prisma.partnershipStats.update({
where: {
userId_guildId: {
userId,
guildId: interaction.guild.id
}
},
data: {
totalPoints: newTotalPoints,
weeklyPoints: newWeeklyPoints,
monthlyPoints: newMonthlyPoints
}
});
logger.info(`✅ Puntos actualizados exitosamente en la base de datos`);
// Calcular las diferencias
const totalDiff = newTotalPoints - stats.totalPoints;
const weeklyDiff = newWeeklyPoints - stats.weeklyPoints;
const monthlyDiff = newMonthlyPoints - stats.monthlyPoints;
const totalDiffText = totalDiff > 0 ? `+${totalDiff}` : `${totalDiff}`;
const weeklyDiffText = weeklyDiff > 0 ? `+${weeklyDiff}` : `${weeklyDiff}`;
const monthlyDiffText = monthlyDiff > 0 ? `+${monthlyDiff}` : `${monthlyDiff}`;
// Crear embed de confirmación
const embed = new EmbedBuilder()
.setColor(totalDiff >= 0 ? 0x00ff00 : 0xff9900)
.setTitle('✅ Puntos Actualizados')
.setDescription(`Se han actualizado los puntos de **${userName}**`)
.addFields(
{
name: '📊 Puntos Totales',
value: `${stats.totalPoints} → **${newTotalPoints}** (${totalDiffText})`,
inline: true
},
{
name: '🗓️ Puntos Mensuales',
value: `${stats.monthlyPoints} → **${newMonthlyPoints}** (${monthlyDiffText})`,
inline: true
},
{
name: '📅 Puntos Semanales',
value: `${stats.weeklyPoints} → **${newWeeklyPoints}** (${weeklyDiffText})`,
inline: true
}
)
.setFooter({ text: `Actualizado por ${interaction.user.username}` })
.setTimestamp();
await interaction.reply({
embeds: [embed],
flags: MessageFlags.Ephemeral
});
} catch (error) {
// @ts-ignore
// @ts-ignore
logger.error('❌ Error en ldPointsModal:', String(error));
if (!interaction.replied && !interaction.deferred) {
await interaction.reply({
content: '❌ Error interno al procesar la solicitud. Revisa los logs para más detalles.',
flags: MessageFlags.Ephemeral
});
}
}
}
};
// Función auxiliar para buscar componentes UserSelect en datos raw
function findUserSelectComponent(components: ComponentData[] | undefined, customId: string): UserSelectComponent | null {
if (!components) return null;
for (const comp of components) {
if (comp.components) {
const found = findUserSelectComponent(comp.components, customId);
if (found) return found;
}
if (comp.component) {
if (comp.component.custom_id === customId) {
return comp.component as UserSelectComponent;
}
const found = findUserSelectComponent([comp.component], customId);
if (found) return found;
}
if (comp.custom_id === customId) {
return comp as UserSelectComponent;
}
}
return null;
}
import logger from "../../core/lib/logger";
import {
ModalSubmitInteraction,
MessageFlags,
EmbedBuilder,
User,
Collection,
Snowflake,
} from "discord.js";
import { prisma } from "../../core/database/prisma";
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
interface UserSelectComponent {
custom_id: string;
type: number;
values: string[];
}
interface ComponentData {
components?: ComponentData[];
component?: ComponentData;
custom_id?: string;
type?: number;
values?: string[];
}
export default {
customId: "ld_points_modal",
run: async (interaction: ModalSubmitInteraction) => {
logger.info(
`🔍 Modal ldPointsModal ejecutado. CustomId: ${interaction.customId}`
);
if (!interaction.guild) {
return interaction.reply({
content: "❌ Solo disponible en servidores.",
flags: MessageFlags.Ephemeral,
});
}
// Verificar permisos (ManageGuild o rol staff)
const member = await interaction.guild.members.fetch(interaction.user.id);
const allowed = await hasManageGuildOrStaff(
member,
interaction.guild.id,
prisma
);
if (!allowed) {
return interaction.reply({
content: "❌ Solo admins o staff pueden gestionar puntos.",
flags: MessageFlags.Ephemeral,
});
}
try {
// Obtener valores del modal con manejo seguro de errores
let totalInput: string = "";
let selectedUsers: ReturnType<
typeof interaction.components.getSelectedUsers
> = null;
try {
selectedUsers = interaction.components.getSelectedUsers("user_select");
if (!selectedUsers || selectedUsers.size === 0) {
// Fallback: intentar obtener los IDs directamente de los datos raw
const rawData = (interaction as any).data?.components as
| ComponentData[]
| undefined;
if (rawData) {
const userSelectComponent = findUserSelectComponent(
rawData,
"user_select"
);
if (
userSelectComponent?.values?.length &&
userSelectComponent.values.length > 0
) {
userId = userSelectComponent.values[0];
logger.info(
`🔄 Fallback: UserId extraído de datos raw: ${userId}`
);
}
}
if (!userId) {
return interaction.reply({
content: "❌ Debes seleccionar un usuario del leaderboard.",
flags: MessageFlags.Ephemeral,
});
}
} else {
const selectedUser = Array.from(selectedUsers.values())[0] as User;
if (selectedUser) {
userId = selectedUser.id;
userName = selectedUser.tag ?? selectedUser.username ?? userId;
}
}
} catch (error) {
// @ts-ignore
logger.error(
"Error procesando UserSelect, intentando fallback:",
String(error)
);
// Fallback más agresivo: obtener directamente de los datos raw
try {
const rawData = (interaction as any).data?.components as
| ComponentData[]
| undefined;
const userSelectComponent = findUserSelectComponent(
rawData,
"user_select"
);
if (
userSelectComponent?.values?.length &&
userSelectComponent.values.length > 0
) {
userId = userSelectComponent.values[0];
logger.info(`🔄 Fallback agresivo: UserId extraído: ${userId}`);
} else {
throw new Error("No se pudo extraer userId de los datos raw");
}
} catch (fallbackError) {
// @ts-ignore
logger.error("Falló el fallback:", String(fallbackError));
return interaction.reply({
content:
"❌ Error procesando la selección de usuario. Inténtalo de nuevo.",
flags: MessageFlags.Ephemeral,
});
}
}
logger.info(`🔍 Input recibido: ${totalInput}`);
logger.info(`🔍 UserId extraído: ${userId}`);
if (!totalInput) {
return interaction.reply({
content: "❌ Debes ingresar un valor para modificar.",
flags: MessageFlags.Ephemeral,
});
}
if (!userId) {
return interaction.reply({
content: "❌ Error al identificar el usuario seleccionado.",
flags: MessageFlags.Ephemeral,
});
}
// Si no tenemos userName, intentar obtenerlo del servidor
if (!userName) {
try {
const targetMember = await interaction.guild.members.fetch(userId);
userName = targetMember.displayName || targetMember.user.username;
} catch (error) {
// @ts-ignore
logger.warn(
`No se pudo obtener info del usuario ${userId}:`,
String(error)
);
userName = `Usuario ${userId}`;
}
}
// ✅ ARREGLO: Asegurar que el User exista en la base de datos antes de crear PartnershipStats
await prisma.user.upsert({
where: { id: userId },
update: {}, // No actualizar nada si ya existe
create: { id: userId }, // Crear si no existe
});
logger.info(`✅ User verificado/creado en BD: ${userId}`);
// Obtener o crear el registro de stats del usuario
let stats = await prisma.partnershipStats.findUnique({
where: {
userId_guildId: {
userId,
guildId: interaction.guild.id,
},
},
});
if (!stats) {
logger.info(
`🔍 Creando nuevo registro de stats para userId: ${userId}`
);
// Crear nuevo registro si no existe
stats = await prisma.partnershipStats.create({
data: {
userId,
guildId: interaction.guild.id,
totalPoints: 0,
weeklyPoints: 0,
monthlyPoints: 0,
},
});
}
logger.info(
`🔍 Stats actuales - Total: ${stats.totalPoints}, Semanal: ${stats.weeklyPoints}, Mensual: ${stats.monthlyPoints}`
);
// Función para parsear el input y calcular el nuevo valor
const calculateNewValue = (
input: string,
currentValue: number
): number => {
const firstChar = input[0];
if (firstChar === "+") {
// Añadir puntos
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, currentValue + numValue);
} else if (firstChar === "-") {
// Quitar puntos (los últimos N puntos añadidos)
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, currentValue - numValue);
} else if (firstChar === "=") {
// Establecer valor absoluto
const numValue = parseInt(input.substring(1)) || 0;
return Math.max(0, numValue);
} else {
// Si no tiene símbolo, tratar como valor absoluto
const parsedValue = parseInt(input);
return isNaN(parsedValue) ? currentValue : Math.max(0, parsedValue);
}
};
// Calcular nuevo valor de puntos totales
const newTotalPoints = calculateNewValue(totalInput, stats.totalPoints);
const totalDifference = newTotalPoints - stats.totalPoints;
logger.info(
`🔍 Nuevo total calculado: ${newTotalPoints} (diferencia: ${totalDifference})`
);
// Calcular nuevos puntos semanales y mensuales
let newWeeklyPoints = stats.weeklyPoints;
let newMonthlyPoints = stats.monthlyPoints;
if (totalInput[0] === "+") {
// Si añadimos puntos, sumar a semanal y mensual también
const pointsToAdd = parseInt(totalInput.substring(1)) || 0;
newWeeklyPoints = stats.weeklyPoints + pointsToAdd;
newMonthlyPoints = stats.monthlyPoints + pointsToAdd;
logger.info(
` Añadiendo ${pointsToAdd} puntos a todas las categorías`
);
} else if (totalInput[0] === "-") {
// Si quitamos puntos, restar proporcionalmente de semanal y mensual
const pointsToRemove = parseInt(totalInput.substring(1)) || 0;
newWeeklyPoints = Math.max(0, stats.weeklyPoints - pointsToRemove);
newMonthlyPoints = Math.max(0, stats.monthlyPoints - pointsToRemove);
logger.info(
` Quitando ${pointsToRemove} puntos de todas las categorías`
);
} else if (totalInput[0] === "=") {
// Si establecemos un valor absoluto, ajustar semanal y mensual proporcionalmente
const targetTotal = parseInt(totalInput.substring(1)) || 0;
if (stats.totalPoints > 0) {
// Calcular el ratio y aplicarlo
const ratio = targetTotal / stats.totalPoints;
newWeeklyPoints = Math.round(stats.weeklyPoints * ratio);
newMonthlyPoints = Math.round(stats.monthlyPoints * ratio);
} else {
// Si no había puntos antes, establecer todo a 0
newWeeklyPoints = 0;
newMonthlyPoints = 0;
}
logger.info(
`🎯 Estableciendo total a ${targetTotal} y ajustando proporcionalmente`
);
}
// Asegurar que semanal no exceda mensual, y mensual no exceda total
newWeeklyPoints = Math.min(
newWeeklyPoints,
newMonthlyPoints,
newTotalPoints
);
newMonthlyPoints = Math.min(newMonthlyPoints, newTotalPoints);
logger.info(
`🔍 Nuevos valores calculados - Total: ${newTotalPoints}, Semanal: ${newWeeklyPoints}, Mensual: ${newMonthlyPoints}`
);
// Actualizar en base de datos (todos los puntos)
await prisma.partnershipStats.update({
where: {
userId_guildId: {
userId,
guildId: interaction.guild.id,
},
},
data: {
totalPoints: newTotalPoints,
weeklyPoints: newWeeklyPoints,
monthlyPoints: newMonthlyPoints,
},
});
logger.info(`✅ Puntos actualizados exitosamente en la base de datos`);
// Calcular las diferencias
const totalDiff = newTotalPoints - stats.totalPoints;
const weeklyDiff = newWeeklyPoints - stats.weeklyPoints;
const monthlyDiff = newMonthlyPoints - stats.monthlyPoints;
const totalDiffText = totalDiff > 0 ? `+${totalDiff}` : `${totalDiff}`;
const weeklyDiffText =
weeklyDiff > 0 ? `+${weeklyDiff}` : `${weeklyDiff}`;
const monthlyDiffText =
monthlyDiff > 0 ? `+${monthlyDiff}` : `${monthlyDiff}`;
// Crear embed de confirmación
const embed = new EmbedBuilder()
.setColor(totalDiff >= 0 ? 0x00ff00 : 0xff9900)
.setTitle("✅ Puntos Actualizados")
.setDescription(`Se han actualizado los puntos de **${userName}**`)
.addFields(
{
name: "📊 Puntos Totales",
value: `${stats.totalPoints} → **${newTotalPoints}** (${totalDiffText})`,
inline: true,
},
{
name: "🗓️ Puntos Mensuales",
value: `${stats.monthlyPoints} → **${newMonthlyPoints}** (${monthlyDiffText})`,
inline: true,
},
{
name: "📅 Puntos Semanales",
value: `${stats.weeklyPoints} → **${newWeeklyPoints}** (${weeklyDiffText})`,
inline: true,
}
)
.setFooter({ text: `Actualizado por ${interaction.user.username}` })
.setTimestamp();
await interaction.reply({
embeds: [embed],
flags: MessageFlags.Ephemeral,
});
} catch (error) {
// @ts-ignore
// @ts-ignore
logger.error("❌ Error en ldPointsModal:", String(error));
if (!interaction.replied && !interaction.deferred) {
await interaction.reply({
content:
"❌ Error interno al procesar la solicitud. Revisa los logs para más detalles.",
flags: MessageFlags.Ephemeral,
});
}
}
},
};
// Función auxiliar para buscar componentes UserSelect en datos raw
function findUserSelectComponent(
components: ComponentData[] | undefined,
customId: string
): UserSelectComponent | null {
if (!components) return null;
for (const comp of components) {
if (comp.components) {
const found = findUserSelectComponent(comp.components, customId);
if (found) return found;
}
if (comp.component) {
if (comp.component.custom_id === customId) {
return comp.component as UserSelectComponent;
}
const found = findUserSelectComponent([comp.component], customId);
if (found) return found;
}
if (comp.custom_id === customId) {
return comp as UserSelectComponent;
}
}
return null;
}