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