您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
大点儿操作方便
// ==UserScript== // @name B站直播增强型关注列表 概念版 // @namespace http://tampermonkey.net/ // @version 2.2.0 // @description 大点儿操作方便 // @author SoraYuki & TGSAN // @match *://live.bilibili.com/* // @grant none // @noframes // ==/UserScript== (function () { 'use strict'; let ENABLE_BLUR = true; if (ENABLE_BLUR == true) { ENABLE_BLUR = CSS.supports("backdrop-filter", "saturate(180%) blur(40px)") || CSS.supports("-webkit-backdrop-filter", "saturate(180%) blur(40px)"); } let isHomePage = document.location.pathname == "/"; let hasInit = false; // 插件是否初始化成功 let followListOpened = false; // 关注列表开启状态 let followListLoading = false; // 列表是否仍在拉取 let applyUniStyle = document.createElement("style"); let applyFeatureStyle = document.createElement("style"); let applyThemeStyle = document.createElement("style"); let darkStyle = ` /* 卡片根框架 */ .plugin-follow-card-root { background-color: rgba(176, 176, 196, 0.1); } /* 卡片信息标题 */ .plugin-follow-card-text-title { color: #fff; } /* 卡片信息子标题 */ .plugin-follow-card-text-subtitle { color: #999; } /* 关注列表标题 */ .plugin-follow-list-title { color: #0080c6; } /* 关注列表 */ .plugin-follow-list-div { background-color: rgba(21, 21, 21, ${ENABLE_BLUR ? "0.85" : "1"}); pointer-events: none; width: 1px; /* 保留1px 绕过 Safari BUG */ opacity: 0; } .plugin-follow-list-div-opened { ${ENABLE_BLUR ? "backdrop-filter: saturate(180%) blur(40px);" : ""} ${ENABLE_BLUR ? "-webkit-backdrop-filter: saturate(180%) blur(40px);" : ""} pointer-events: auto !important; width: 320px; opacity: 1; } /* 滚动条 */ .plugin-follow-list-div::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, 0.1); } `; let lightStyle = ` /* 卡片根框架 */ .plugin-follow-card-root { background-color: rgba(176, 176, 196, 0.2); } /* 卡片信息标题 */ .plugin-follow-card-text-title { color: #000; } /* 卡片信息子标题 */ .plugin-follow-card-text-subtitle { color: #999; } /* 关注列表标题 */ .plugin-follow-list-title { color: #23ade6; } /* 关注列表 */ .plugin-follow-list-div { background-color: rgba(255, 255, 255, ${ENABLE_BLUR ? "0.85" : "1"}); pointer-events: none; width: 1px; /* 保留1px 绕过 Safari BUG */ opacity: 0; } .plugin-follow-list-div-opened { ${ENABLE_BLUR ? "backdrop-filter: saturate(180%) blur(40px);" : ""} ${ENABLE_BLUR ? "-webkit-backdrop-filter: saturate(180%) blur(40px);" : ""} pointer-events: auto !important; width: 320px; opacity: 1; } /* 滚动条 */ .plugin-follow-list-div::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.1); } `; let uniStyle = ` /* 卡片根框架 */ .plugin-follow-card-root { width: calc(100% - 50px); height: max-content; margin: 15px 15px; padding: 0px 10px; border-radius: 10px; transition: transform .2s cubic-bezier(.22,.58,.12,.98); } .plugin-follow-card-root:hover { transform: perspective(1px) scale(1.05) translate3d(0,0,0); } .plugin-follow-card-root:active { transform: perspective(1px) scale(0.98) translate3d(0,0,0); } /* 卡片信息 */ .plugin-follow-card-info-div { vertical-align: top; display: inline-block; padding-left: 10px; overflow-x: hidden; -webkit-box-sizing: border-box; box-sizing: border-box; width: calc(100% - 40px); } /* 卡片信息标题 */ .plugin-follow-card-text-title { font-size: 15px; } /* 卡片信息子标题 */ .plugin-follow-card-text-subtitle { font-size: 12px; } /* 卡片头像 */ .plugin-follow-card-avatar { vertical-align: top; width: 40px; height: 40px; margin-top: 10px; display: inline-block; border-radius: 50%; background-color: #F7F7F7; background-size: cover; } /* 卡片文字 */ .plugin-follow-card-text { line-height: 1.15; margin: 10px 0; display: block; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; white-space: nowrap; -webkit-box-sizing: border-box; box-sizing: border-box; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* 关注列表标题 */ .plugin-follow-list-title { font-size: 18px; line-height: 24px; } /* 关注列表 */ .plugin-follow-list-div { position: fixed; height: 100vh; z-index: 9999; top: 0px; left: 0px; box-shadow: rgba(0, 0, 0, 0.22) 1px 0px 12px 0px; transition: opacity 0.4s cubic-bezier(0.22, 0.58, 0.12, 0.98) 0s, width 0.4s cubic-bezier(0.22, 0.58, 0.12, 0.98) 0s; overflow-y: auto; pointer-events: auto; -webkit-tap-highlight-color: transparent; } /* 滚动条 */ .plugin-follow-list-div::-webkit-scrollbar { width: 20px; } /* 滚动条 */ .plugin-follow-list-div::-webkit-scrollbar-thumb { height: 56px; border-radius: 10px; border: 4px solid transparent; background-clip: content-box; } /* 首页新关注栏标题 */ .focus-left-ctnr { cursor: pointer; } `; let featureStyle = ` /* 直播间显示分区 */ .header-info-ctnr .rows-ctnr .lower-row .live-area { display: flex !important; } .header-info-ctnr .rows-ctnr .lower-row .live-area .area-link { max-width: min-content !important; } /* 删除瞎子都能看见的“盲水印” */ .blur-edges-ctnr { display: none !important; } /* 删除非常浪费性能的敏感分区打码 */ #web-player-module-area-mask-panel > * { backdrop-filter: none !important } `; function parseColor(color) { var x = document.createElement('div'); document.body.appendChild(x); var rgba; var red = 0, green = 0, blue = 0, alpha = 0; try { x.style = 'color: ' + color + '!important'; color = window.getComputedStyle(x).color; rgba = color.match(/rgba?\((.*)\)/)[1].split(',').map(Number); red = rgba[0]; green = rgba[1]; blue = rgba[2]; alpha = '3' in rgba ? rgba[3] : 1; } catch (e) { } x.parentNode.removeChild(x); return { 'red': red, 'green': green, 'blue': blue, 'alpha': alpha }; } function secondsToHms(seconds) { let SECONDS_PER_DAY = 86400; let HOURS_PER_DAY = 24; let days = Math.floor(seconds / SECONDS_PER_DAY); let remainderSeconds = seconds % SECONDS_PER_DAY; let hms = new Date(remainderSeconds * 1000).toISOString().substring(11, 19); return hms.replace(/^(\d+)/, h => `${Number(h) + days * HOURS_PER_DAY}`.padStart(2, '0')); }; function createLiveCard(avatarUrl, userName, titleName, link, verifyNum, tiptext) { let getVerifyColor = function (_verifyNum) { switch (_verifyNum) { case -1: return "#fd5d91ff"; // 普通用户 case 0: return "#feb959ff"; // 个人 case 1: return "#50c8fdff"; // 企业 default: return "#808080ff"; } }; let avatarStyle = "background-image: url("" + avatarUrl + ""); box-shadow: 0 0 10px 1px " + getVerifyColor(verifyNum) + ";"; let cardRoot = document.createElement("div"); cardRoot.className = "plugin-follow-card-root"; let innerHTML = '<a title="' + tiptext + '" href="' + link + '" target="' + (isHomePage === true ? "_blank" : "_self") + '" ' + (isHomePage === true ? 'onclick="playerInstance.stop()"' : '') + ' style="text-decoration: none; margin: 0;">'; innerHTML += '<div class="plugin-follow-card-avatar" style="' + avatarStyle + '"></div>'; innerHTML += '<div class="plugin-follow-card-info-div">'; innerHTML += '<p class="plugin-follow-card-text plugin-follow-card-text-title">' + userName + '</p>'; innerHTML += '<p class="plugin-follow-card-text plugin-follow-card-text-subtitle">' + titleName + '</p>'; innerHTML += '</div>'; cardRoot.innerHTML = innerHTML; return cardRoot; } function clearList(followListBodyDiv) { while (followListBodyDiv.lastElementChild) { followListBodyDiv.removeChild(followListBodyDiv.lastElementChild); } } async function loadList(followListBodyDiv) { clearList(followListBodyDiv); // 标题 let titleDiv = document.createElement("div"); titleDiv.style = "width: max-content; padding: 20px 10px 5px 20px;"; titleDiv.innerHTML = '<a href="https://link.bilibili.com/p/center/index#/user-center/follow/1" target="_blank" style="text-decoration: none;"><span class="plugin-follow-list-title"></span></a>'; followListBodyDiv.appendChild(titleDiv); let titleText = titleDiv.children[0].children[0]; titleText.innerText = "我的关注 - 载入中..."; let j; try { let result = await fetch('https://api.live.bilibili.com/xlive/app-interface/v1/relation/liveAnchor', { credentials: 'include' }); j = await result.json(); } catch (e) { titleText.innerText = "我的关注 - 列表拉取失败 (゚∀。)"; return; } let count = j.data.total_count; if (j.data.rooms != undefined) { try { j.data.rooms.forEach(room => { let tiptext = "人气:" + room.online + " 已开播:" + secondsToHms((Date.now() - (room.live_time * 1000)) / 1000); followListBodyDiv.appendChild(createLiveCard(room.face, room.uname, room.title, "//live.bilibili.com/" + room.roomid, room.official_verify, tiptext)); }) } catch (e) { titleText.innerText = "我的关注 - 列表读取失败 (゚∀。)"; return; } } // 页脚 let footerDiv = document.createElement("div"); footerDiv.style = "height: 40px;"; followListBodyDiv.appendChild(footerDiv); titleText.innerText = "我的关注 (" + count + ")"; }; async function loadListOld(followListBodyDiv) { clearList(followListBodyDiv); // 标题 let titleDiv = document.createElement("div"); titleDiv.style = "width: max-content; padding: 20px 10px 10px 20px;"; titleDiv.innerHTML = '<a href="https://link.bilibili.com/p/center/index#/user-center/follow/1" target="_blank" style="text-decoration: none;"><span style="font-size: 18px; color: #23ade6; line-height: 24px;"></span></a>'; followListBodyDiv.appendChild(titleDiv); let titleText = titleDiv.children[0].children[0]; titleText.innerText = "我的关注 - 载入中..."; let j; try { let result = await fetch('https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=1&page_size=10', { credentials: 'include' }); j = await result.json(); } catch (e) { titleText.innerText = "我的关注 - 列表拉取失败 (゚∀。)"; return; } let count = j.data.count; let offset = 0; while (count > offset) { try { if (offset > 0) { let result = await fetch('https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=' + (offset / 10 + 1) + '&page_size=10', { credentials: 'include' }); j = await result.json(); } } catch (e) { titleText.innerText = "我的关注 - 列表拉取失败 (゚∀。)"; return; } for (let i = 0; i < j.data.rooms.length; ++i) { let x = j.data.rooms[i]; followListBodyDiv.appendChild(createLiveCard(x.face, x.uname, x.title, x.link, "")); } offset += j.data.rooms.length; } // 页脚 let footerDiv = document.createElement("div"); footerDiv.style = "height: 40px;"; followListBodyDiv.appendChild(footerDiv); titleText.innerText = "我的关注 (" + count + ")"; }; function openList(followListFrameDiv, followListBodyDiv) { if (followListOpened === false) { followListOpened = true; followListFrameDiv.classList.add("plugin-follow-list-div-opened"); if (followListLoading === false) { followListLoading = true; loadList(followListBodyDiv).then(function () { followListLoading = false; if (followListOpened === false) { clearList(followListBodyDiv); } }); } } } function closeList(followListFrameDiv, followListBodyDiv) { if (followListOpened === true) { followListOpened = false; followListFrameDiv.classList.remove("plugin-follow-list-div-opened"); clearList(followListBodyDiv); } } function updateStyle() { let bodyColor = parseColor(window.getComputedStyle(document.body, null)['background-color']); let isLight = (bodyColor.alpha == 0 || (((bodyColor.red + bodyColor.green + bodyColor.blue) / 3) >= 128)); // 添加样式(通用) if (document.head == applyFeatureStyle.parentNode) { document.head.removeChild(applyFeatureStyle) } applyFeatureStyle.innerHTML = featureStyle; document.head.appendChild(applyFeatureStyle); // 添加样式(附加功能) if (document.head == applyUniStyle.parentNode) { document.head.removeChild(applyUniStyle) } applyUniStyle.innerHTML = uniStyle; document.head.appendChild(applyUniStyle); // 添加样式(主题色) if (document.head == applyThemeStyle.parentNode) { document.head.removeChild(applyThemeStyle) } applyThemeStyle.innerHTML = isLight ? lightStyle : darkStyle; document.head.appendChild(applyThemeStyle); } let initIntervalId = setInterval(function () { let oldFollowBtn = document.querySelector(".side-bar-btn[data-upgrade-intro='Follow']"); // 直播间 if (oldFollowBtn == null) { oldFollowBtn = document.querySelector(".focus-left-ctnr"); // 首页(新版) } let oldFollowBtn2 = document.querySelector(".item-icon-linkPanel")?.parentElement?.parentElement?.parentElement?.parentElement; // 标头 let oldFollowDiag = document.querySelector(".follow-cntr")?.parentElement; if (oldFollowBtn != null && oldFollowBtn2 != null) { hasInit = true; if (oldFollowDiag != null) { oldFollowDiag.hidden = true; // 隐藏旧的关注框 } let newFollowBtn = oldFollowBtn.cloneNode(true); oldFollowBtn.parentNode.replaceChild(newFollowBtn, oldFollowBtn); newFollowBtn.addEventListener("click", function (event) { event.cancelBubble = true; // 重新阻止按钮点击事件传递 }); let newFollowBtn2 = oldFollowBtn2.cloneNode(true); oldFollowBtn2.parentNode.replaceChild(newFollowBtn2, oldFollowBtn2); newFollowBtn2.addEventListener("click", function (event) { event.cancelBubble = true; // 重新阻止按钮点击事件传递 }); let followListFrameDiv = document.createElement("div"); followListFrameDiv.classList.add("plugin-follow-list-div"); followListFrameDiv.addEventListener("click", function (event) { event.cancelBubble = true; // 阻止关注列表点击事件传到父级 }); document.addEventListener("click", function () { closeList(followListFrameDiv, followListBodyDiv); // 点其他元素(会传递事件的)收回列表 }); document.body.appendChild(followListFrameDiv); let followListBodyDiv = document.createElement("div"); followListBodyDiv.style.setProperty("height", "100%"); followListBodyDiv.style.setProperty("width", "100%"); followListFrameDiv.appendChild(followListBodyDiv); let onOpenFollow = function() { if (followListOpened === true) { closeList(followListFrameDiv, followListBodyDiv); } else { updateStyle(); openList(followListFrameDiv, followListBodyDiv); } } newFollowBtn.addEventListener('click', function () { onOpenFollow(); }); newFollowBtn2.addEventListener('click', function () { onOpenFollow(); }); updateStyle(); let sideBarPopup = document.querySelector(".side-bar-popup-cntr"); if (sideBarPopup != null) { sideBarPopup.addEventListener("click", () => { setTimeout(updateStyle, 100); }); } clearInterval(initIntervalId); } }, 100); setTimeout(function () { if (hasInit === false) { clearInterval(initIntervalId); } }, 5000); // 元素查找超时(5s) })();