import express from 'express'; import { createServer as createViteServer } from 'vite'; import { GoogleGenAI } from '@google/genai'; import dotenv from 'dotenv'; import path from 'path'; import { fileURLToPath } from 'url'; dotenv.config(); const resolvedFilename = typeof import.meta !== 'undefined' && import.meta.url ? fileURLToPath(import.meta.url) : __filename; const resolvedDirname = typeof import.meta !== 'undefined' && import.meta.url ? path.dirname(resolvedFilename) : __dirname; async function startServer() { const app = express(); app.use(express.json()); // Initialize Gemini AI SDK const apiKey = process.env.GEMINI_API_KEY; const ai = apiKey ? new GoogleGenAI({ apiKey: apiKey, httpOptions: { headers: { 'User-Agent': 'aistudio-build', } } }) : null; // Health check app.get('/api/health', (req, res) => { res.json({ status: 'ok', hasApiKey: !!apiKey }); }); // Suggest or search tracks using Gemini 3.5 Flash app.post('/api/music/suggest', async (req, res) => { try { const { query } = req.body; if (!query) { return res.status(400).json({ error: 'Query is required.' }); } if (!ai) { // Fallback mock songs if no API Key const mockResponses: Record = { 'synthwave': [ { title: 'Neon Skyline', artist: 'Laserhawk', duration: '3:45', genre: 'Synthwave' }, { title: 'Miami Nights', artist: 'Outrun Kid', duration: '4:12', genre: 'Synthwave' }, { title: 'Midnight City', artist: 'Retro Future', duration: '3:50', genre: 'Synthwave' } ], 'lofi': [ { title: 'Rainy Cafe Cozy', artist: 'Lofi Butter', duration: '2:30', genre: 'Lofi Chill' }, { title: 'Midnight Coffee', artist: 'Study Beats', duration: '3:05', genre: 'Lofi Chill' }, { title: 'Warm Tea Lounge', artist: 'Snoozy Cat', duration: '2:45', genre: 'Lofi Chill' } ] }; const key = query.toLowerCase(); let matched = mockResponses.lofi; if (key.includes('synth') || key.includes('retro') || key.includes('wave') || key.includes('rock')) { matched = mockResponses.synthwave; } else { matched = [ { title: `${query} Club Mix`, artist: 'DJ Glizh', duration: '04:20', genre: 'Dance' }, { title: `Acoustic ${query}`, artist: 'The Strings', duration: '03:15', genre: 'Acoustic' }, { title: `${query} (Chill Version)`, artist: 'Echo Sound', duration: '03:40', genre: 'Chillout' } ]; } return res.json({ tracks: matched }); } const prompt = `Based on the search query: "${query}", suggest exactly 4 highly realistic/creative music tracks. Please yield the response as a JSON array of objects with the fields "title", "artist", "duration" (format like MM:SS), and "genre". Do not wrap in markdown or any code blocks, return only raw JSON. Ensure it is valid JSON.`; const response = await ai.models.generateContent({ model: 'gemini-3.5-flash', contents: prompt, config: { responseMimeType: 'application/json', } }); const responseText = response.text || '[]'; const parsed = JSON.parse(responseText.trim()); res.json({ tracks: parsed }); } catch (error: any) { console.error('Gemini music search error:', error); res.status(500).json({ error: error.message || 'Failed to suggest tracks.' }); } }); // Chat with the Symphony AI assistant/Music bot inside the console app.post('/api/music/chat', async (req, res) => { try { const { message, history = [] } = req.body; if (!message) { return res.status(400).json({ error: 'Message is required.' }); } if (!ai) { return res.json({ text: `🎸 **Glizh Bot** está listo para sonar! Intenta ejecutar comandos en nuestro Simulador de Terminal de Discord como \`/play provenza\` o \`/queue\`. (¡Configura un GEMINI_API_KEY para desbloquear respuestas inteligentes y listas generadas por IA!)` }); } // Contexto enriquecido para el chatbot asistente Glizh const prompt = ` Eres "Glizh", el Bot de Música de Discord definitivo y su asistente de IA integrado. Un usuario te ha enviado un mensaje en el simulador: "${message}" Detalles clave del bot Glizh: 1. COMANDOS: - /play [búsqueda o enlace]: reproduce cualquier canción o vibe (reggaeton, bachata, trap, pop, lofi) usando nuestro reproductor sintetizado en vivo. - /skip: salta a la siguiente canción en la cola. - /queue: muestra la lista de canciones en espera y la actual. - /volume [0-150]: ajusta el nivel del volumen sintetizado. - /bassboost [mode]: cambia el ecualizador de bajos (Off, Low, Heavy, Extreme). - /lyrics: busca las letras de la canción actual. - /filter [ambient | lofi | nightcore | vaporwave | normal]: aplica filtros de audio premium en tiempo real. 2. RESOLUCIÓN DE PROBLEMAS TÉCNICOS (ERRORES DE YOUTUBE / LAVALINK): - Si preguntan sobre caídas, logs, o errores como "SocketTimeoutException" o "Read timed out" conectando a YouTube: * Explica que YouTube bloquea/limita IPs de hosting públicos (como Render, Hugging Face o AWS). * Recomienda configurar rotación de bloques IPv6 en "application.yml" de Lavalink en "ratelimit.ipBlocks". * Sugiere usar la última versión del plugin "youtube-plugin" (1.18.1+) configurando tokens OAuth2/refreshToken. * Recomienda usar proxies residenciales HTTP o SOCKS5 en la configuración. COMPORTAMIENTO Y TONO: - Responde en español fluido y conversacional de forma predeterminada (o inglés si el mensaje del usuario está en inglés). - Mantén la respuesta súper concisa (máximo 2 a 4 oraciones) para que encaje perfectamente en la interfaz de chat. - Usa negrita, listas o viñetas cortas para que sea fácil de leer de un vistazo. - Añade emojis llamativos y tecnológicos (✨, 🔊, ⚡, 👑, 👾, 🎸, 🎶). - ¡Demuestra mucho entusiasmo y un tono premium y festivo! `; const response = await ai.models.generateContent({ model: 'gemini-3.5-flash', contents: prompt, }); res.json({ text: response.text }); } catch (error: any) { console.error('Gemini music chat error:', error); res.status(500).json({ error: error.message || 'Failed to chat.' }); } }); // Serve static assets or use Vite dev server const isProd = process.env.NODE_ENV === 'production'; if (isProd) { app.use(express.static(path.join(resolvedDirname, 'dist'))); app.get('*', (req, res) => { res.sendFile(path.join(resolvedDirname, 'dist', 'index.html')); }); } else { const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'custom', }); app.use(vite.middlewares); app.get('*', async (req, res, next) => { const url = req.originalUrl; try { let template = await vite.transformIndexHtml(url, ` Glizh - El Bot de Música de Discord Definitivo
`); res.status(200).set({ 'Content-Type': 'text/html' }).end(template); } catch (e) { vite.ssrFixStacktrace(e as Error); next(e); } }); } const port = parseInt(process.env.PORT || '7860'); app.listen(port, '0.0.0.0', () => { console.log(`Server running at http://0.0.0.0:${port}`); }); } startServer();