zelin-bot / src /polls.js
Z User
v5.8.5: Gemma 4, MC Wiki, MC Player, anti-hallucination, CPU optimizations
ee826ee
/**
* polls.js — Encuestas y Votaciones v1.1
* FIX: restoreActivePolls maneja mensajes eliminados sin crashear
*/
import { EmbedBuilder } from 'discord.js';
import * as db from './db.js';
const activePoll = new Map();
const EMOJIS = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣'];
export async function createPoll(channel, question, options, durationMin = 60) {
if (options.length < 2 || options.length > 5) throw new Error('Entre 2 y 5 opciones');
const embed = new EmbedBuilder()
.setTitle(`🗳️ ${question}`)
.setColor(0x5865f2)
.setDescription(options.map((o, i) => `${EMOJIS[i]} ${o}`).join('\n'))
.setFooter({ text: `Termina en ${durationMin} minutos • Zelin` })
.setTimestamp(new Date(Date.now() + durationMin * 60000));
const msg = await channel.send({ embeds: [embed] });
for (let i = 0; i < options.length; i++) {
await msg.react(EMOJIS[i]);
}
const pollData = {
messageId: msg.id,
channelId: channel.id,
question,
options,
startedAt: Date.now(),
endsAt : Date.now() + durationMin * 60000,
};
activePoll.set(channel.id, pollData);
await db.memSet(`poll.active.${channel.id}`, pollData, 'polls').catch(() => {});
setTimeout(() => closePoll(channel, msg.id, pollData), durationMin * 60000);
return msg;
}
async function closePoll(channel, messageId, pollData) {
try {
// FIX: manejar mensaje eliminado
const msg = await channel.messages.fetch(messageId).catch(() => null);
if (!msg) {
// Mensaje eliminado — limpiar sin crashear
console.warn(`[Poll] Mensaje ${messageId} no encontrado (posiblemente eliminado), cerrando encuesta`);
activePoll.delete(pollData.channelId);
await db.db.execute({ sql: 'DELETE FROM zelin_memory WHERE key = ?', args: [`poll.active.${pollData.channelId}`] }).catch(() => {});
await channel.send(`La encuesta **"${pollData.question}"** finalizó pero su mensaje fue eliminado.`).catch(() => {});
return;
}
const results = await Promise.all(
pollData.options.map(async (opt, i) => {
const reaction = msg.reactions.cache.get(EMOJIS[i]);
const count = (reaction?.count ?? 1) - 1;
return { option: opt, votes: count };
})
);
results.sort((a, b) => b.votes - a.votes);
const winner = results[0];
const embed = new EmbedBuilder()
.setTitle(`🏆 Resultados: ${pollData.question}`)
.setColor(0x00ff00)
.setDescription(results.map((r, i) =>
`${i === 0 ? '🥇' : i === 1 ? '🥈' : '🥉'} ${r.option}: **${r.votes} votos**`
).join('\n'))
.setFooter({ text: `Ganador: ${winner.option} • Zelin` })
.setTimestamp();
await channel.send({ embeds: [embed] });
activePoll.delete(pollData.channelId);
await db.db.execute({ sql: 'DELETE FROM zelin_memory WHERE key = ?', args: [`poll.active.${pollData.channelId}`] }).catch(() => {});
} catch (err) {
console.error('[Poll] Error cerrando encuesta:', err.message);
// Limpiar de todas formas para no dejar basura
activePoll.delete(pollData.channelId);
}
}
export function getActivePoll(channelId) {
return activePoll.get(channelId) ?? null;
}
export async function restoreActivePolls(client) {
try {
const r = await db.db.execute({
sql : "SELECT key, value FROM zelin_memory WHERE category = 'polls' AND key LIKE 'poll.active.%'",
args: [],
});
for (const row of r.rows) {
try {
const pollData = typeof row.value === 'string' ? JSON.parse(row.value) : row.value;
const remaining = pollData.endsAt - Date.now();
// FIX: obtener canal con manejo de errores
const ch = client.channels.cache.get(pollData.channelId);
if (!ch) {
// Canal no disponible — limpiar de DB
await db.db.execute({ sql: 'DELETE FROM zelin_memory WHERE key = ?', args: [row.key] }).catch(() => {});
continue;
}
if (remaining <= 0) {
// Ya expiró
await closePoll(ch, pollData.messageId, pollData).catch(() => {});
continue;
}
activePoll.set(pollData.channelId, pollData);
setTimeout(() => closePoll(ch, pollData.messageId, pollData), remaining);
console.log(`[Polls] Encuesta restaurada en #${ch.name}, cierra en ${Math.round(remaining/60000)}min`);
} catch (e) {
console.warn('[Polls] Error restaurando encuesta:', e.message);
}
}
} catch (e) {
console.error('[Polls] Error en restoreActivePolls:', e.message);
}
}
export function isPollRequest(content) {
const lower = content.toLowerCase();
return (lower.includes('encuesta') || lower.includes('votación') || lower.includes('vota')) &&
(lower.includes('haz') || lower.includes('crea') || lower.includes('pon') || lower.includes('hacer'));
}