feat: implement reminder command with Appwrite integration and polling mechanism
This commit is contained in:
8
.env
8
.env
@@ -1,6 +1,14 @@
|
|||||||
# Configuración de ejemplo para optimización de memoria
|
# Configuración de ejemplo para optimización de memoria
|
||||||
# Copia este archivo como .env.test y ajusta los valores según tus necesidades
|
# Copia este archivo como .env.test y ajusta los valores según tus necesidades
|
||||||
|
|
||||||
|
APPWRITE_PROJECT_ID="68d8c4b2001abc54d3cd"
|
||||||
|
APPWRITE_PROJECT_NAME="amayo"
|
||||||
|
APPWRITE_ENDPOINT="https://nyc.cloud.appwrite.io/v1"
|
||||||
|
APPWRITE_API_KEY="standard_b123c1dbaaf7d3f99aa81492509d6277da0cb89eaf86bb8c42210bae0bb39c3ce911ddf101b6be2d3af8972d44ae63beb32c269c24eedbbe032a6c4c13a16c7e542be00c06ab8e9c131986729d45b68e25ac35e770442ea5285b7367938105a7b2d0e380e944d3bd6582db2c3311b3c9d84be5227718f795f30e31de0b9e8439"
|
||||||
|
APPWRITE_DATABASE_ID="68d8cb9a00250607e236"
|
||||||
|
PPWRITE_COLLECTION_REMINDERS_ID="reminders_id"
|
||||||
|
REMINDERS_POLL_INTERVAL_SECONDS="30"
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# CONFIGURACIÓN DE DISCORD
|
# CONFIGURACIÓN DE DISCORD
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|||||||
33
package-lock.json
generated
33
package-lock.json
generated
@@ -12,8 +12,11 @@
|
|||||||
"@google/genai": "1.20.0",
|
"@google/genai": "1.20.0",
|
||||||
"@google/generative-ai": "0.24.1",
|
"@google/generative-ai": "0.24.1",
|
||||||
"@prisma/client": "6.16.2",
|
"@prisma/client": "6.16.2",
|
||||||
|
"appwrite": "20.1.0",
|
||||||
|
"chrono-node": "2.9.0",
|
||||||
"discord-api-types": "0.38.24",
|
"discord-api-types": "0.38.24",
|
||||||
"discord.js": "14.22.1",
|
"discord.js": "14.22.1",
|
||||||
|
"node-appwrite": "19.1.0",
|
||||||
"prisma": "6.16.2",
|
"prisma": "6.16.2",
|
||||||
"redis": "5.8.2"
|
"redis": "5.8.2"
|
||||||
},
|
},
|
||||||
@@ -490,6 +493,12 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/appwrite": {
|
||||||
|
"version": "20.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-20.1.0.tgz",
|
||||||
|
"integrity": "sha512-dApZoqsb8Ug4Nbq5wlMd0WQxyBnxAC+utKgjiluRFimrwbbp8QVsntC4qbnYY8w25LzCRd4icKEFxCJrpQg3Qw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/arg": {
|
"node_modules/arg": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||||
@@ -575,6 +584,15 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chrono-node": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/citty": {
|
"node_modules/citty": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||||
@@ -960,6 +978,15 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-appwrite": {
|
||||||
|
"version": "19.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-19.1.0.tgz",
|
||||||
|
"integrity": "sha512-fq8IGvukZX73Zf70p61sKoACEB3d+vwv47GklMVQAODXJJ1vZqszJF9ljCzCFVKuhX/WiImcDKSYXFF9kci+wg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch-native-with-agent": "1.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
@@ -986,6 +1013,12 @@
|
|||||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch-native-with-agent": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/nypm": {
|
"node_modules/nypm": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"start:prod-optimized": "NODE_ENV=production ENABLE_MEMORY_OPTIMIZER=true NODE_OPTIONS='--max-old-space-size=512 --expose-gc' npx tsx src/main.ts",
|
"start:prod-optimized": "NODE_ENV=production ENABLE_MEMORY_OPTIMIZER=true NODE_OPTIONS='--max-old-space-size=512 --expose-gc' npx tsx src/main.ts",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": [ ],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
@@ -22,8 +22,11 @@
|
|||||||
"@google/genai": "1.20.0",
|
"@google/genai": "1.20.0",
|
||||||
"@google/generative-ai": "0.24.1",
|
"@google/generative-ai": "0.24.1",
|
||||||
"@prisma/client": "6.16.2",
|
"@prisma/client": "6.16.2",
|
||||||
|
"appwrite": "20.1.0",
|
||||||
|
"chrono-node": "2.9.0",
|
||||||
"discord-api-types": "0.38.24",
|
"discord-api-types": "0.38.24",
|
||||||
"discord.js": "14.22.1",
|
"discord.js": "14.22.1",
|
||||||
|
"node-appwrite": "19.1.0",
|
||||||
"prisma": "6.16.2",
|
"prisma": "6.16.2",
|
||||||
"redis": "5.8.2"
|
"redis": "5.8.2"
|
||||||
},
|
},
|
||||||
|
|||||||
89
src/commands/messages/others/recordar.ts
Normal file
89
src/commands/messages/others/recordar.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Comando para crear recordatorios con Appwrite: !recordar {texto} {fecha}
|
||||||
|
// Ejemplos:
|
||||||
|
// !recordar hacer esto el miércoles a las 5pm
|
||||||
|
// !recordar pagar el hosting mañana a las 9:00
|
||||||
|
// @ts-ignore
|
||||||
|
import { CommandMessage } from "../../../core/types/commands";
|
||||||
|
import type { Message } from 'discord.js';
|
||||||
|
import * as chrono from 'chrono-node';
|
||||||
|
import { scheduleReminder } from '../../../core/api/reminders';
|
||||||
|
import { isAppwriteConfigured } from '../../../core/api/appwrite';
|
||||||
|
|
||||||
|
function humanizeDate(d: Date) {
|
||||||
|
return d.toLocaleString('es-ES', { timeZone: 'UTC', hour12: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
name: 'recordar',
|
||||||
|
type: 'message',
|
||||||
|
aliases: ['reminder', 'rec'],
|
||||||
|
cooldown: 5,
|
||||||
|
description: 'Crea un recordatorio. Ej: !recordar hacer esto el miércoles a las 17:00',
|
||||||
|
category: 'Utilidad',
|
||||||
|
usage: 'recordar <texto> <cuando>',
|
||||||
|
run: async (message: Message, args: string[]) => {
|
||||||
|
if (!isAppwriteConfigured()) {
|
||||||
|
await message.reply('⚠️ Appwrite no está configurado en el bot. Define APPWRITE_* en variables de entorno.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = (args || []).join(' ').trim();
|
||||||
|
if (!text) {
|
||||||
|
await message.reply('Uso: !recordar <texto> <fecha/hora> Ej: !recordar enviar reporte el viernes a las 10:00');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsear en español, forzando fechas futuras cuando sea ambiguo
|
||||||
|
const ref = new Date();
|
||||||
|
const results = chrono.es.parse(text, ref, { forwardDate: true });
|
||||||
|
if (!results.length) {
|
||||||
|
await message.reply('❌ No pude entender cuándo. Intenta algo como: "mañana 9am", "el miércoles 17:00", "en 2 horas".');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = results[0];
|
||||||
|
const when = r.date();
|
||||||
|
|
||||||
|
if (!when || isNaN(when.getTime())) {
|
||||||
|
await message.reply('❌ La fecha/hora no es válida.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evitar fechas pasadas
|
||||||
|
if (when.getTime() <= Date.now()) {
|
||||||
|
await message.reply('❌ La fecha/hora ya pasó. Especifica una fecha futura.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraer el texto del recordatorio eliminando el fragmento reconocido de fecha
|
||||||
|
const matched = r.text || '';
|
||||||
|
let reminderText = text;
|
||||||
|
if (matched) {
|
||||||
|
const idx = text.toLowerCase().indexOf(matched.toLowerCase());
|
||||||
|
if (idx >= 0) {
|
||||||
|
reminderText = (text.slice(0, idx) + text.slice(idx + matched.length)).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Si quedó vacío, usar el texto completo
|
||||||
|
if (!reminderText) reminderText = text;
|
||||||
|
|
||||||
|
// Guardar en Appwrite
|
||||||
|
const iso = new Date(when.getTime()).toISOString();
|
||||||
|
try {
|
||||||
|
await scheduleReminder({
|
||||||
|
userId: message.author.id,
|
||||||
|
guildId: message.guild?.id || null,
|
||||||
|
channelId: message.channel.id,
|
||||||
|
message: reminderText,
|
||||||
|
executeAt: iso
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error programando recordatorio:', e);
|
||||||
|
await message.reply('❌ No pude guardar el recordatorio. Revisa la configuración de Appwrite.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const whenHuman = humanizeDate(when);
|
||||||
|
await message.reply(`✅ Recordatorio guardado para: ${whenHuman} UTC\nMensaje: ${reminderText}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
29
src/core/api/appwrite.ts
Normal file
29
src/core/api/appwrite.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Simple Appwrite client wrapper
|
||||||
|
// @ts-ignore
|
||||||
|
import { Client, Databases } from 'node-appwrite';
|
||||||
|
|
||||||
|
const endpoint = process.env.APPWRITE_ENDPOINT || '';
|
||||||
|
const projectId = process.env.APPWRITE_PROJECT_ID || '';
|
||||||
|
const apiKey = process.env.APPWRITE_API_KEY || '';
|
||||||
|
|
||||||
|
export const APPWRITE_DATABASE_ID = process.env.APPWRITE_DATABASE_ID || '';
|
||||||
|
export const APPWRITE_COLLECTION_REMINDERS_ID = process.env.APPWRITE_COLLECTION_REMINDERS_ID || '';
|
||||||
|
|
||||||
|
let client: Client | null = null;
|
||||||
|
let databases: Databases | null = null;
|
||||||
|
|
||||||
|
function ensureClient() {
|
||||||
|
if (!endpoint || !projectId || !apiKey) return null;
|
||||||
|
if (client) return client;
|
||||||
|
client = new Client().setEndpoint(endpoint).setProject(projectId).setKey(apiKey);
|
||||||
|
databases = new Databases(client);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDatabases(): Databases | null {
|
||||||
|
return ensureClient() ? (databases as Databases) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAppwriteConfigured(): boolean {
|
||||||
|
return Boolean(endpoint && projectId && apiKey && APPWRITE_DATABASE_ID && APPWRITE_COLLECTION_REMINDERS_ID);
|
||||||
|
}
|
||||||
121
src/core/api/reminders.ts
Normal file
121
src/core/api/reminders.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const sdk: any = require('node-appwrite');
|
||||||
|
import type Amayo from '../client';
|
||||||
|
import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite';
|
||||||
|
|
||||||
|
export type ReminderDoc = {
|
||||||
|
$id?: string;
|
||||||
|
userId: string;
|
||||||
|
guildId?: string | null;
|
||||||
|
channelId?: string | null;
|
||||||
|
message: string;
|
||||||
|
executeAt: string; // ISO string
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Row type returned by Appwrite for our reminders
|
||||||
|
export type ReminderRow = ReminderDoc & {
|
||||||
|
$id: string;
|
||||||
|
$createdAt?: string;
|
||||||
|
$updatedAt?: string;
|
||||||
|
$permissions?: string[];
|
||||||
|
$collectionId?: string;
|
||||||
|
$databaseId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function scheduleReminder(doc: ReminderDoc): Promise<string> {
|
||||||
|
const db = getDatabases();
|
||||||
|
if (!db || !isAppwriteConfigured()) throw new Error('Appwrite no está configurado');
|
||||||
|
const data = {
|
||||||
|
userId: doc.userId,
|
||||||
|
guildId: doc.guildId ?? null,
|
||||||
|
channelId: doc.channelId ?? null,
|
||||||
|
message: doc.message,
|
||||||
|
executeAt: doc.executeAt,
|
||||||
|
createdAt: doc.createdAt ?? new Date().toISOString()
|
||||||
|
} as Record<string, any>;
|
||||||
|
const res = await db.createDocument(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, sdk.ID.unique(), data) as unknown as { $id: string };
|
||||||
|
return res.$id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDueReminders(limit = 25): Promise<ReminderRow[]> {
|
||||||
|
const db = getDatabases();
|
||||||
|
if (!db || !isAppwriteConfigured()) return [];
|
||||||
|
const nowIso = new Date().toISOString();
|
||||||
|
try {
|
||||||
|
const list = await db.listDocuments(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, [
|
||||||
|
sdk.Query.lessThanEqual('executeAt', nowIso),
|
||||||
|
sdk.Query.limit(limit)
|
||||||
|
]) as unknown as { documents?: ReminderRow[] };
|
||||||
|
return (list.documents || []) as ReminderRow[];
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error listando recordatorios vencidos:', e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||||
|
const userId: string = doc.userId;
|
||||||
|
const channelId: string | null = (doc.channelId as string | null) || null;
|
||||||
|
const message: string = doc.message || '';
|
||||||
|
|
||||||
|
let delivered = false;
|
||||||
|
// 1) Intentar en el canal original si existe y es de texto
|
||||||
|
if (channelId) {
|
||||||
|
try {
|
||||||
|
const ch: any = await bot.channels.fetch(channelId).catch(() => null);
|
||||||
|
if (ch && typeof ch.send === 'function') {
|
||||||
|
await ch.send({ content: `⏰ <@${userId}> Recordatorio: ${message}` });
|
||||||
|
delivered = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No se pudo enviar al canal original:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2) Fallback: DM al usuario
|
||||||
|
if (!delivered) {
|
||||||
|
try {
|
||||||
|
const user = await bot.users.fetch(userId);
|
||||||
|
await user.send({ content: `⏰ Recordatorio: ${message}` });
|
||||||
|
delivered = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No se pudo enviar DM al usuario:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startReminderPoller(bot: Amayo) {
|
||||||
|
if (!isAppwriteConfigured()) {
|
||||||
|
console.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`);
|
||||||
|
|
||||||
|
const timer = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const due = await fetchDueReminders(50);
|
||||||
|
if (!due.length) return;
|
||||||
|
for (const d of due) {
|
||||||
|
const ok = await deliverReminder(bot, d);
|
||||||
|
if (!ok) continue; // Dejar para reintento futuro
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error en ciclo de recordatorios:', e);
|
||||||
|
}
|
||||||
|
}, Math.max(10, intervalSec) * 1000);
|
||||||
|
|
||||||
|
// Node no debería impedir salida por timers; por si acaso, unref
|
||||||
|
// @ts-ignore
|
||||||
|
if (typeof timer.unref === 'function') timer.unref();
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
@@ -35,8 +35,13 @@ const getInviteObject = (invite?: Invite) => invite?.guild ? {
|
|||||||
icon: invite.guild.icon ? `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.webp?size=256` : ''
|
icon: invite.guild.icon ? `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.webp?size=256` : ''
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
// Helper: calcula el rank dentro del servidor para un campo (weeklyPoints / monthlyPoints)
|
// Helper: calcula el rank dentro del servidor para un campo (weeklyPoints / monthlyPoints / totalPoints)
|
||||||
async function computeRankInGuild(guildId: string, userId: string, field: 'weeklyPoints' | 'monthlyPoints', knownPoints?: number): Promise<number> {
|
async function computeRankInGuild(
|
||||||
|
guildId: string,
|
||||||
|
userId: string,
|
||||||
|
field: 'weeklyPoints' | 'monthlyPoints' | 'totalPoints',
|
||||||
|
knownPoints?: number
|
||||||
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
let points = knownPoints;
|
let points = knownPoints;
|
||||||
if (typeof points !== 'number') {
|
if (typeof points !== 'number') {
|
||||||
@@ -85,6 +90,13 @@ export const VARIABLES: Record<string, VarResolver> = {
|
|||||||
const rank = await computeRankInGuild(guildId, userId, 'monthlyPoints', stats?.monthlyPoints);
|
const rank = await computeRankInGuild(guildId, userId, 'monthlyPoints', stats?.monthlyPoints);
|
||||||
return String(rank || 0);
|
return String(rank || 0);
|
||||||
},
|
},
|
||||||
|
'user.rankTotal': async ({ user, guild, stats }) => {
|
||||||
|
const userId = getUserId(user);
|
||||||
|
const guildId = guild?.id;
|
||||||
|
if (!userId || !guildId) return '0';
|
||||||
|
const rank = await computeRankInGuild(guildId, userId, 'totalPoints', stats?.totalPoints);
|
||||||
|
return String(rank || 0);
|
||||||
|
},
|
||||||
|
|
||||||
// GUILD INFO
|
// GUILD INFO
|
||||||
'guild.name': ({ guild }) => guild?.name ?? '',
|
'guild.name': ({ guild }) => guild?.name ?? '',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { registeringCommands } from "./core/api/discordAPI";
|
|||||||
import {loadComponents} from "./core/lib/components";
|
import {loadComponents} from "./core/lib/components";
|
||||||
import { startMemoryMonitor } from "./core/memory/memoryMonitor";
|
import { startMemoryMonitor } from "./core/memory/memoryMonitor";
|
||||||
import {memoryOptimizer} from "./core/memory/memoryOptimizer";
|
import {memoryOptimizer} from "./core/memory/memoryOptimizer";
|
||||||
|
import { startReminderPoller } from "./core/api/reminders";
|
||||||
|
|
||||||
// Activar monitor de memoria si se define la variable
|
// Activar monitor de memoria si se define la variable
|
||||||
const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10);
|
const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10);
|
||||||
@@ -178,6 +179,9 @@ async function bootstrap() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Iniciar poller de recordatorios si Appwrite está configurado
|
||||||
|
startReminderPoller(bot);
|
||||||
|
|
||||||
console.log("✅ Bot conectado a Discord");
|
console.log("✅ Bot conectado a Discord");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user