SkySongs Slide-out Dashboard

Slide-out skuffe til SkySongs med kontrolpanel. Hele indholdet vises kun når skuffen er åben. Lukket = kun håndtag synligt.

// ==UserScript==
// @name         SkySongs Slide-out Dashboard
// @namespace    https://skyskraber.dk
// @version      3.3
// @description  Slide-out skuffe til SkySongs med kontrolpanel. Hele indholdet vises kun når skuffen er åben. Lukket = kun håndtag synligt.
// @author       Kevin
// @license      GNU General Public License v3.0
// @match        https://www.skyskraber.dk/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // =========================
    // INIT SONGS FRA localStorage
    // =========================
    window.songs = JSON.parse(localStorage.getItem("skySongs") || "{}");

    function saveSongs() {
        localStorage.setItem("skySongs", JSON.stringify(window.songs));
    }

    function addSong(title, rawLyrics) {
        const cleaned = rawLyrics.replace(/\[.*?\]/g, '');
        const lines = cleaned.split('\n').map(l => l.trim()).filter(l => l.length > 0);
        window.songs[title.toLowerCase()] = lines;
        saveSongs();
        console.log(`🎵 Sang tilføjet: "${title}" (${lines.length} linjer)`);
    }

    window.addNewSongPrompt = function() {
        const title = prompt("Indtast sangtitel:");
        if (!title) return alert("Ingen titel indtastet!");
        const lyrics = prompt("Indsæt sangteksten:");
        if (!lyrics) return alert("Ingen lyrics indsat!");
        addSong(title, lyrics);
        alert(`Sangen "${title}" er nu klar til brug med !syng ${title}`);
    };

    window.deleteSongFromList = function() {
        const titles = Object.keys(window.songs);
        if (titles.length === 0) return alert("Ingen sange gemt!");
        const selection = prompt("Gemte sange:\n" + titles.map((t, i) => `${i+1}. ${t}`).join("\n") + "\n\nIndtast nummeret på sangen der skal slettes:");
        const index = parseInt(selection) - 1;
        if (isNaN(index) || index < 0 || index >= titles.length) return alert("Ugyldigt valg!");
        const title = titles[index];
        if (confirm(`Er du sikker på, at du vil slette "${title}"?`)) {
            delete window.songs[title];
            saveSongs();
            alert(`✅ Sangen "${title}" er slettet!`);
        }
    };

    function splitMessageWithHyphen(line, maxLength = 100) {
        const result = [];
        let words = line.split(/(\s+)/);
        let current = "";

        for (let word of words) {
            if ((current + word).length > maxLength) {
                if (word.trim().length > maxLength) {
                    let remainingWord = word;
                    while (remainingWord.length > 0) {
                        let spaceLeft = maxLength - current.length - 1;
                        if (spaceLeft <= 0) {
                            result.push(current + "-");
                            current = "";
                            spaceLeft = maxLength - 1;
                        }
                        let part = remainingWord.slice(0, spaceLeft);
                        remainingWord = remainingWord.slice(spaceLeft);
                        current += part;
                        if (remainingWord.length > 0) {
                            result.push(current + "-");
                            current = "";
                        }
                    }
                } else {
                    if (current.trim().length > 0) result.push(current.trim());
                    current = word;
                }
            } else {
                current += word;
            }
        }

        if (current.trim().length > 0) result.push(current.trim());
        return result;
    }

    function getDelayForChunk(chunk) {
        const baseDelay = 600;
        const extraPerChar = 15;
        let delay = baseDelay + (chunk.length * extraPerChar);
        if (chunk.includes("-")) delay += 300;
        delay += parseInt(localStorage.getItem("skySongsLinePause") || 3000);
        return delay;
    }

    window.sangSkyState = { playing: false, paused: false, stopped: false };

    // =========================
    // SLIDE-OUT DASHBOARD
    // =========================
    const drawer = document.createElement("div");
    drawer.style.position = "fixed";
    drawer.style.left = "-460px"; // starter skjult
    drawer.style.top = "603px";   // Y-position
    drawer.style.width = "450px";
    drawer.style.background = "#111";
    drawer.style.color = "#fff";
    drawer.style.padding = "10px";
    drawer.style.borderRadius = "0 8px 8px 0";
    drawer.style.zIndex = "9999";
    drawer.style.fontFamily = "Arial, sans-serif";
    drawer.style.transition = "left 0.3s ease";

    const drawerHandle = document.createElement("div");
    drawerHandle.innerText = "🎶";
    drawerHandle.style.position = "absolute";
    drawerHandle.style.right = "-30px";
    drawerHandle.style.top = "0";
    drawerHandle.style.width = "30px";
    drawerHandle.style.height = "40px";
    drawerHandle.style.background = "#3b82f6";
    drawerHandle.style.color = "#fff";
    drawerHandle.style.display = "flex";
    drawerHandle.style.alignItems = "center";
    drawerHandle.style.justifyContent = "center";
    drawerHandle.style.cursor = "pointer";
    drawerHandle.style.borderRadius = "0 6px 6px 0";
    drawerHandle.style.fontSize = "18px";

    let drawerOpen = localStorage.getItem("skySongsDrawerOpen") === "true";
    drawer.style.left = drawerOpen ? "0px" : "-460px";

    const contentWrap = document.createElement("div");
    contentWrap.style.display = drawerOpen ? "flex" : "none";
    contentWrap.style.flexDirection = "column";
    contentWrap.style.alignItems = "center";
    contentWrap.style.gap = "8px";

    drawerHandle.addEventListener("click", () => {
        drawerOpen = !drawerOpen;
        localStorage.setItem("skySongsDrawerOpen", drawerOpen);
        drawer.style.left = drawerOpen ? "0px" : "-460px";
        contentWrap.style.display = drawerOpen ? "flex" : "none";
    });

    drawer.appendChild(drawerHandle);

    // =========================
    // DASHBOARD INDHOLD
    // =========================
    const title = document.createElement("h3");
    title.innerText = "SkySong af brugeren Kevin";
    title.style.margin = "0";
    title.style.padding = "4px";
    title.style.fontSize = "16px";
    title.style.fontWeight = "bold";
    title.style.textAlign = "center";
    contentWrap.appendChild(title);

    const buttonContainer = document.createElement("div");
    buttonContainer.style.display = "flex";
    buttonContainer.style.gap = "6px";

    function createBtn(label, color, onClick) {
        const btn = document.createElement("button");
        btn.innerHTML = label;
        btn.style.padding = "6px 12px";
        btn.style.background = color;
        btn.style.color = "#fff";
        btn.style.border = "none";
        btn.style.borderRadius = "6px";
        btn.style.cursor = "pointer";
        btn.style.fontSize = "14px";
        btn.addEventListener("click", onClick);
        return btn;
    }

    buttonContainer.appendChild(createBtn("📃", "#3b82f6", () => window.showSongList()));
    buttonContainer.appendChild(createBtn("➕", "#22c55e", () => window.addNewSongPrompt()));
    buttonContainer.appendChild(createBtn("🗑", "#ef4444", () => window.deleteSongFromList()));
    buttonContainer.appendChild(createBtn("⏸", "#facc15", () => window.sangSkyState.paused = true));
    buttonContainer.appendChild(createBtn("▶", "#16a34a", () => window.sangSkyState.paused = false));
    buttonContainer.appendChild(createBtn("⏹", "#dc2626", () => window.sangSkyState.stopped = true));

    contentWrap.appendChild(buttonContainer);

    const sliderWrap = document.createElement("div");
    sliderWrap.style.display = "flex";
    sliderWrap.style.alignItems = "center";
    sliderWrap.style.gap = "6px";
    sliderWrap.style.fontSize = "12px";

    const slider = document.createElement("input");
    slider.type = "range";
    slider.min = "500";
    slider.max = "5000";
    slider.step = "100";
    slider.value = localStorage.getItem("skySongsLinePause") || 3000;
    slider.style.width = "200px";

    const sliderVal = document.createElement("span");
    sliderVal.textContent = slider.value + " ms";

    slider.addEventListener("input", () => {
        sliderVal.textContent = slider.value + " ms";
        localStorage.setItem("skySongsLinePause", slider.value);
    });

    sliderWrap.appendChild(document.createTextNode("Pause:"));
    sliderWrap.appendChild(slider);
    sliderWrap.appendChild(sliderVal);

    contentWrap.appendChild(sliderWrap);

    const progressOuter = document.createElement("div");
    progressOuter.style.width = "100%";
    progressOuter.style.height = "12px";
    progressOuter.style.background = "#333";
    progressOuter.style.borderRadius = "6px";
    progressOuter.style.overflow = "hidden";

    const progressBar = document.createElement("div");
    progressBar.style.width = "0%";
    progressBar.style.height = "100%";
    progressBar.style.background = "#3b82f6";
    progressBar.style.textAlign = "center";
    progressBar.style.fontSize = "10px";
    progressBar.style.lineHeight = "12px";
    progressBar.style.color = "#fff";
    progressBar.style.transition = "width 0.2s";

    progressOuter.appendChild(progressBar);
    contentWrap.appendChild(progressOuter);

    drawer.appendChild(contentWrap);
    document.body.appendChild(drawer);

    // =========================
    // VIS SANGE
    // =========================
    window.showSongList = function() {
        const titles = Object.keys(window.songs);
        if (titles.length === 0) return alert("Ingen sange gemt!");
        alert("🎵 Gemte sange:\n\n" + titles.map((t, i) => `${i+1}. ${t}`).join("\n"));
    };

    // =========================
    // AFSPILNING
    // =========================
    async function playSong(ws, lyrics) {
        window.sangSkyState.stopped = false;
        window.sangSkyState.paused = false;
        window.sangSkyState.playing = true;

        const allChunks = lyrics.flatMap(line => splitMessageWithHyphen(line, 100));
        const totalChunks = allChunks.length;
        let chunkIndex = 0;

        for (let chunk of allChunks) {
            if (window.sangSkyState.stopped) {
                window.sangSkyState.playing = false;
                progressBar.style.width = "0%";
                progressBar.innerText = "";
                return;
            }

            while (window.sangSkyState.paused) {
                await new Promise(r => setTimeout(r, 200));
            }

            ws.send(JSON.stringify({ type: "chat", data: { message: chunk } }));

            chunkIndex++;
            const progressPercent = Math.round((chunkIndex / totalChunks) * 100);
            progressBar.style.width = progressPercent + "%";
            progressBar.innerText = progressPercent + "%";

            await new Promise(r => setTimeout(r, getDelayForChunk(chunk)));
        }

        window.sangSkyState.playing = false;
        progressBar.style.width = "0%";
        progressBar.innerText = "";
    }

    // =========================
    // WEBSOCKET OVERRIDE
    // =========================
    let WebSocketOrig = window.WebSocket;
    window.WebSocket = new Proxy(WebSocketOrig, {
        construct(target, args) {
            const ws = new target(...args);
            const originalSend = ws.send;

            ws.send = function(data) {
                try {
                    const parsed = JSON.parse(data);
                    if (parsed.type === "chat" && parsed.data?.message) {
                        const msg = parsed.data.message.trim();
                        if (msg.startsWith("!syng ")) {
                            const songTitle = msg.replace("!syng ", "").trim().toLowerCase();
                            if (window.songs && window.songs[songTitle]) {
                                playSong(ws, window.songs[songTitle]);
                            } else {
                                ws.send(JSON.stringify({ type: "chat", data: { message: `❌ Ingen sang fundet med titlen "${songTitle}"` } }));
                            }
                            return;
                        }
                    }
                } catch (e) { console.error("WS send error:", e); }
                return originalSend.call(this, data);
            };

            return ws;
        }
    });

})();