[APP][Pro] Dashboard Studio - A completely free-form dashboard designer

Execute the following Homey script after change of artist or track and save the song text in a Homey flow variable:

// 1. Configuratie & Hardware
const SONOS_ID = 'ce48fa04-xxxx-xxxx-xxxxxxxxxxxxxxxxx';
const LRCLIB_ENDPOINT = 'https://lrclib.net/api/get';
const DEFAULT_TEXT = "Geen songtext gevonden"; // Centraal ingesteld

// 2. Data ophalen
const sonos = await Homey.devices.getDevice({ id: SONOS_ID });
const { 
    speaker_artist, 
    speaker_track, 
    speaker_playing, 
    speaker_duration 
} = sonos.capabilitiesObj;

const artist = speaker_artist?.value;
const track = speaker_track?.value;
const isPlaying = speaker_playing?.value;
const duration = speaker_duration?.value;

// 3. Validatie
if (!artist || !track || !isPlaying) return false;

// 4. Voorbereiding resultaat
let resultaat = DEFAULT_TEXT;

// 5. API Request
const query = new URLSearchParams({
    artist_name: artist,
    track_name: track,
    duration: duration
});

try {
    const response = await fetch(LRCLIB_ENDPOINT + '?' + query.toString());

    if (response.ok) {
        const data = await response.json();
        const syncedLyrics = data.syncedLyrics;

        // 6. Verwerking van Lyrics (indien aanwezig)
        if (syncedLyrics && syncedLyrics.trim() !== "") {
            const verwerkteTekst = syncedLyrics
                .split('\n')
                .filter(line => line.trim() !== "")
                .map(line => {
                    const timestamp = line.substring(1, 6); // mm:ss
                    let text = line.substring(10).trim(); 
                    
                    if (!text) {
                        text = "[instrumentaal]";
                    }
                    
                    return timestamp + " - " + text;
                })
                .join('\n');
            
            if (verwerkteTekst.length > 0) {
                resultaat = verwerkteTekst;
            }
        }
    }
} catch (error) {
    console.log("Fout bij ophalen lyrics: " + error.message);
}

// 7. Opslaan in Homey Logic variabelen
const allVars = await Homey.logic.getVariables();
const myVar = Object.values(allVars).find(v => v.name === 'DSSongtext');
const myVarNL = Object.values(allVars).find(v => v.name === 'DSSongtextNL');

// Altijd DSSongtextNL leegmaken bij een nieuwe actie
if (myVarNL) {
    await Homey.logic.updateVariable({ id: myVarNL.id, variable: { value: "" } });
}

if (myVar) {
    await Homey.logic.updateVariable({ id: myVar.id, variable: { value: resultaat } });
    console.log("Status opgeslagen in DSSongtext. DSSongtextNL is leeggemaakt.");
    return true;
} else {
    console.log("Variabele 'DSSongtext' niet gevonden in Homey Logic.");
    return false;
}

Match the song text with the actual play time of the current song. There is even a song text translation option in the code, which needs a separate script (not included here).

// 1. Configuratie & Performance Caching
const SONOS_ID = 'ce48fa04-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const CORRECTIE_ID = '88ba3497-xxxx-xxxx-xxxx-xxxxxxxxxxxx'';
const OFFSET_MS = 1000;
const MELDING = "Geen songtext gevonden";

global.myDevices = global.myDevices || {};
if (!global.myDevices.sonos) {
    global.myDevices.sonos = await Homey.devices.getDevice({ id: SONOS_ID });
}
if (!global.myDevices.correction) {
    global.myDevices.correction = await Homey.devices.getDevice({ id: CORRECTIE_ID });
}

const sonos = global.myDevices.sonos;
const correctionDevice = global.myDevices.correction;

// 2. Haal de Sonos data op
let posObj = sonos.capabilitiesObj.speaker_position || { value: 0, lastUpdated: new Date().toISOString() };
const durObj = sonos.capabilitiesObj.speaker_duration;

let manualCorrectionSeconds = 0;
if (correctionDevice?.capabilitiesObj.target_temperature) {
    manualCorrectionSeconds = correctionDevice.capabilitiesObj.target_temperature.value || 0;
}

// 3. De EXACTE berekening
const lastUpdatedUnix = new Date(posObj.lastUpdated).getTime(); 
const driftSeconds = (Date.now() - lastUpdatedUnix) / 1000;
const exactTimeSeconds = posObj.value + driftSeconds + (OFFSET_MS / 1000) + manualCorrectionSeconds;

// 4. Helper functie
const formatTime = (totalSeconds) => {
    if (isNaN(totalSeconds) || totalSeconds < 0) return "0:00";
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = Math.floor(totalSeconds % 60);
    return minutes + ":" + seconds.toString().padStart(2, '0');
};

// 5. Haal songtekst variabelen op
const allVars = await Homey.logic.getVariables();
const vertaalNaarNL = Object.values(allVars).find(v => v.name === "DSVertaalNaarNL")?.value || false;

const targetVarName = vertaalNaarNL ? "DSSongtextNL" : "DSSongtext";
const songtextVar = Object.values(allVars).find(v => v.name === targetVarName);

if (!songtextVar || !songtextVar.value) {
    return JSON.stringify({ error: "Variabele '" + targetVarName + "' is leeg" });
}

// 6. Bepaal de tekstinhoud (Efficiënte toewijzing)
let lyrics = { vorige_regel: "", huidige_regel: "[instrumentaal]", volgende_regel: "" };

if (songtextVar.value.trim() === MELDING) {
    // Specifieke afhandeling voor "Geen songtext gevonden"
    lyrics = { vorige_regel: "", huidige_regel: MELDING, volgende_regel: "" };
} else {
    // Verwerk tekst naar array en zoek huidige index
    const lines = songtextVar.value.split('\n')
        .filter(l => l.includes(' - '))
        .map(line => {
            const [timeStr, ...textParts] = line.split(' - ');
            const [min, sec] = timeStr.split(':').map(Number);
            return { totalSeconds: (min * 60) + sec, text: textParts.join(' - ') };
        });

    let currentIndex = -1;
    for (let i = 0; i < lines.length; i++) {
        if (exactTimeSeconds >= lines[i].totalSeconds) currentIndex = i;
        else break;
    }

    if (currentIndex !== -1) {
        lyrics = {
            vorige_regel: lines[currentIndex - 1]?.text || "",
            huidige_regel: lines[currentIndex].text,
            volgende_regel: lines[currentIndex + 1]?.text || ""
        };
    } else if (lines.length > 0) {
        lyrics.volgende_regel = lines[0].text;
    }
}

// 7. Eén centrale return voor alle data
return JSON.stringify({
    ...lyrics,
    actuele_speeltijd: formatTime(exactTimeSeconds),
    actuele_speeltijd_sec: Math.round(exactTimeSeconds),
    totale_duur: formatTime(durObj?.value || 0)
});

The Homey script “card” is the script above.