const { RoonExtension } = require('roon-kit'); const fs = require('fs'); const { exec } = require('child_process'); const lyricsScriptPath = "/home/teraflops/.config/waybar/scripts/get_lyrics.py"; function log(msg) { const timestamp = new Date().toISOString(); const fullMsg = `[${timestamp}] ${msg}`; console.log(fullMsg); fs.appendFileSync('/tmp/roon_debug.log', fullMsg + '\n'); } const extension = new RoonExtension({ description: { extension_id: 'roon-kit-now-playing', display_name: "Roon Kit Now Playing", display_version: "0.2.0", publisher: 'roon-kit', email: 'stevenic@microsoft.com', website: 'https://github.com/Stevenic/roon-kit' }, RoonApiBrowse: 'not_required', RoonApiImage: 'required', RoonApiTransport: 'required', subscribe_outputs: false, subscribe_zones: true, log_level: 'none' }); function cleanTitle(title) { return title.replace(/^[A-Za-z0-9]{1,3}\s+/, '').replace(/[$#@!%^&*(){}\[\]<>?/\\|`~]/g, '').trim(); } function getLyrics(artist, title, callback) { const cmd = `python3 ${lyricsScriptPath} "${artist}" "${title}"`; log(`🎤 Ejecutando: ${cmd}`); exec(cmd, (error, stdout, stderr) => { if (error) { log(`❌ Error en getLyrics: ${stderr}`); callback("Letras no disponibles."); } else { const lyrics = stdout.trim(); log(`✅ Letras obtenidas (${lyrics.length} chars)`); callback(lyrics || "Letras no encontradas."); } }); } extension.on("subscribe_zones", async (core, response, body) => { log("🔄 Evento subscribe_zones recibido"); log(JSON.stringify(body, null, 2)); const changedZones = body.zones_changed ?? []; const addedZones = body.zones_added ?? []; const removedZones = body.zones_removed ?? []; if (removedZones.length > 0) { log("🧹 Zona eliminada, limpiando /tmp/waybar_roon_info.json"); fs.writeFileSync('/tmp/waybar_roon_info.json', JSON.stringify({ text: '', tooltip: '' })); exec("/usr/bin/pkill -RTMIN+3 waybar"); } for (const zone of [...addedZones, ...changedZones]) { log(`🎧 Zona: ${zone.display_name}, estado: ${zone.state}`); if (zone.state === 'playing') { const track = cleanTitle(zone.now_playing?.one_line?.line1 || ''); const artist = zone.now_playing?.one_line?.line2 || ''; const album = zone.now_playing?.three_line?.line3 || ''; log(`🎵 Reproduciendo: ${track} - ${artist} (${album})`); getLyrics(artist, track, async (lyrics) => { const displayText = `${track} - ${artist}`; const tooltip = `🎵 ${track}\n👤 ${artist}\n💿 ${album}\n\n${lyrics}`; const data = { text: displayText, tooltip: tooltip }; log(`✍️ Escribiendo JSON para Waybar: ${JSON.stringify(data)}`); fs.writeFileSync('/tmp/waybar_roon_info.json', JSON.stringify(data)); exec("/usr/bin/pkill -RTMIN+3 waybar"); const image_key = zone.now_playing?.image_key; if (image_key) { try { const imageData = await core.services.RoonApiImage.get_image(image_key, { width: 300, height: 300 }); const coverPath = '/tmp/roon_album_cover.jpg'; fs.writeFileSync(coverPath, imageData.image); exec(`notify-send -i ${coverPath} "Now Playing" "${displayText}"`); log("✅ Notificación enviada con carátula"); } catch (error) { log(`⚠️ Error obteniendo imagen: ${error}`); exec(`notify-send "Now Playing" "${displayText}"`); } } else { exec(`notify-send "Now Playing" "${displayText}"`); } }); } } }); extension.start_discovery(); extension.set_status('Esperando conexión al Core de Roon...'); log("🔍 Buscando Core..."); (async () => { const core = await extension.get_core(); if (core) { log("✅ Core emparejado correctamente"); extension.set_status('Emparejado con el Core de Roon'); } else { log("❌ No se emparejó con ningún Core"); } })();