您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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; } }); })();