您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Overrides Internet Roadtrip radio with WBOR and shows live song and show info
当前为
// ==UserScript== // @name Internet Roadtrip Permanent Radio - WBOR // @description Overrides Internet Roadtrip radio with WBOR and shows live song and show info // @namespace http://tampermonkey.net/ // @match https://neal.fun/internet-roadtrip/ // @version 1.3.1 // @author TotallyNotSamm // @license MIT // @run-at document-end // @require https://cdn.jsdelivr.net/npm/[email protected] // @grant GM_xmlhttpRequest // @connect azura.wbor.org // @connect playlists.wbor.org // @icon https://neal.fun/favicons/internet-roadtrip.png // ==/UserScript== (async function () { if (!IRF.isInternetRoadtrip) return; let nowPlayingText = "Loading..."; let liveShowName = null; let liveDjName = null; let isPopupOpen = false; async function fetchNowPlaying() { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: "https://azura.wbor.org/api/nowplaying/1", onload: function (response) { try { const data = JSON.parse(response.responseText); const song = data[0]?.now_playing?.song || {}; const artist = song.artist || "Unknown Artist"; const title = song.title || "Unknown Title"; nowPlayingText = `${title} – ${artist}`; } catch (e) { console.error("[WBOR] JSON parse error:", e); nowPlayingText = "Unknown Track – Unknown Artist"; } finally { resolve(); } }, onerror: function (e) { console.error("[WBOR] GM_xmlhttpRequest failed:", e); nowPlayingText = "Unknown Track – Unknown Artist"; resolve(); } }); }); } async function fetchLiveShowInfo() { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: "https://playlists.wbor.org/WBOR/", onload: function (response) { try { const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, "text/html"); const showTitleEl = doc.querySelector("h3.show-title a"); liveShowName = showTitleEl ? showTitleEl.textContent.trim() : null; const djNameEl = doc.querySelector("p.dj-name a"); liveDjName = djNameEl ? djNameEl.textContent.trim() : null; } catch (e) { console.error("[WBOR] Failed to parse live show info HTML:", e); liveShowName = null; liveDjName = null; } finally { resolve(); } }, onerror: function (e) { console.error("[WBOR] GM_xmlhttpRequest failed for live show info:", e); liveShowName = null; liveDjName = null; resolve(); } }); }); } async function updateAllInfo() { await Promise.all([fetchNowPlaying(), fetchLiveShowInfo()]); } await updateAllInfo(); setInterval(updateAllInfo, 30000); const container = await IRF.vdom.container; const originalUpdateData = container.methods.updateData; container.state.updateData = new Proxy(originalUpdateData, { apply: (target, thisArg, args) => { const currentStation = args[0].station?.name; const alreadySet = currentStation === "WBOR 91.1 FM"; if (!alreadySet) { args[0].station = { name: "WBOR 91.1 FM", url: "https://listen.wbor.org/", distance: 0 }; } IRF.vdom.radio.then(radio => { if (radio.state.isPoweredOn) { radio.state.stationInfo = nowPlayingText; } else { radio.state.stationInfo = "TUNE IN"; } }); return Reflect.apply(target, thisArg, args); } }); const radioBody = document.querySelector(".radio-body[data-v-30357640]"); if (!radioBody) return; radioBody.style.position = "relative"; const infoButton = document.createElement("button"); infoButton.textContent = "i"; infoButton.setAttribute("aria-label", "Show WBOR Info"); Object.assign(infoButton.style, { position: "absolute", top: "8px", left: "8px", width: "20px", height: "20px", borderRadius: "50%", border: "none", background: "transparent", color: "white", fontWeight: "bold", fontFamily: "inherit", cursor: "pointer", padding: "0", lineHeight: "18px", textAlign: "center", userSelect: "none", zIndex: "9999", }); radioBody.appendChild(infoButton); const tooltipSpan = document.createElement("div"); tooltipSpan.textContent = "Show more info"; Object.assign(tooltipSpan.style, { position: "fixed", backgroundColor: "rgba(0, 0, 0, 0.75)", color: "#fff", padding: "4px 8px", borderRadius: "4px", fontSize: "12px", fontFamily: "inherit", opacity: "0", visibility: "hidden", transition: "opacity 0.2s ease", whiteSpace: "nowrap", zIndex: "9998", }); document.body.appendChild(tooltipSpan); const infoPopup = document.createElement("div"); const refStyles = getComputedStyle(radioBody.querySelector(".station-name")); Object.assign(infoPopup.style, { position: "fixed", backgroundColor: "rgba(0, 0, 0, 0.75)", color: "#fff", padding: "8px 12px", borderRadius: "6px", fontFamily: refStyles.fontFamily, fontWeight: "normal", fontSize: "14px", maxWidth: "240px", boxShadow: "0 2px 8px rgba(0,0,0,0.8)", opacity: "0", visibility: "hidden", transition: "opacity 0.25s ease", userSelect: "none", zIndex: 9998, }); document.body.appendChild(infoPopup); function updatePopupContent() { let liveShowText = "No live shows currently"; // doesnt show live show info if dj is "commodore 64" if ( liveDjName && !/wbor'?s commodore\s*64/i.test(liveDjName.trim()) ) { liveShowText = `Live Show: ${liveShowName}${liveDjName ? ` with ${liveDjName}` : ""}`; } infoPopup.innerHTML = ` <div><strong>Now Playing:</strong> ${nowPlayingText}</div> <div style="margin-top: 8px;"><strong>${liveShowText}</strong></div> `; } function positionPopup() { const btnRect = infoButton.getBoundingClientRect(); const popupRect = infoPopup.getBoundingClientRect(); let left = btnRect.left - popupRect.width - 8; let top = btnRect.top + (btnRect.height / 2) - (popupRect.height / 2); if (left < 8) { left = btnRect.right + 8; } if (top < 8) top = 8; if (top + popupRect.height > window.innerHeight - 8) top = window.innerHeight - popupRect.height - 8; infoPopup.style.left = `${left}px`; infoPopup.style.top = `${top}px`; } infoButton.addEventListener("mouseenter", () => { if (isPopupOpen) return; const rect = infoButton.getBoundingClientRect(); tooltipSpan.style.left = `${rect.left - tooltipSpan.offsetWidth - 8}px`; tooltipSpan.style.top = `${rect.top + (rect.height / 2) - 10}px`; tooltipSpan.style.opacity = "1"; tooltipSpan.style.visibility = "visible"; }); infoButton.addEventListener("mouseleave", () => { tooltipSpan.style.opacity = "0"; tooltipSpan.style.visibility = "hidden"; }); infoButton.addEventListener("click", () => { isPopupOpen = !isPopupOpen; if (isPopupOpen) { updatePopupContent(); infoPopup.style.opacity = "1"; infoPopup.style.visibility = "visible"; tooltipSpan.style.opacity = "0"; tooltipSpan.style.visibility = "hidden"; positionPopup(); } else { infoPopup.style.opacity = "0"; infoPopup.style.visibility = "hidden"; } }); document.addEventListener("click", (e) => { if (!infoPopup.contains(e.target) && e.target !== infoButton) { infoPopup.style.opacity = "0"; infoPopup.style.visibility = "hidden"; isPopupOpen = false; } }); window.addEventListener("resize", () => { if (infoPopup.style.visibility === "visible") { positionPopup(); } }); })(); //vibe-coded like crazy
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址