``` require('dotenv').config(); const cloudscraper = require('cloudscraper'); const { KickClient } = require('@botk4cp3r/kick.js'); const initDatabase = require('./database'); const fs = require('fs'); const path = require('path'); // Bloqueo para evitar ejecución múltiple const lockFile = path.join(__dirname, 'bot.lock'); try { if (fs.existsSync(lockFile)) { const lockContent = fs.readFileSync(lockFile, 'utf8'); console.log(`Script ya está ejecutándose (PID: ${lockContent}), saliendo...`); process.exit(0); } fs.writeFileSync(lockFile, process.pid.toString()); } catch (error) { console.error('Error al manejar lock file:', error.message); process.exit(1); } // Limpiar lock file al salir process.on('exit', () => { try { if (fs.existsSync(lockFile)) { fs.unlinkSync(lockFile); console.log('Lock file eliminado'); } } catch (error) { console.error('Error al eliminar lock file:', error.message); } }); process.on('SIGINT', () => process.exit(0)); process.on('SIGTERM', () => process.exit(0)); let giveawayActive = false; let participants = []; let endTime = null; (async () => { // Conectar a la base de datos let db; try { db = await initDatabase(); console.log('Conectado a la base de datos'); } catch (error) { console.error('Error al conectar a la base de datos:', error.message); return; } // Verificar variables de entorno const requiredEnvVars = ['KICK_CHANNEL', 'CLIENT_ID', 'CLIENT_SECRET', 'BROADCASTER_USER_ID']; for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { console.error(`Error: ${envVar} no está definido en .env`); return; } } // Obtener access_token con cloudscraper let accessToken; try { const response = await cloudscraper({ method: 'POST', url: 'https://id.kick.com/oauth/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET }).toString(), cloudflareMaxTimeout: 30000, challengesToSolve: 3 }); const data = JSON.parse(response); if (!data.access_token) { throw new Error('No se recibió access_token'); } accessToken = data.access_token; console.log('Access token obtenido correctamente'); } catch (error) { console.error('Error al obtener access_token:', error.message); return; } // Obtener broadcaster_user_id let broadcasterUserId = process.env.BROADCASTER_USER_ID; const broadcasterUserIdNum = parseInt(broadcasterUserId, 10); if (isNaN(broadcasterUserIdNum) || broadcasterUserIdNum <= 0) { console.error('Error: BROADCASTER_USER_ID debe ser un número válido'); return; } console.log('Usando BROADCASTER_USER_ID de .env:', broadcasterUserIdNum); // Crear instancia del cliente const clientOptions = { token: accessToken, channel: process.env.KICK_CHANNEL, broadcaster_user_id: broadcasterUserIdNum, broadcaster_id: broadcasterUserId // Prueba ambos formatos }; console.log('Inicializando KickClient con:', { channel: clientOptions.channel, broadcaster_user_id: clientOptions.broadcaster_user_id, broadcaster_id: clientOptions.broadcaster_id }); let client; try { client = new KickClient(clientOptions); console.log('KickClient creado correctamente'); } catch (error) { console.error('Error al crear KickClient:', error.message); console.error('Parámetros enviados:', clientOptions); return; } // Intentar inicializar el cliente para eventos de chat try { await client.subscribeToEvents(['chat.message.sent']); console.log('Conectado al chat de Kick'); } catch (error) { console.error('Error al inicializar el cliente:', error.message); console.error('Parámetros enviados a KickClient:', clientOptions); return; } // Escuchar mensajes del chat client.on('chatMessage', async (message) => { try { const sender = message.sender.username; const content = message.content; const args = content.split(' '); if (content.startsWith('!giveaway')) { const subcommand = args[1]?.toLowerCase(); if (subcommand === 'start' && args[2]) { if (!giveawayActive) { const minutes = parseInt(args[2]); if (isNaN(minutes) || minutes <= 0) { await client.sendChatMessage({ content: 'Por favor, indica un número válido de minutos.' }); return; } giveawayActive = true; participants = []; endTime = new Date(Date.now() + minutes * 60 * 1000); await db.query('INSERT INTO giveaways (active, end_time, participants) VALUES (?, ?, ?)', [ true, endTime, JSON.stringify(participants) ]); await client.sendChatMessage({ content: `¡Sorteo iniciado! Dura ${minutes} minutos. Usa !giveaway join para participar.` }); setTimeout(async () => { if (giveawayActive) { await endGiveaway(db, client); } }, minutes * 60 * 1000); } else { await client.sendChatMessage({ content: 'Ya hay un sorteo activo.' }); } } if (subcommand === 'join') { if (giveawayActive) { if (!participants.includes(sender)) { participants.push(sender); await db.query('UPDATE giveaways SET participants = ? WHERE active = TRUE', [ JSON.stringify(participants) ]); await client.sendChatMessage({ content: `${sender} ha entrado al sorteo.` }); } else { await client.sendChatMessage({ content: `${sender}, ya estás en el sorteo.` }); } } else { await client.sendChatMessage({ content: 'No hay un sorteo activo.' }); } } if (subcommand === 'participants') { if (giveawayActive) { await client.sendChatMessage({ content: `Participantes: ${participants.join(', ') || 'Ninguno'}` }); } else { await client.sendChatMessage({ content: 'No hay un sorteo activo.' }); } } if (subcommand === 'end') { if (giveawayActive) { await endGiveaway(db, client); } else { await client.sendChatMessage({ content: 'No hay un sorteo activo.' }); } } if (subcommand === 'enable') { giveawayActive = true; await db.query('UPDATE giveaways SET active = TRUE WHERE id = (SELECT MAX(id) FROM giveaways)'); await client.sendChatMessage({ content: 'Sorteos habilitados.' }); } if (subcommand === 'disable') { giveawayActive = false; await db.query('UPDATE giveaways SET active = FALSE WHERE id = (SELECT MAX(id) FROM giveaways)'); await client.sendChatMessage({ content: 'Sorteos deshabilitados.' }); } } } catch (error) { console.error('Error al procesar mensaje:', error.message); } }); // Manejar errores del cliente client.on('error', (error) => { console.error('Error en el cliente:', error.message); }); // Manejar desconexión client.on('close', async () => { console.log('Desconectado del chat, intentando reconectar...'); try { const tokenResponse = await cloudscraper({ method: 'POST', url: 'https://id.kick.com/oauth/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET }).toString(), cloudflareMaxTimeout: 30000, challengesToSolve: 3 }); const tokenData = JSON.parse(tokenResponse); if (!tokenData.access_token) { throw new Error('No se recibió access_token'); } accessToken = tokenData.access_token; client.token = accessToken; await client.subscribeToEvents(['chat.message.sent']); console.log('Reconectado al chat de Kick'); } catch (error) { console.error('Error al reconectar:', error.message); } }); async function endGiveaway(db, client) { if (participants.length > 0) { const winner = participants[Math.floor(Math.random() * participants.length)]; await client.sendChatMessage({ content: `¡El sorteo ha terminado! El ganador es: ${winner}` }); } else { await client.sendChatMessage({ content: 'El sorteo ha terminado, pero no hubo participantes.' }); } giveawayActive = false; participants = []; endTime = null; await db.query('UPDATE giveaways SET active = FALSE, participants = ? WHERE active = TRUE', [ JSON.stringify([]) ]); } })(); ``` === KICK_CHANNEL=benzitczo CLIENT_ID= CLIENT_SECRET= ACCESS_TOKEN= BROADCASTER_USER_ID= ``` Oh sorry, that's bot hosting, this Pterodacty, for if I can, right? and how explain where is acces tokens?