gfds / server.ts
Zestysson
Fix: resolve CJS path fileURLToPath runtime error
b64dfae
Raw
History Blame Contribute Delete
8.34 kB
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();