File size: 8,335 Bytes
0ba4f29
 
 
 
 
 
 
 
 
b64dfae
 
 
 
 
 
0ba4f29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b64dfae
0ba4f29
b64dfae
0ba4f29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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<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.' });
    }
  });

  // 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, `
          <!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();