// ==UserScript==
// @name Music Room
// @namespace music-room-bgm
// @version 1.4.0
// @author CHANG
// @description 一起在bgm听bgm吧!
// @match https://bgm.tv/
// @match https://bangumi.tv/
// @match https://chii.in/
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
const WORKER_WS = "wss://music-room.mikuorz.workers.dev/room/default";
const style = document.createElement("style");
style.innerHTML = `
#music-room-panel {
position: fixed; top: 100px; right: 20px; width: 320px;
background: rgba(17, 17, 17, 0.98); color: #eee;
border-radius: 12px; font-family: sans-serif; z-index: 999999;
box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #333;
display: flex; flex-direction: column; overflow: hidden; transition: width 0.3s;
}
#music-room-panel.minimized { width: 45px; height: 45px; border-radius: 50%; overflow: hidden; }
#room-header {
background: #222; padding: 10px 15px; cursor: move;
display: flex; justify-content: space-between; align-items: center;
border-bottom: 1px solid #333; user-select: none;
}
.header-btns span { margin-left: 10px; cursor: pointer; font-family: monospace; font-size: 16px; color: #888; }
.header-btns span:hover { color: #fff; }
.room-content { padding: 15px; }
#statusTag { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #333; color: #7fd; }
#current-title { font-size: 15px; font-weight: bold; margin: 10px 0 5px; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#playlist-container { max-height: 100px; overflow-y: auto; font-size: 12px; color: #999; margin: 10px 0; border-top: 1px solid #222; padding-top: 5px; }
.song-item { padding: 3px 0; border-bottom: 1px solid #222; }
.input-group { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.input-group input { background: #111; border: 1px solid #444; color: #fff; padding: 6px; border-radius: 4px; font-size: 12px; }
.input-group button { background: #1db954; border: none; color: #fff; padding: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; }
#room-audio { width: 100%; height: 35px; filter: invert(90%) hue-rotate(180deg); margin-top: 10px; }
#skip-link { font-size: 11px; color: #666; text-align: center; margin: 8px 0; cursor: pointer; text-decoration: underline; }
#mini-icon { display: none; width: 100%; height: 100%; justify-content: center; align-items: center; font-size: 20px; cursor: pointer; background: #1db954; }
#music-room-panel.minimized .room-content, #music-room-panel.minimized #room-header { display: none; }
#music-room-panel.minimized #mini-icon { display: flex; }
`;
document.head.appendChild(style);
const panel = document.createElement("div");
panel.id = "music-room-panel";
panel.innerHTML = `
🎵
CONNECTING
0人
...
00:00 / 00:00
Skip
`;
document.body.appendChild(panel);
const audio = document.getElementById("room-audio");
const titleText = document.getElementById("current-title");
const timeText = document.getElementById("time-info");
const statusTag = document.getElementById("statusTag");
const listContainer = document.getElementById("playlist-container");
// 窗口控制逻辑
document.getElementById("btn-min").onclick = (e) => { e.stopPropagation(); panel.classList.add("minimized"); };
document.getElementById("mini-icon").onclick = () => panel.classList.remove("minimized");
document.getElementById("btn-close").onclick = () => { if(confirm("确定要退出播放间吗?")) panel.style.display = "none"; };
// 拖动逻辑
let isDragging = false, offsetX, offsetY;
const header = document.getElementById("room-header");
header.onmousedown = (e) => { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; };
document.onmousemove = (e) => { if (!isDragging || panel.classList.contains("minimized")) return; panel.style.left = (e.clientX - offsetX) + "px"; panel.style.top = (e.clientY - offsetY) + "px"; panel.style.right = "auto"; };
document.onmouseup = () => { isDragging = false; };
// WebSocket 逻辑
const ws = new WebSocket(WORKER_WS);
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === "state") {
if (!data.current) { titleText.textContent = "暂无播放"; listContainer.innerHTML = ""; audio.src = ""; return; }
titleText.textContent = data.current.title;
listContainer.innerHTML = "队列:";
data.playlist.forEach(s => { const item = document.createElement("div"); item.className = "song-item"; item.textContent = s.title; listContainer.appendChild(item); });
const now = Date.now();
if (now >= data.endsAt) { statusTag.textContent = "BUFFERING"; audio.pause(); return; }
statusTag.textContent = "LIVE";
if (audio.src !== data.current.url) { audio.src = data.current.url; audio.load(); }
const target = (now - data.startedAt) / 1000;
if (Math.abs(audio.currentTime - target) > 2) audio.currentTime = target;
if (audio.paused) audio.play().catch(() => statusTag.textContent = "MUTED");
} else if (data.type === "online") {
document.getElementById("onlineCount").textContent = `${data.count}人`;
}
};
// 点歌逻辑
document.getElementById("addSong").onclick = async () => {
const tI = document.getElementById("songTitle"), uI = document.getElementById("songUrl"), btn = document.getElementById("addSong");
const url = uI.value.trim(); if(!url) return;
btn.disabled = true; btn.textContent = "...";
try {
const dur = await new Promise((res, rej) => {
const a = new Audio(url); a.onloadedmetadata = () => res(a.duration); a.onerror = rej; setTimeout(rej, 8000);
});
ws.send(JSON.stringify({ type: "enqueue", song: { id: Date.now().toString(), title: tI.value || "未命名", url, duration: Math.ceil(dur) } }));
tI.value = ""; uI.value = "";
} catch(e) { alert("Error"); }
btn.disabled = false; btn.textContent = "点歌";
};
document.getElementById("skip-link").onclick = () => ws.send(JSON.stringify({ type: "skip" }));
setInterval(() => { if (audio.src) timeText.textContent = `${Math.floor(audio.currentTime/60)}:${Math.floor(audio.currentTime%60).toString().padStart(2,'0')} / ${Math.floor(audio.duration/60 || 0)}:${Math.floor(audio.duration%60 || 0).toString().padStart(2,'0')}`; }, 1000);
})();