Skip to main content
Glama
auth.js6.19 kB
#!/usr/bin/env node /** * Script de autenticación OAuth con Spotify * Ejecutar con: npm run auth */ import crypto from 'node:crypto'; import http from 'node:http'; import { URL } from 'node:url'; import open from 'open'; import { cargarConfiguracion, guardarConfiguracion } from './core/configuracion.js'; /** Genera una cadena aleatoria para el state de OAuth */ function generarCadenaAleatoria(longitud) { const array = new Uint8Array(longitud); crypto.getRandomValues(array); const caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; return Array.from(array).map((b) => caracteres.charAt(b % 62)).join(''); } /** Codifica en base64 */ function codificarBase64(str) { return Buffer.from(str).toString('base64'); } /** Intercambia el código de autorización por tokens */ async function intercambiarCodigoPorToken(codigo) { const config = cargarConfiguracion(); const urlToken = 'https://accounts.spotify.com/api/token'; const authHeader = `Basic ${codificarBase64(`${config.clientId}:${config.clientSecret}`)}`; const params = new URLSearchParams(); params.append('grant_type', 'authorization_code'); params.append('code', codigo); params.append('redirect_uri', config.redirectUri); const respuesta = await fetch(urlToken, { method: 'POST', headers: { Authorization: authHeader, 'Content-Type': 'application/x-www-form-urlencoded', }, body: params, }); if (!respuesta.ok) { const error = await respuesta.text(); throw new Error(`Error al obtener token: ${error}`); } const datos = await respuesta.json(); return { access_token: datos.access_token, refresh_token: datos.refresh_token }; } /** Inicia el flujo de autenticación OAuth */ async function autenticarSpotify() { const config = cargarConfiguracion(); const uriRedireccion = new URL(config.redirectUri); if (uriRedireccion.hostname !== 'localhost' && uriRedireccion.hostname !== '127.0.0.1') { console.error('Error: La URI de redirección debe usar localhost'); process.exit(1); } const puerto = uriRedireccion.port || '80'; const rutaCallback = uriRedireccion.pathname || '/callback'; const state = generarCadenaAleatoria(16); const permisos = [ 'user-read-private', 'user-read-email', 'user-read-playback-state', 'user-modify-playback-state', 'user-read-currently-playing', 'playlist-read-private', 'playlist-modify-private', 'playlist-modify-public', 'user-library-read', 'user-library-modify', 'user-read-recently-played', 'user-top-read', ]; const paramsAuth = new URLSearchParams({ client_id: config.clientId, response_type: 'code', redirect_uri: config.redirectUri, scope: permisos.join(' '), state, show_dialog: 'true', }); const urlAutorizacion = `https://accounts.spotify.com/authorize?${paramsAuth.toString()}`; const promesaAuth = new Promise((resolve, reject) => { const servidor = http.createServer(async (req, res) => { if (!req.url) return res.end('Sin URL'); const urlReq = new URL(req.url, `http://localhost:${puerto}`); if (urlReq.pathname !== rutaCallback) { res.writeHead(404); return res.end(); } const codigo = urlReq.searchParams.get('code'); const stateRetornado = urlReq.searchParams.get('state'); const error = urlReq.searchParams.get('error'); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); if (error) { console.error(`Error de autorización: ${error}`); res.end('<html><body><h1>❌ Error</h1><p>Cierra esta ventana e intenta de nuevo.</p></body></html>'); servidor.close(); return reject(new Error(error)); } if (stateRetornado !== state) { console.error('Error: State no coincide'); res.end('<html><body><h1>❌ Error de verificación</h1></body></html>'); servidor.close(); return reject(new Error('State mismatch')); } if (!codigo) { console.error('Sin código de autorización'); res.end('<html><body><h1>❌ Sin código</h1></body></html>'); servidor.close(); return reject(new Error('No code')); } try { const tokens = await intercambiarCodigoPorToken(codigo); config.accessToken = tokens.access_token; config.refreshToken = tokens.refresh_token; guardarConfiguracion(config); res.end('<html><body><h1>✅ ¡Autenticación exitosa!</h1><p>Puedes cerrar esta ventana.</p></body></html>'); console.log('✅ Autenticación completada. Token guardado.'); servidor.close(); resolve(); } catch (err) { console.error('Error al intercambiar token:', err); res.end('<html><body><h1>❌ Error</h1><p>No se pudo obtener el token.</p></body></html>'); servidor.close(); reject(err); } }); servidor.listen(Number.parseInt(puerto), '127.0.0.1', () => { console.log(`🎧 Escuchando en puerto ${puerto}...`); console.log('🌐 Abriendo navegador...'); open(urlAutorizacion).catch(() => { console.log('No se pudo abrir el navegador. Visita:'); console.log(urlAutorizacion); }); }); servidor.on('error', (err) => { console.error(`Error del servidor: ${err.message}`); reject(err); }); }); await promesaAuth; } console.log('🎵 Iniciando autenticación con Spotify...'); autenticarSpotify() .then(() => { console.log('✅ ¡Listo!'); process.exit(0); }) .catch((error) => { console.error('❌ Error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Yonsn76/spotify-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server