Arreglando errores garrafales como bugs a la hora de crear la alianza o errores del displayComponent.

This commit is contained in:
2025-09-19 21:56:39 -05:00
parent 58bb3844ad
commit 6707760e83
11 changed files with 1292 additions and 297 deletions

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="dev" uuid="ed4d8df0-abeb-4f93-a766-8c8fe5abee6c">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/prisma/dev.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

BIN
prisma/dev.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,76 @@
-- CreateTable
CREATE TABLE "Guild" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"prefix" TEXT NOT NULL DEFAULT '!'
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY
);
-- CreateTable
CREATE TABLE "PartnershipStats" (
"totalPoints" INTEGER NOT NULL DEFAULT 0,
"weeklyPoints" INTEGER NOT NULL DEFAULT 0,
"monthlyPoints" INTEGER NOT NULL DEFAULT 0,
"lastWeeklyReset" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastMonthlyReset" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
PRIMARY KEY ("userId", "guildId"),
CONSTRAINT "PartnershipStats_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "PartnershipStats_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Alliance" (
"id" TEXT NOT NULL PRIMARY KEY,
"channelId" TEXT NOT NULL,
"messageId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"guildId" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
CONSTRAINT "Alliance_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Alliance_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "EmbedConfig" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"color" TEXT,
"title" TEXT,
"url" TEXT,
"authorName" TEXT,
"authorIconURL" TEXT,
"authorURL" TEXT,
"description" TEXT,
"thumbnailURL" TEXT,
"imageURL" TEXT,
"footerText" TEXT,
"footerIconURL" TEXT,
"fields" TEXT DEFAULT '[]',
"guildId" TEXT NOT NULL,
CONSTRAINT "EmbedConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "BlockV2Config" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"config" JSONB NOT NULL,
"guildId" TEXT NOT NULL,
CONSTRAINT "BlockV2Config_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Alliance_messageId_key" ON "Alliance"("messageId");
-- CreateIndex
CREATE UNIQUE INDEX "EmbedConfig_guildId_name_key" ON "EmbedConfig"("guildId", "name");
-- CreateIndex
CREATE UNIQUE INDEX "BlockV2Config_guildId_name_key" ON "BlockV2Config"("guildId", "name");

View File

@@ -0,0 +1,31 @@
-- CreateTable
CREATE TABLE "AllianceChannel" (
"id" TEXT NOT NULL PRIMARY KEY,
"channelId" TEXT NOT NULL,
"blockConfigName" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"guildId" TEXT NOT NULL,
CONSTRAINT "AllianceChannel_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "PointHistory" (
"id" TEXT NOT NULL PRIMARY KEY,
"points" INTEGER NOT NULL DEFAULT 1,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"messageId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
CONSTRAINT "PointHistory_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "PointHistory_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "PointHistory_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "AllianceChannel" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "AllianceChannel_channelId_key" ON "AllianceChannel"("channelId");
-- CreateIndex
CREATE UNIQUE INDEX "AllianceChannel_guildId_channelId_key" ON "AllianceChannel"("guildId", "channelId");

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"

View File

@@ -30,6 +30,9 @@ model Guild {
// ✅ CAMBIO: Ahora un Guild puede tener MÚLTIPLES configuraciones de embed.
embedConfigs EmbedConfig[]
BlockV2Config BlockV2Config[]
// ✅ NUEVAS RELACIONES
allianceChannels AllianceChannel[]
pointsHistory PointHistory[]
}
/*
* -----------------------------------------------------------------------------
@@ -43,6 +46,8 @@ model User {
// Relaciones
partnerStats PartnershipStats[]
createdAlliances Alliance[]
// ✅ NUEVA RELACIÓN
pointsHistory PointHistory[]
}
/*
@@ -96,6 +101,62 @@ model Alliance {
creatorId String
}
/*
* -----------------------------------------------------------------------------
* Modelo para Canales de Alianza
* -----------------------------------------------------------------------------
* Gestiona qué canales están configurados para otorgar puntos y qué bloque enviar
*/
model AllianceChannel {
id String @id @default(cuid())
channelId String @unique // ID del canal de Discord
// Configuración del canal
blockConfigName String // Nombre del BlockV2Config a enviar
isActive Boolean @default(true)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// --- Relaciones ---
guild Guild @relation(fields: [guildId], references: [id])
guildId String
// Historial de puntos otorgados en este canal
pointsHistory PointHistory[]
// Un canal solo puede estar en un servidor
@@unique([guildId, channelId])
}
/*
* -----------------------------------------------------------------------------
* Modelo para Historial de Puntos
* -----------------------------------------------------------------------------
* Registra cada vez que un usuario gana puntos con fecha y hora
*/
model PointHistory {
id String @id @default(cuid())
// Información del punto otorgado
points Int @default(1)
timestamp DateTime @default(now())
messageId String // ID del mensaje que generó el punto
// --- Relaciones ---
user User @relation(fields: [userId], references: [id])
userId String
guild Guild @relation(fields: [guildId], references: [id])
guildId String
allianceChannel AllianceChannel @relation(fields: [channelId], references: [id])
channelId String
}
/*
* -----------------------------------------------------------------------------
* Modelo para la Configuración del Embed

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "setchannel-alliance",
type: "message",
aliases: ["alchannel", "channelally"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
// Validar argumentos
if (args.length < 2) {
return message.reply("❌ Uso correcto: `!setchannel-alliance <#canal|ID> <blockConfigName>`");
}
const channelInput = args[0];
const blockConfigName = args[1];
// Extraer ID del canal
let channelId: string;
// Si es una mención de canal (#canal)
if (channelInput.startsWith('<#') && channelInput.endsWith('>')) {
channelId = channelInput.slice(2, -1);
}
// Si es solo un ID
else if (/^\d+$/.test(channelInput)) {
channelId = channelInput;
}
else {
return message.reply("❌ Formato de canal inválido. Usa `#canal` o el ID del canal.");
}
try {
// Verificar que el canal existe en el servidor
const channel = await message.guild?.channels.fetch(channelId);
if (!channel) {
return message.reply("❌ El canal especificado no existe en este servidor.");
}
// Verificar que el canal es un canal de texto
if (!channel.isTextBased()) {
return message.reply("❌ El canal debe ser un canal de texto.");
}
// Verificar que existe el blockConfig
const blockConfig = await client.prisma.blockV2Config.findFirst({
where: {
guildId: message.guildId,
name: blockConfigName
}
});
if (!blockConfig) {
return message.reply(`❌ No se encontró el bloque de configuración \`${blockConfigName}\`. Asegúrate de que exista.`);
}
// Configurar el canal de alianzas
const allianceChannel = await client.prisma.allianceChannel.upsert({
where: {
guildId_channelId: {
guildId: message.guildId,
channelId: channelId
}
},
create: {
guildId: message.guildId,
channelId: channelId,
blockConfigName: blockConfigName,
isActive: true
},
update: {
blockConfigName: blockConfigName,
isActive: true,
updatedAt: new Date()
}
});
return message.reply(`✅ Canal de alianzas configurado correctamente!\n\n` +
`**Canal:** <#${channelId}>\n` +
`**Configuración:** \`${blockConfigName}\`\n` +
`**Estado:** Activo\n\n` +
`Los enlaces de Discord válidos en este canal ahora otorgarán puntos de alianza.`);
} catch (error) {
console.error('Error configurando canal de alianzas:', error);
return message.reply("❌ Ocurrió un error al configurar el canal de alianzas. Inténtalo de nuevo.");
}
}
}

View File

@@ -1,8 +1,17 @@
import {Guild, User} from "discord.js";
import {Guild, Invite, User} from "discord.js";
export async function replaceVars(text: string, user: User | undefined, guild: Guild | undefined, stats?: any): Promise<string> {
//@ts-ignore
export async function replaceVars(text: string, user: User | undefined, guild: Guild | undefined, stats?: any, invite: Invite | undefined): Promise<string> {
if(!text) return '';
// Crear inviteObject solo si invite existe y tiene guild
const inviteObject = invite?.guild ? {
guild: {
//@ts-ignore
icon: `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.webp?size=256`
}
} : null;
return text
/**
* USER INFO
@@ -12,9 +21,23 @@ export async function replaceVars(text: string, user: User | undefined, guild: G
.replace(/(user\.mention)/g, user ? `<@${user.id}>` : '')
.replace(/(user\.avatar)/g, user?.displayAvatarURL({ forceStatic: false }) ?? '')
/**
* USER STATS
*/
.replace(/(user\.pointsAll)/g, stats?.totalPoints?.toString() ?? '0')
.replace(/(user\.pointsWeekly)/g, stats?.weeklyPoints?.toString() ?? '0')
.replace(/(user\.pointsMonthly)/g, stats?.monthlyPoints?.toString() ?? '0')
/**
* GUILD INFO
*/
.replace(/(guild\.name)/g, guild?.name ?? '')
.replace(/(guild\.icon)/g, guild?.iconURL({ forceStatic: false }) ?? '');
.replace(/(guild\.icon)/g, guild?.iconURL({ forceStatic: false }) ?? '')
/**
* INVITE INFO
*/
.replace(/(invite\.name)/g, invite?.guild?.name ?? "")
.replace(/(invite\.icon)/g, inviteObject?.guild.icon ?? '0')
}

View File

@@ -0,0 +1,424 @@
import {
Message
} from "discord.js";
// Se agrega ts
//@ts-ignore
import { PrismaClient } from "@prisma/client";
import { replaceVars } from "../../core/lib/vars";
const prisma = new PrismaClient();
// 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;
// Dominios de Discord válidos para invitaciones
const DISCORD_DOMAINS = [
'discord.gg',
'discord.com/invite',
'discordapp.com/invite'
];
export async function alliance(message: Message) {
try {
// Verificar que el mensaje tenga contenido
if (!message.content || message.content.trim() === '') {
return;
}
// Buscar enlaces en el mensaje
const links = extractValidLinks(message.content);
if (links.length === 0) {
return; // No hay enlaces válidos
}
// Verificar si el canal está configurado para alianzas
const allianceChannel = await prisma.allianceChannel.findFirst({
where: {
guildId: message.guild!.id,
channelId: message.channel.id,
isActive: true
}
});
if (!allianceChannel) {
return; // Canal no configurado para alianzas
}
// Verificar permisos del usuario (corregido para evitar errores con tipos de canal)
const member = await message.guild!.members.fetch(message.author.id);
// Verificar que es un canal de texto antes de verificar permisos
if (!message.channel.isTextBased()) {
return; // No es un canal de texto
}
//@ts-ignore
const permissions = message.channel.permissionsFor(member);
if (!permissions?.has('SendMessages')) {
return; // Usuario sin permisos
}
// Validar que los enlaces sean de Discord (invitaciones)
const validDiscordLinks = validateDiscordLinks(links);
if (validDiscordLinks.length === 0) {
return; // No hay enlaces válidos de Discord
}
// Procesar cada enlace válido
for (const link of validDiscordLinks) {
await processValidLink(message, allianceChannel, link);
}
} catch (error) {
console.error('Error en función alliance:', error);
}
}
function extractValidLinks(content: string): string[] {
const matches = content.match(URL_REGEX);
return matches || [];
}
function validateDiscordLinks(links: string[]): string[] {
return links.filter(link => {
return DISCORD_DOMAINS.some(domain => link.includes(domain));
});
}
async function processValidLink(message: Message, allianceChannel: any, link: string) {
try {
// Verificar si el enlace de Discord es válido (opcional: hacer fetch)
const inviteData = await validateDiscordInvite(link);
if (!inviteData) {
return; // Enlace inválido o expirado
}
// Asegurar que el usuario existe en la base de datos
await prisma.user.upsert({
where: { id: message.author.id },
update: {},
create: { id: message.author.id }
});
// Asegurar que el guild existe en la base de datos
await prisma.guild.upsert({
where: { id: message.guild!.id },
update: {},
create: {
id: message.guild!.id,
name: message.guild!.name
}
});
// Registrar el punto en el historial
await prisma.pointHistory.create({
data: {
userId: message.author.id,
guildId: message.guild!.id,
channelId: allianceChannel.id,
messageId: message.id,
points: 1
}
});
// Actualizar estadísticas del usuario
await updateUserStats(message.author.id, message.guild!.id);
// Obtener estadísticas para reemplazar variables
const userStats = await getUserAllianceStats(message.author.id, message.guild!.id);
// 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}`);
} catch (error) {
console.error('Error procesando enlace válido:', error);
}
}
async function validateDiscordInvite(link: string): Promise<any> {
try {
// Extraer el código de invitación del enlace
const inviteCode = extractInviteCode(link);
if (!inviteCode) return null;
// 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`, {
method: 'GET',
headers: {
'User-Agent': 'DiscordBot (https://github.com/discord/discord-api-docs, 1.0)'
}
});
if (response.status === 200) {
const inviteData = await response.json();
// Verificar que la invitación tenga un servidor válido
if (inviteData.guild && inviteData.guild.id) {
return inviteData; // Retornar datos completos de la invitación
}
}
return null;
} catch (error) {
console.error('Error validando invitación de Discord:', error);
return null; // En caso de error, considerar como inválido
}
}
function extractInviteCode(link: string): string | null {
// Patrones para extraer códigos de invitación
const patterns = [
/discord\.gg\/([a-zA-Z0-9]+)/,
/discord\.com\/invite\/([a-zA-Z0-9]+)/,
/discordapp\.com\/invite\/([a-zA-Z0-9]+)/
];
for (const pattern of patterns) {
const match = link.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
}
async function updateUserStats(userId: string, guildId: string) {
const now = new Date();
// Obtener o crear las estadísticas del usuario
let userStats = await prisma.partnershipStats.findFirst({
where: {
userId: userId,
guildId: guildId
}
});
if (!userStats) {
await prisma.partnershipStats.create({
data: {
userId: userId,
guildId: guildId,
totalPoints: 1,
weeklyPoints: 1,
monthlyPoints: 1,
lastWeeklyReset: now,
lastMonthlyReset: now
}
});
return;
}
// Verificar si necesita reset semanal (7 días)
const weeksPassed = Math.floor((now.getTime() - userStats.lastWeeklyReset.getTime()) / (7 * 24 * 60 * 60 * 1000));
const needsWeeklyReset = weeksPassed >= 1;
// Verificar si necesita reset mensual (30 días)
const daysPassed = Math.floor((now.getTime() - userStats.lastMonthlyReset.getTime()) / (24 * 60 * 60 * 1000));
const needsMonthlyReset = daysPassed >= 30;
// Actualizar estadísticas
await prisma.partnershipStats.update({
where: {
userId_guildId: {
userId: userId,
guildId: guildId
}
},
data: {
totalPoints: { increment: 1 },
weeklyPoints: needsWeeklyReset ? 1 : { increment: 1 },
monthlyPoints: needsMonthlyReset ? 1 : { increment: 1 },
lastWeeklyReset: needsWeeklyReset ? now : userStats.lastWeeklyReset,
lastMonthlyReset: needsMonthlyReset ? now : userStats.lastMonthlyReset
}
});
}
async function sendBlockConfigV2(message: Message, blockConfigName: string, guildId: string, validLink: string, userStats?: any, inviteObject?: any) {
try {
// Obtener la configuración del bloque
const blockConfig = await prisma.blockV2Config.findFirst({
where: {
guildId: guildId,
name: blockConfigName
}
});
if (!blockConfig) {
console.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`);
return;
}
// Procesar las variables en la configuración usando la función unificada
const processedConfig = await processConfigVariables(blockConfig.config, message.author, message.guild!, userStats, inviteObject);
// Convertir el JSON plano a la estructura de Display Components correcta
const displayComponent = await convertConfigToDisplayComponent(processedConfig, message.author, message.guild!);
// Enviar usando Display Components con la flag correcta
// Usar la misma estructura que el editor: flag 32768 y type 17
//@ts-ignore
await message.reply({
flags: 32768, // Equivalente a MessageFlags.IsComponentsV2
components: [displayComponent]
});
} catch (error) {
console.error('❌ Error enviando bloque de configuración V2:', error);
console.log('Detalles del error:', error);
// Fallback: usar mensaje simple
try {
await message.reply({
content: '✅ ¡Enlace de alianza procesado correctamente!'
});
} catch (fallbackError) {
console.error('❌ Error en fallback:', fallbackError);
}
}
}
async function convertConfigToDisplayComponent(config: any, user: any, guild: any): Promise<any> {
try {
const previewComponents = [];
// Añadir imagen de portada primero si existe
if (config.coverImage && isValidUrl(config.coverImage)) {
const processedCoverUrl = await replaceVars(config.coverImage, user, guild);
if (isValidUrl(processedCoverUrl)) {
previewComponents.push({
type: 12,
items: [{ media: { url: processedCoverUrl } }]
});
}
}
// Añadir título después de la portada
if (config.title) {
previewComponents.push({
type: 10,
content: await replaceVars(config.title, user, guild)
});
}
// Procesar componentes en orden (igual que el editor)
if (config.components && Array.isArray(config.components)) {
for (const c of config.components) {
if (c.type === 10) {
// Componente de texto con thumbnail opcional
const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, user, guild) : null;
if (processedThumbnail && isValidUrl(processedThumbnail)) {
// Si tiene thumbnail válido, usar contenedor tipo 9 con accessory
previewComponents.push({
type: 9,
components: [
{
type: 10,
content: await replaceVars(c.content || " ", user, guild)
}
],
accessory: {
type: 11,
media: { url: processedThumbnail }
}
});
} else {
// Sin thumbnail o thumbnail inválido, componente normal
previewComponents.push({
type: 10,
content: await replaceVars(c.content || " ", user, guild)
});
}
} else if (c.type === 14) {
// Separador
previewComponents.push({
type: 14,
divider: c.divider ?? true,
spacing: c.spacing ?? 1
});
} else if (c.type === 12) {
// Imagen - validar URL también
const processedImageUrl = await replaceVars(c.url, user, guild);
if (isValidUrl(processedImageUrl)) {
previewComponents.push({
type: 12,
items: [{ media: { url: processedImageUrl } }]
});
}
}
}
}
// Retornar la estructura exacta que usa el editor
return {
type: 17, // Container type
accent_color: config.color ?? null,
components: previewComponents
};
} catch (error) {
console.error('Error convirtiendo configuración a Display Component:', error);
// Fallback: crear un componente básico
return {
type: 17,
accent_color: null,
components: [
{ type: 10, content: 'Error al procesar la configuración del bloque.' }
]
};
}
}
// Función helper para validar URLs
function isValidUrl(url: string): boolean {
if (!url || typeof url !== 'string') return false;
try {
new URL(url);
return url.startsWith('http://') || url.startsWith('https://');
} catch {
return false;
}
}
async function processConfigVariables(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
return await replaceVars(config, user, guild, userStats, inviteObject);
}
if (Array.isArray(config)) {
const processedArray = [];
for (const item of config) {
processedArray.push(await processConfigVariables(item, user, guild, userStats, inviteObject));
}
return processedArray;
}
if (config && typeof config === 'object') {
const processedObject: any = {};
for (const [key, value] of Object.entries(config)) {
processedObject[key] = await processConfigVariables(value, user, guild, userStats, inviteObject);
}
return processedObject;
}
return config;
}
// Función auxiliar para obtener estadísticas
export async function getUserAllianceStats(userId: string, guildId: string) {
return prisma.partnershipStats.findFirst({
where: {
userId: userId,
guildId: guildId
}
});
}

View File

@@ -2,10 +2,12 @@ import {bot} from "../main";
import {Events} from "discord.js";
import {redis} from "../core/redis";
import {commands} from "../core/loader";
import {alliance} from "./extras/alliace";
bot.on(Events.MessageCreate, async (message) => {
if (message.author.bot) return;
await alliance(message);
const server = await bot.prisma.guild.upsert({
where: {
id: message.guildId