Refactor y mejora de la función 'alliance' para validar enlaces de Discord y optimizar el manejo de errores

This commit is contained in:
2025-10-11 15:51:15 -05:00
parent 8114ea6fc4
commit b218dd4501

View File

@@ -1,27 +1,25 @@
import { import { Message } from "discord.js";
Message
} from "discord.js";
// Reemplaza instancia local -> usa singleton // Reemplaza instancia local -> usa singleton
import { prisma } from "../../core/database/prisma"; import { prisma } from "../../core/database/prisma";
import { replaceVars } from "../../core/lib/vars"; import { replaceVars } from "../../core/lib/vars";
import logger from "../../core/lib/logger"; import logger from "../../core/lib/logger";
import { sendComponentsV2Message } from "../../core/api/discordAPI"; import { sendComponentsV2Message } from "../../core/api/discordAPI";
// Regex para detectar URLs válidas (corregido) // Regex para detectar URLs válidas (corregido)
const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/gi; const URL_REGEX =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/gi;
// Dominios de Discord válidos para invitaciones // Dominios de Discord válidos para invitaciones
const DISCORD_DOMAINS = [ const DISCORD_DOMAINS = [
'discord.gg', "discord.gg",
'discord.com/invite', "discord.com/invite",
'discordapp.com/invite' "discordapp.com/invite",
]; ];
export async function alliance(message: Message) { export async function alliance(message: Message) {
try { try {
// Verificar que el mensaje tenga contenido // Verificar que el mensaje tenga contenido
if (!message.content || message.content.trim() === '') { if (!message.content || message.content.trim() === "") {
return; return;
} }
@@ -37,8 +35,8 @@ export async function alliance(message: Message) {
where: { where: {
guildId: message.guild!.id, guildId: message.guild!.id,
channelId: message.channel.id, channelId: message.channel.id,
isActive: true isActive: true,
} },
}); });
if (!allianceChannel) { if (!allianceChannel) {
@@ -54,7 +52,7 @@ export async function alliance(message: Message) {
} }
//@ts-ignore //@ts-ignore
const permissions = message.channel.permissionsFor(member); const permissions = message.channel.permissionsFor(member);
if (!permissions?.has('SendMessages')) { if (!permissions?.has("SendMessages")) {
return; // Usuario sin permisos return; // Usuario sin permisos
} }
@@ -69,24 +67,66 @@ export async function alliance(message: Message) {
for (const link of validDiscordLinks) { for (const link of validDiscordLinks) {
await processValidLink(message, allianceChannel, link); await processValidLink(message, allianceChannel, link);
} }
} catch (error) { } catch (error) {
logger.error({ err: error }, 'Error en función alliance'); logger.error({ err: error }, "Error en función alliance");
} }
} }
function extractValidLinks(content: string): string[] { function extractValidLinks(content: string): string[] {
const matches = content.match(URL_REGEX); const matches = content.match(URL_REGEX);
return matches || []; if (!matches) return [];
}
const results: string[] = [];
for (const raw of matches) {
try {
const url = new URL(raw);
// Sólo http/https
if (url.protocol !== "http:" && url.protocol !== "https:") continue;
// Rechazar URLs con query string o fragment (ej: ?event=...)
if (url.search || url.hash) continue;
// Normalizar hostname y pathname
const host = url.hostname.toLowerCase();
const pathname = url.pathname.replace(/\/+/g, "/");
// Validar formatos de invitación de Discord estrictos
if (host === "discord.gg") {
// debe ser /<codigo>
if (!/^\/[A-Za-z0-9]+$/.test(pathname)) continue;
} else if (
host === "discord.com" ||
host === "www.discord.com" ||
host === "discordapp.com" ||
host === "www.discordapp.com"
) {
// debe ser /invite/<codigo>
if (!/^\/invite\/[A-Za-z0-9]+$/.test(pathname)) continue;
}
// Si llegó hasta aquí, es aceptable
results.push(url.toString());
} catch {
// ignorar coincidencias inválidas
continue;
}
}
return results;
}
// ...existing code...
function validateDiscordLinks(links: string[]): string[] { function validateDiscordLinks(links: string[]): string[] {
return links.filter(link => { return links.filter((link) => {
return DISCORD_DOMAINS.some(domain => link.includes(domain)); return DISCORD_DOMAINS.some((domain) => link.includes(domain));
}); });
} }
async function processValidLink(message: Message, allianceChannel: any, link: string) { async function processValidLink(
message: Message,
allianceChannel: any,
link: string
) {
try { try {
// Verificar si el enlace de Discord es válido (opcional: hacer fetch) // Verificar si el enlace de Discord es válido (opcional: hacer fetch)
const inviteData = await validateDiscordInvite(link); const inviteData = await validateDiscordInvite(link);
@@ -99,7 +139,7 @@ async function processValidLink(message: Message, allianceChannel: any, link: st
await prisma.user.upsert({ await prisma.user.upsert({
where: { id: message.author.id }, where: { id: message.author.id },
update: {}, update: {},
create: { id: message.author.id } create: { id: message.author.id },
}); });
// Asegurar que el guild existe en la base de datos // Asegurar que el guild existe en la base de datos
@@ -108,8 +148,8 @@ async function processValidLink(message: Message, allianceChannel: any, link: st
update: {}, update: {},
create: { create: {
id: message.guild!.id, id: message.guild!.id,
name: message.guild!.name name: message.guild!.name,
} },
}); });
// Registrar el punto en el historial // Registrar el punto en el historial
@@ -119,23 +159,34 @@ async function processValidLink(message: Message, allianceChannel: any, link: st
guildId: message.guild!.id, guildId: message.guild!.id,
channelId: allianceChannel.id, channelId: allianceChannel.id,
messageId: message.id, messageId: message.id,
points: 1 points: 1,
} },
}); });
// Actualizar estadísticas del usuario // Actualizar estadísticas del usuario
await updateUserStats(message.author.id, message.guild!.id); await updateUserStats(message.author.id, message.guild!.id);
// Obtener estadísticas para reemplazar variables // Obtener estadísticas para reemplazar variables
const userStats = await getUserAllianceStats(message.author.id, message.guild!.id); const userStats = await getUserAllianceStats(
message.author.id,
message.guild!.id
);
// Enviar el bloque configurado usando Display Components // Enviar el bloque configurado usando Display Components
await sendBlockConfigV2(message, allianceChannel.blockConfigName, message.guild!.id, link, userStats, inviteData); await sendBlockConfigV2(
message,
logger.info(`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`); allianceChannel.blockConfigName,
message.guild!.id,
link,
userStats,
inviteData
);
logger.info(
`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`
);
} catch (error) { } catch (error) {
logger.error({ err: error }, 'Error procesando enlace válido'); logger.error({ err: error }, "Error procesando enlace válido");
} }
} }
@@ -146,12 +197,16 @@ async function validateDiscordInvite(link: string): Promise<any> {
if (!inviteCode) return null; if (!inviteCode) return null;
// Hacer una solicitud a la API de Discord para validar la invitación // Hacer una solicitud a la API de Discord para validar la invitación
const response = await fetch(`https://discord.com/api/v10/invites/${inviteCode}?with_counts=true`, { const response = await fetch(
method: 'GET', `https://discord.com/api/v10/invites/${inviteCode}?with_counts=true`,
{
method: "GET",
headers: { headers: {
'User-Agent': 'DiscordBot (https://github.com/discord/discord-api-docs, 1.0)' "User-Agent":
"DiscordBot (https://github.com/discord/discord-api-docs, 1.0)",
},
} }
}); );
if (response.status === 200) { if (response.status === 200) {
const inviteData = await response.json(); const inviteData = await response.json();
@@ -163,7 +218,7 @@ async function validateDiscordInvite(link: string): Promise<any> {
return null; return null;
} catch (error) { } catch (error) {
logger.error({ err: error }, 'Error validando invitación de Discord'); logger.error({ err: error }, "Error validando invitación de Discord");
return null; // En caso de error, considerar como inválido return null; // En caso de error, considerar como inválido
} }
} }
@@ -173,7 +228,7 @@ function extractInviteCode(link: string): string | null {
const patterns = [ const patterns = [
/discord\.gg\/([a-zA-Z0-9]+)/, /discord\.gg\/([a-zA-Z0-9]+)/,
/discord\.com\/invite\/([a-zA-Z0-9]+)/, /discord\.com\/invite\/([a-zA-Z0-9]+)/,
/discordapp\.com\/invite\/([a-zA-Z0-9]+)/ /discordapp\.com\/invite\/([a-zA-Z0-9]+)/,
]; ];
for (const pattern of patterns) { for (const pattern of patterns) {
@@ -193,8 +248,8 @@ async function updateUserStats(userId: string, guildId: string) {
let userStats = await prisma.partnershipStats.findFirst({ let userStats = await prisma.partnershipStats.findFirst({
where: { where: {
userId: userId, userId: userId,
guildId: guildId guildId: guildId,
} },
}); });
if (!userStats) { if (!userStats) {
@@ -206,18 +261,24 @@ async function updateUserStats(userId: string, guildId: string) {
weeklyPoints: 1, weeklyPoints: 1,
monthlyPoints: 1, monthlyPoints: 1,
lastWeeklyReset: now, lastWeeklyReset: now,
lastMonthlyReset: now lastMonthlyReset: now,
} },
}); });
return; return;
} }
// Verificar si necesita reset semanal (7 días) // Verificar si necesita reset semanal (7 días)
const weeksPassed = Math.floor((now.getTime() - userStats.lastWeeklyReset.getTime()) / (7 * 24 * 60 * 60 * 1000)); const weeksPassed = Math.floor(
(now.getTime() - userStats.lastWeeklyReset.getTime()) /
(7 * 24 * 60 * 60 * 1000)
);
const needsWeeklyReset = weeksPassed >= 1; const needsWeeklyReset = weeksPassed >= 1;
// Verificar si necesita reset mensual (30 días) // Verificar si necesita reset mensual (30 días)
const daysPassed = Math.floor((now.getTime() - userStats.lastMonthlyReset.getTime()) / (24 * 60 * 60 * 1000)); const daysPassed = Math.floor(
(now.getTime() - userStats.lastMonthlyReset.getTime()) /
(24 * 60 * 60 * 1000)
);
const needsMonthlyReset = daysPassed >= 30; const needsMonthlyReset = daysPassed >= 30;
// Actualizar estadísticas // Actualizar estadísticas
@@ -225,39 +286,58 @@ async function updateUserStats(userId: string, guildId: string) {
where: { where: {
userId_guildId: { userId_guildId: {
userId: userId, userId: userId,
guildId: guildId guildId: guildId,
} },
}, },
data: { data: {
totalPoints: { increment: 1 }, totalPoints: { increment: 1 },
weeklyPoints: needsWeeklyReset ? 1 : { increment: 1 }, weeklyPoints: needsWeeklyReset ? 1 : { increment: 1 },
monthlyPoints: needsMonthlyReset ? 1 : { increment: 1 }, monthlyPoints: needsMonthlyReset ? 1 : { increment: 1 },
lastWeeklyReset: needsWeeklyReset ? now : userStats.lastWeeklyReset, lastWeeklyReset: needsWeeklyReset ? now : userStats.lastWeeklyReset,
lastMonthlyReset: needsMonthlyReset ? now : userStats.lastMonthlyReset lastMonthlyReset: needsMonthlyReset ? now : userStats.lastMonthlyReset,
} },
}); });
} }
async function sendBlockConfigV2(message: Message, blockConfigName: string, guildId: string, validLink: string, userStats?: any, inviteObject?: any) { async function sendBlockConfigV2(
message: Message,
blockConfigName: string,
guildId: string,
validLink: string,
userStats?: any,
inviteObject?: any
) {
try { try {
// Obtener la configuración del bloque // Obtener la configuración del bloque
const blockConfig = await prisma.blockV2Config.findFirst({ const blockConfig = await prisma.blockV2Config.findFirst({
where: { where: {
guildId: guildId, guildId: guildId,
name: blockConfigName name: blockConfigName,
} },
}); });
if (!blockConfig) { if (!blockConfig) {
logger.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`); logger.error(
`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`
);
return; return;
} }
// Procesar las variables en la configuración usando la función unificada // Procesar las variables en la configuración usando la función unificada
const processedConfig = await processConfigVariables(blockConfig.config, message.author, message.guild!, userStats, inviteObject); const processedConfig = await processConfigVariables(
blockConfig.config,
message.author,
message.guild!,
userStats,
inviteObject
);
// Convertir el JSON plano a la estructura de Display Components correcta // Convertir el JSON plano a la estructura de Display Components correcta
const displayComponent = await convertConfigToDisplayComponent(processedConfig, message.author, message.guild!); const displayComponent = await convertConfigToDisplayComponent(
processedConfig,
message.author,
message.guild!
);
// Construir adjuntos desde la config si existen // Construir adjuntos desde la config si existen
const attachments = buildAttachmentsFromConfig(processedConfig); const attachments = buildAttachmentsFromConfig(processedConfig);
@@ -268,33 +348,49 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil
replyToMessageId: message.id, replyToMessageId: message.id,
attachments: attachments.length ? attachments : undefined, attachments: attachments.length ? attachments : undefined,
}); });
} catch (error) { } catch (error) {
logger.error({ err: error }, '❌ Error enviando bloque de configuración V2'); logger.error(
{ err: error },
"❌ Error enviando bloque de configuración V2"
);
// Fallback: usar mensaje simple // Fallback: usar mensaje simple
try { try {
await message.reply({ await message.reply({
content: '✅ ¡Enlace de alianza procesado correctamente!' content: "✅ ¡Enlace de alianza procesado correctamente!",
}); });
} catch (fallbackError) { } catch (fallbackError) {
logger.error({ err: fallbackError }, '❌ Error en fallback'); logger.error({ err: fallbackError }, "❌ Error en fallback");
} }
} }
} }
// Extrae adjuntos desde la config (base64) para usar attachment://<filename> // Extrae adjuntos desde la config (base64) para usar attachment://<filename>
function buildAttachmentsFromConfig(config: any) { function buildAttachmentsFromConfig(config: any) {
const results: { name: string; data: Buffer; description?: string; spoiler?: boolean }[] = []; const results: {
if (!config || typeof config !== 'object') return results; name: string;
data: Buffer;
description?: string;
spoiler?: boolean;
}[] = [];
if (!config || typeof config !== "object") return results;
const arr = Array.isArray(config.attachments) ? config.attachments : []; const arr = Array.isArray(config.attachments) ? config.attachments : [];
for (const item of arr) { for (const item of arr) {
if (!item || typeof item !== 'object') continue; if (!item || typeof item !== "object") continue;
const name = typeof item.name === 'string' && item.name.trim() ? item.name.trim() : null; const name =
const description = typeof item.description === 'string' ? item.description : undefined; typeof item.name === "string" && item.name.trim()
? item.name.trim()
: null;
const description =
typeof item.description === "string" ? item.description : undefined;
const spoiler = Boolean(item.spoiler); const spoiler = Boolean(item.spoiler);
const raw = typeof item.dataBase64 === 'string' ? item.dataBase64 : (typeof item.data === 'string' ? item.data : null); const raw =
typeof item.dataBase64 === "string"
? item.dataBase64
: typeof item.data === "string"
? item.data
: null;
if (!name || !raw) continue; if (!name || !raw) continue;
const buf = decodeBase64Payload(raw); const buf = decodeBase64Payload(raw);
if (!buf) continue; if (!buf) continue;
@@ -307,13 +403,13 @@ function decodeBase64Payload(raw: string): Buffer | null {
try { try {
let base64 = raw.trim(); let base64 = raw.trim();
// Soportar formatos: "base64:..." o data URLs "data:mime/type;base64,...." // Soportar formatos: "base64:..." o data URLs "data:mime/type;base64,...."
if (base64.startsWith('base64:')) { if (base64.startsWith("base64:")) {
base64 = base64.slice('base64:'.length); base64 = base64.slice("base64:".length);
} else if (base64.startsWith('data:')) { } else if (base64.startsWith("data:")) {
const comma = base64.indexOf(','); const comma = base64.indexOf(",");
if (comma !== -1) base64 = base64.slice(comma + 1); if (comma !== -1) base64 = base64.slice(comma + 1);
} }
return Buffer.from(base64, 'base64'); return Buffer.from(base64, "base64");
} catch { } catch {
return null; return null;
} }
@@ -321,10 +417,10 @@ function decodeBase64Payload(raw: string): Buffer | null {
// Helper: URLs http/https únicamente // Helper: URLs http/https únicamente
function isHttpUrl(url: unknown): url is string { function isHttpUrl(url: unknown): url is string {
if (typeof url !== 'string' || !url) return false; if (typeof url !== "string" || !url) return false;
try { try {
const u = new URL(url); const u = new URL(url);
return u.protocol === 'http:' || u.protocol === 'https:'; return u.protocol === "http:" || u.protocol === "https:";
} catch { } catch {
return false; return false;
} }
@@ -332,10 +428,10 @@ function isHttpUrl(url: unknown): url is string {
// Helper: permitir http/https y attachment:// para medios (thumbnail/media/file) // Helper: permitir http/https y attachment:// para medios (thumbnail/media/file)
function isMediaUrl(url: unknown): boolean { function isMediaUrl(url: unknown): boolean {
if (typeof url !== 'string' || !url) return false; if (typeof url !== "string" || !url) return false;
if (isHttpUrl(url)) return true; if (isHttpUrl(url)) return true;
const s = url as string; const s = url as string;
return s.startsWith('attachment://'); return s.startsWith("attachment://");
} }
// Helper: construir accessory de Link Button para Display Components // Helper: construir accessory de Link Button para Display Components
@@ -347,10 +443,10 @@ async function buildLinkAccessory(link: any, user: any, guild: any) {
// En botones de enlace solo se permite http/https // En botones de enlace solo se permite http/https
if (!isHttpUrl(processedUrl)) return null; if (!isHttpUrl(processedUrl)) return null;
const accessory: any = { type: 2, style: 5, url: processedUrl }; const accessory: any = { type: 2, style: 5, url: processedUrl };
if (link.label && typeof link.label === 'string' && link.label.trim()) { if (link.label && typeof link.label === "string" && link.label.trim()) {
accessory.label = link.label.trim().slice(0, 80); accessory.label = link.label.trim().slice(0, 80);
} }
if (link.emoji && typeof link.emoji === 'string') { if (link.emoji && typeof link.emoji === "string") {
const parsed = parseEmojiInput(link.emoji); const parsed = parseEmojiInput(link.emoji);
if (parsed) accessory.emoji = parsed; if (parsed) accessory.emoji = parsed;
} }
@@ -362,16 +458,27 @@ async function buildLinkAccessory(link: any, user: any, guild: any) {
} }
} }
async function convertConfigToDisplayComponent(config: any, user: any, guild: any): Promise<any> { async function convertConfigToDisplayComponent(
config: any,
user: any,
guild: any
): Promise<any> {
try { try {
const previewComponents: any[] = []; const previewComponents: any[] = [];
// Añadir imagen de portada primero si existe // Añadir imagen de portada primero si existe
if (config.coverImage) { if (config.coverImage) {
// @ts-ignore // @ts-ignore
const processedCoverUrl = await replaceVars(config.coverImage, user, guild); const processedCoverUrl = await replaceVars(
config.coverImage,
user,
guild
);
if (isMediaUrl(processedCoverUrl)) { if (isMediaUrl(processedCoverUrl)) {
previewComponents.push({ type: 12, items: [{ media: { url: processedCoverUrl } }] }); previewComponents.push({
type: 12,
items: [{ media: { url: processedCoverUrl } }],
});
} }
} }
@@ -380,7 +487,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
previewComponents.push({ previewComponents.push({
type: 10, type: 10,
// @ts-ignore // @ts-ignore
content: await replaceVars(config.title, user, guild) content: await replaceVars(config.title, user, guild),
}); });
} }
@@ -390,42 +497,75 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
if (c.type === 10) { if (c.type === 10) {
// Texto con accessory opcional: priorizar linkButton > thumbnail // Texto con accessory opcional: priorizar linkButton > thumbnail
// @ts-ignore // @ts-ignore
const processedContent = await replaceVars(c.content || " ", user, guild); const processedContent = await replaceVars(
c.content || " ",
user,
guild
);
// @ts-ignore // @ts-ignore
const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, user, guild) : null; const processedThumbnail = c.thumbnail
? await replaceVars(c.thumbnail, user, guild)
: null;
let accessory: any = null; let accessory: any = null;
if (c.linkButton) { if (c.linkButton) {
accessory = await buildLinkAccessory(c.linkButton, user, guild); accessory = await buildLinkAccessory(c.linkButton, user, guild);
} }
if (!accessory && processedThumbnail && isMediaUrl(processedThumbnail)) { if (
!accessory &&
processedThumbnail &&
isMediaUrl(processedThumbnail)
) {
accessory = { type: 11, media: { url: processedThumbnail } }; accessory = { type: 11, media: { url: processedThumbnail } };
} }
if (accessory) { if (accessory) {
previewComponents.push({ type: 9, components: [{ type: 10, content: processedContent }], accessory }); previewComponents.push({
type: 9,
components: [{ type: 10, content: processedContent }],
accessory,
});
} else { } else {
previewComponents.push({ type: 10, content: processedContent }); previewComponents.push({ type: 10, content: processedContent });
} }
} else if (c.type === 14) { } else if (c.type === 14) {
previewComponents.push({ type: 14, divider: c.divider ?? true, spacing: c.spacing ?? 1 }); previewComponents.push({
type: 14,
divider: c.divider ?? true,
spacing: c.spacing ?? 1,
});
} else if (c.type === 12) { } else if (c.type === 12) {
// Imagen - validar http/https o attachment:// // Imagen - validar http/https o attachment://
// @ts-ignore // @ts-ignore
const processedImageUrl = await replaceVars(c.url, user, guild); const processedImageUrl = await replaceVars(c.url, user, guild);
if (isMediaUrl(processedImageUrl)) { if (isMediaUrl(processedImageUrl)) {
previewComponents.push({ type: 12, items: [{ media: { url: processedImageUrl } }] }); previewComponents.push({
type: 12,
items: [{ media: { url: processedImageUrl } }],
});
} }
} }
} }
} }
// Retornar la estructura exacta que usa el editor // Retornar la estructura exacta que usa el editor
return { type: 17, accent_color: config.color ?? null, components: previewComponents }; return {
type: 17,
accent_color: config.color ?? null,
components: previewComponents,
};
} catch (error) { } catch (error) {
logger.error({ err: error }, 'Error convirtiendo configuración a Display Component'); logger.error(
return { type: 17, accent_color: null, components: [ { type: 10, content: 'Error al procesar la configuración del bloque.' } ] }; { 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." },
],
};
} }
} }
@@ -436,7 +576,7 @@ function parseEmojiInput(input?: string): any | null {
if (!trimmed) return null; if (!trimmed) return null;
const match = trimmed.match(/^<(a?):(\w+):(\d+)>$/); const match = trimmed.match(/^<(a?):(\w+):(\d+)>$/);
if (match) { if (match) {
const animated = match[1] === 'a'; const animated = match[1] === "a";
const name = match[2]; const name = match[2];
const id = match[3]; const id = match[3];
return { id, name, animated }; return { id, name, animated };
@@ -447,25 +587,39 @@ function parseEmojiInput(input?: string): any | null {
// Función helper para validar URLs (http/https y attachment:// para medios) // Función helper para validar URLs (http/https y attachment:// para medios)
async function processConfigVariables(config: any, user: any, guild: any, userStats?: any, inviteObject?: any): Promise<any> { async function processConfigVariables(
if (typeof config === 'string') { config: any,
user: any,
guild: any,
userStats?: any,
inviteObject?: any
): Promise<any> {
if (typeof config === "string") {
// Usar la función unificada replaceVars con todos los parámetros // Usar la función unificada replaceVars con todos los parámetros
return await replaceVars(config, user, guild, userStats, inviteObject); return await replaceVars(config, user, guild, userStats, inviteObject);
} }
if (Array.isArray(config)) { if (Array.isArray(config)) {
const processedArray = []; const processedArray: any[] = [];
for (const item of config) { for (const item of config) {
// @ts-ignore // @ts-ignore
processedArray.push(await processConfigVariables(item, user, guild, userStats, inviteObject)); processedArray.push(
await processConfigVariables(item, user, guild, userStats, inviteObject)
);
} }
return processedArray; return processedArray;
} }
if (config && typeof config === 'object') { if (config && typeof config === "object") {
const processedObject: any = {}; const processedObject: any = {};
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
processedObject[key] = await processConfigVariables(value, user, guild, userStats, inviteObject); processedObject[key] = await processConfigVariables(
value,
user,
guild,
userStats,
inviteObject
);
} }
return processedObject; return processedObject;
} }
@@ -473,13 +627,12 @@ async function processConfigVariables(config: any, user: any, guild: any, userSt
return config; return config;
} }
// Función auxiliar para obtener estadísticas // Función auxiliar para obtener estadísticas
export async function getUserAllianceStats(userId: string, guildId: string) { export async function getUserAllianceStats(userId: string, guildId: string) {
return prisma.partnershipStats.findFirst({ return prisma.partnershipStats.findFirst({
where: { where: {
userId: userId, userId: userId,
guildId: guildId guildId: guildId,
} },
}); });
} }