/** * 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')); }