| 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()); |
|
|
| |
| const apiKey = process.env.GEMINI_API_KEY; |
| const ai = apiKey ? new GoogleGenAI({ |
| apiKey: apiKey, |
| httpOptions: { |
| headers: { |
| 'User-Agent': 'aistudio-build', |
| } |
| } |
| }) : null; |
|
|
| |
| app.get('/api/health', (req, res) => { |
| res.json({ status: 'ok', hasApiKey: !!apiKey }); |
| }); |
|
|
| |
| 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) { |
| |
| const mockResponses: Record<string, any[]> = { |
| '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.' }); |
| } |
| }); |
|
|
| |
| 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!)` |
| }); |
| } |
|
|
| |
| 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.' }); |
| } |
| }); |
|
|
| |
| 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, ` |
| <!doctype html> |
| <html lang="es"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Glizh - El Bot de Música de Discord Definitivo</title> |
| <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap"> |
| </head> |
| <body style="background-color: #09090b; margin: 0; padding: 0;"> |
| <div id="root"></div> |
| <script type="module" src="/src/main.tsx"></script> |
| </body> |
| </html> |
| `); |
| 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(); |
|
|