// ==UserScript== // @name BiliPlus - Bilibili 加大杯 // @namespace https://github.com/timothy-lau/biliplus // @version 1.0.3 // @description 专门为细节控的人群使用,只要在B站上设计不合理的地方,都可以加入到大杯中。 // @author timothy-lau@outlook.com // @match https://www.bilibili.com/* // @match https://*.bilibili.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // 默认配置 const DEFAULT_SETTINGS = { 'feed-roll-history-btn': true, 'clean-home-page': true, 'hide-hot-search-list': true, 'stepless-video-rate': true }; // 获取设置 function getSetting(key) { const value = GM_getValue(key); return value !== undefined ? value : DEFAULT_SETTINGS[key]; } // 保存设置 function setSetting(key, value) { GM_setValue(key, value); } // 工具库 class _UTILS { static getBvidFromUrl(url) { const match = /\/video\/([A-Za-z0-9]+)/.exec(url); if (match) { return match[1]; } return null; } static findParentElement(element, func) { let _pe = element.parentElement; while (_pe != null) { if (func(_pe)) { return _pe; } _pe = _pe.parentElement; } if (_pe == null) { return null; } } static mixinKeyEncTab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52, ]; // 对 imgKey 和 subKey 进行字符顺序打乱编码 static getMixinKey = (orig) => this.mixinKeyEncTab .map((n) => orig[n]) .join("") .slice(0, 32); // 为请求参数进行 wbi 签名 static encWbi(params, img_key, sub_key) { const mixin_key = this.getMixinKey(img_key + sub_key), curr_time = Math.round(Date.now() / 1000), chr_filter = /[!'()*]/g; Object.assign(params, { wts: curr_time }); // 添加 wts 字段 // 按照 key 重排参数 const query = Object.keys(params) .sort() .map((key) => { // 过滤 value 中的 "!'()*" 字符 const value = params[key].toString().replace(chr_filter, ""); return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; }) .join("&"); const wbi_sign = md5(query + mixin_key); // 计算 w_rid return query + "&w_rid=" + wbi_sign; } // 获取最新的 img_key 和 sub_key static async getWbiKeys() { const { wbi_img: { img_url, sub_url }, } = await _BILIAPI.getNavUserInfo(); return { img_key: img_url.slice( img_url.lastIndexOf("/") + 1, img_url.lastIndexOf(".") ), sub_key: sub_url.slice( sub_url.lastIndexOf("/") + 1, sub_url.lastIndexOf(".") ), }; } // 刷新 wts 和 wrid static async getwts(params) { const web_keys = await this.getWbiKeys(); const img_key = web_keys.img_key; const sub_key = web_keys.sub_key; const query = this.encWbi(params, img_key, sub_key); return query; } static observe(node, callback, options) { const observer = new MutationObserver((mutations, ob) => { callback(mutations, ob); }); observer.observe( node, Object.assign( { childList: true, subtree: true }, options ) ); const disconnect = () => observer.disconnect(); return disconnect; } } // B站API class _BILIAPI { static BILIBILI_API = 'https://api.bilibili.com'; /** * 获取B站视频 aid、cid 等信息 * @param {string} 视频 bvid * @returns 视频data */ static async getVideoInfo(bvid) { const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/view?bvid=${bvid}`); const jsonData = await response.json(); if (response.status !== 200 || !jsonData) { throw new Error(); } return jsonData.data; } /** * 获取导航栏用户信息 * @returns 用户信息data */ static async getNavUserInfo() { const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/nav`); const jsonData = await response.json(); if (response.status !== 200 || !jsonData) { throw new Error(); } return jsonData.data; } } // MD5 加密 !function(){ "use strict"; function t(t){if(t)b[0]=b[16]=b[1]=b[2]=b[3]=b[4]=b[5]=b[6]=b[7]=b[8]=b[9]=b[10]=b[11]=b[12]=b[13]=b[14]=b[15]=0,this.blocks=b,this.buffer8=a;else if(u){var r=new ArrayBuffer(68);this.buffer8=new Uint8Array(r),this.blocks=new Uint32Array(r)}else this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];this.h0=this.h1=this.h2=this.h3=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0} function r(r,e){var i,s=_(r);if(r=s[0],s[1]){var h,n=[],a=r.length,o=0;for(i=0;i>>6,n[o++]=128|63&h):h<55296||h>=57344?(n[o++]=224|h>>>12,n[o++]=128|h>>>6&63,n[o++]=128|63&h):(h=65536+((1023&h)<<10|1023&r.charCodeAt(++i)),n[o++]=240|h>>>18,n[o++]=128|h>>>12&63,n[o++]=128|h>>>6&63,n[o++]=128|63&h);r=n}r.length>64&&(r=new t(!0).update(r).array());var f=[],u=[];for(i=0;i<64;++i){var c=r[i]||0;f[i]=92^c,u[i]=54^c}t.call(this,e),this.update(u),this.oKeyPad=f,this.inner=!0,this.sharedMemory=e} var e="input is invalid type",i="object"==typeof window,s=i?window:{};s.JS_MD5_NO_WINDOW&&(i=!1);var h=!i&&"object"==typeof self,n=!s.JS_MD5_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node;n?s=global:h&&(s=self);var a,o=!s.JS_MD5_NO_COMMON_JS&&"object"==typeof module&&module.exports,f="function"==typeof define&&define.amd,u=!s.JS_MD5_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,c="0123456789abcdef".split(""),y=[128,32768,8388608,-2147483648],p=[0,8,16,24],d=["hex","array","digest","buffer","arrayBuffer","base64"],l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),b=[];if(u){var v=new ArrayBuffer(68);a=new Uint8Array(v),b=new Uint32Array(v)}var w=Array.isArray;!s.JS_MD5_NO_NODE_JS&&w||(w=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var A=ArrayBuffer.isView;!u||!s.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW&&A||(A=function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer});var _=function(t){var r=typeof t;if(r==="string")return[[t],!1];if(r==="object")if(t===null)throw new Error(e);if(A(t))return[[t],!0];if(w(t))return[[t],!1];if(r==="number")return[[t],!1];throw new Error(e)};t.prototype.update=function(t){var e,i,s=_(t),h=s[0],n=s[1],a=h.length;if(this.hashed)throw new Error("hashed");if(!a)return this;if(n&&h.length===1&&h[0].constructor===Uint8Array){var o=h[0];if(!o.length)return this;h=o} for(this.first=!1,e=0;e=a)break;this.blocks[d]=i=h[f],i=String.fromCharCode(i),this.blocks[d]=i.charCodeAt(0),l=0}}if(f>=a)break;y+=i.length} this.bytes+=u} else if("number"==typeof i){if(isNaN(i))throw new Error(e);this.blocks[this.bytes%64]=i&255,this.bytes++} else if(A(i)){if(!i.length)continue;for(var b=0,v=i.length,w=Math.ceil((this.bytes%64+v)/64),A=0;A>(8*x)&255}else{if(++f>=a)break;this.blocks[_]=i=h[f],i instanceof ArrayBuffer&&(i=new Uint8Array(i)),A=0,b+=v}} this.bytes+=v} else{if(!w(i))throw new Error(e);for(var T=0,O=i.length;T>>31},x=function(t){return t<<5|t>>>27},T=function(t){return t<<3|t>>>29},O=function(t){return t<<7|t>>>25},j=function(t,r,e,i,s,h,n){return t=r&e|~r&i,t+=s+t+h+n,r=E(r),t+=r,t};var R=function(t,r,e,i,s,h,n){return t=r&i|e&~i,t+=s+t+h+n,r=x(r),t+=r,t};var S=function(t,r,e,i,s,h,n){return t=r^e^i,t+=s+t+h+n,r=T(r),t+=r,t};var M=function(t,r,e,i,s,h,n){return t=e^(r|~i),t+=s+t+h+n,r=O(r),t+=r,t};t.prototype.finalize=function(){if(this.finalized)return this;this.finalized=!0;for(var t=this.bytes,e=this.blocks,i=[],s=0;s<64;++s)i[s]=e[s];i[t%64]=y[t%64];for(var h=Math.ceil((t+8)/64)-1;h<16;++h)i[h*4+3]=t<<3;var n=this.h0,a=this.h1,o=this.h2,f=this.h3,u=[1732584193,4023233417,2562383102,271733878];for(t=0;t<16;++t){var c=[],y=0;for(s=0;s<4;++s)c[s]=i[t*4+s]<>p[s]&255;u[t]+=u[t+4]}return this.h0=u[0],this.h1=u[1],this.h2=u[2],this.h3=u[3],this.hBytes=16,v};t.prototype.array=function(){return E.call(this).slice(0,16)};t.prototype.digest=function(){return E.call(this)};t.prototype.buffer=function(){if(!u)throw new Error("ArrayBuffer is not supported");return new Uint8Array(this.digest()).buffer};t.prototype.arrayBuffer=function(){return this.buffer()};t.prototype.hex=function(){var t=E.call(this),r=[];for(var e=0;e>>4]),r.push(c[15&t[e]]);return r.join("")};t.prototype.base64=function(){var t=E.call(this),r=t.length,e=(t[r-1]<<16|t[r-2]<<8|t[r-3])<<8,e=e.toString(64),i="",s=0;if(e.length<4)for(;s<4-e.length;)i+="=",s++;e=i+e;var h="",n=0;for(s=0;s *:nth-of-type(n + 8) { margin-top: 0px !important; } body[biliplus-clean-mode] .recommended-container_floor-aside .container.is-version8 > *:nth-of-type(n + 13) { margin-top: 0px !important; } .stepless-video-rate-btn { fill: #fff; color: hsla(0, 0%, 100%, 0.8); height: 22px; line-height: 22px; outline: 0; position: relative; text-align: center; z-index: 2; font-size: 14px; width: auto; margin-right: 10px; } .stepless-video-rate-btn:hover { color: #fff; } .stepless-video-rate-btn-result { cursor: pointer; font-weight: 600; width: 100%; user-select: none; } .stepless-video-rate-box { background: hsla(0, 0%, 8%, 0.9); border-radius: 2px; bottom: 41px; display: none; height: 100px; left: 50%; margin-left: -16px; position: absolute; width: 32px; } .stepless-video-rate-box.display { display: block; } .stepless-video-rate-number { color: #e5e9ef; font-size: 12px; height: 28px; line-height: 28px; margin-bottom: 2px; text-align: center; width: 100%; } .stepless-video-rate-progress { height: 60px !important; margin: 0 auto; } .stepless-video-rate-progress .bui-area { -webkit-box-pack: center !important; -ms-flex-pack: center !important; justify-content: center !important; } @media screen and (min-width: 750px) { .bpx-player-container[data-screen='full'] .stepless-video-rate-btn, .bpx-player-container[data-screen='web'] .stepless-video-rate-btn { height: 43px; line-height: 32px; font-size: 16px; } .bpx-player-container[data-screen='full'] .stepless-video-rate-box, .bpx-player-container[data-screen='web'] .stepless-video-rate-box { bottom: 74px; } } /* 设置面板样式 */ .biliplus-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 99999; display: flex; justify-content: center; align-items: center; } .biliplus-settings-panel { background: #fff; border-radius: 12px; padding: 24px; width: 400px; max-width: 90%; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } .biliplus-settings-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #e3e5e7; } .biliplus-settings-title { font-size: 18px; font-weight: 600; color: #18191c; margin: 0; } .biliplus-settings-close { background: none; border: none; font-size: 24px; color: #9499a0; cursor: pointer; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: all 0.2s; } .biliplus-settings-close:hover { background: #f1f2f3; color: #18191c; } .biliplus-settings-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid #f1f2f3; } .biliplus-settings-item:last-child { border-bottom: none; } .biliplus-settings-item-label { font-size: 14px; color: #18191c; font-weight: 500; } .biliplus-settings-item-desc { font-size: 12px; color: #9499a0; margin-top: 4px; } .biliplus-settings-switch { position: relative; width: 44px; height: 24px; background: #c9ccd0; border-radius: 12px; cursor: pointer; transition: background 0.3s; flex-shrink: 0; } .biliplus-settings-switch.active { background: #00aeec; } .biliplus-settings-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: #fff; border-radius: 50%; transition: transform 0.3s; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .biliplus-settings-switch.active::after { transform: translateX(20px); } .biliplus-settings-footer { margin-top: 20px; padding-top: 16px; border-top: 1px solid #e3e5e7; text-align: center; font-size: 12px; color: #9499a0; } `); // 全局补丁 window.addEventListener('keydown', e => { if (e.key === 'Escape') { const exitButton = document.querySelector('.reply-view-image .operation-btn-icon.close-container'); if (exitButton) exitButton.click(); } }); // 首页"换一换"回溯功能 function initFeedRollHistoryBtn() { if (!getSetting('feed-roll-history-btn')) return; const feedHistory = []; let feedHistoryIndex = 0; const feedRollBackBtn = ` `; const feedRollNextBtn = ` `; const targetNode = document.querySelector('.recommended-container_floor-aside'); if (targetNode) { const disconnect = _UTILS.observe(targetNode, () => { let feedRollBtn = document.getElementsByClassName('roll-btn')[0]; if (feedRollBtn) { // 处理返回上一页feed的历史内容 let backBtn = document.createElement('button'); feedRollBtn.parentNode.appendChild(backBtn); backBtn.outerHTML = feedRollBackBtn; document.getElementById('feed-roll-back-btn').addEventListener('click', () => { let feedCards = document.getElementsByClassName('feed-card'); if (feedHistoryIndex == feedHistory.length) { feedHistory.push(listInnerHTMLOfFeedCard(feedCards)); } for (let fc_i = 0; fc_i < feedCards.length; fc_i++) { feedCards[fc_i].innerHTML = feedHistory[feedHistoryIndex - 1][fc_i]; } feedHistoryIndex = feedHistoryIndex - 1; if (feedHistoryIndex == 0) { disableElementById('feed-roll-back-btn', true); } disableElementById('feed-roll-next-btn', false); }); // 处理返回下一页feed的历史内容 let nextBtn = document.createElement('div'); feedRollBtn.parentNode.appendChild(nextBtn); nextBtn.outerHTML = feedRollNextBtn; document.getElementById('feed-roll-next-btn').addEventListener('click', () => { let feedCards = document.getElementsByClassName('feed-card'); for (let fc_i = 0; fc_i < feedCards.length; fc_i++) { feedCards[fc_i].innerHTML = feedHistory[feedHistoryIndex + 1][fc_i]; } feedHistoryIndex = feedHistoryIndex + 1; if (feedHistoryIndex == feedHistory.length - 1) { disableElementById('feed-roll-next-btn', true); } disableElementById('feed-roll-back-btn', false); }); // 处理点击换一换事件 feedRollBtn.id = 'feed-roll-btn'; feedRollBtn.addEventListener('click', () => { // 等待元素加载 setTimeout(() => { if (feedHistoryIndex == feedHistory.length) { let feedCards = listInnerHTMLOfFeedCard(document.getElementsByClassName('feed-card')); feedHistory.push(feedCards); } feedHistoryIndex = feedHistory.length; disableElementById('feed-roll-back-btn', false); disableElementById('feed-roll-next-btn', true); }); }); // disconnect observer disconnect(); } }); } function disableElementById(id, bool) { const element = document.getElementById(id); if (element) { if (bool) { element.classList.add('biliplus-disabled'); } else { element.classList.remove('biliplus-disabled'); } } } function listInnerHTMLOfFeedCard(feedCardElements) { let feedCardInnerHTMLs = []; for (let fc of feedCardElements) { feedCardInnerHTMLs.push(fc.innerHTML); } return feedCardInnerHTMLs; } } // 首页干净模式 function initCleanHomePage() { if (!getSetting('clean-home-page')) return; let body = document.getElementsByTagName('body')[0]; body.setAttribute('biliplus-clean-mode', ''); const recommendedSwipe = document.getElementsByClassName('recommended-swipe')[0]; if (recommendedSwipe) recommendedSwipe.remove(); const loadMoreAnchor = document.querySelector('.load-more-anchor'); if (loadMoreAnchor) { loadMoreAnchor.classList.add('biliplus-load-more-anchor'); const scroll = new Event('scroll'); dispatchEvent(scroll); loadMoreAnchor.classList.remove('biliplus-load-more-anchor'); } } // 隐藏搜索栏热搜列表 function initHideHotSearchList() { if (!getSetting('hide-hot-search-list')) return; const body = document.querySelector('body'); body.classList.add('biliplus-hide-hot-search-list'); // 解决没有历史记录时显示空白的问题 const navSearchform = document.querySelector('#nav-searchform'); if (navSearchform) { navSearchform.addEventListener('focusin', () => { const history = document.querySelector('.search-panel .history'); const searchPanel = document.querySelector('.search-panel'); if (!history) { if (searchPanel) { searchPanel.style.display = 'none'; body.classList.add('biliplus-hide-hot-search-list-search-panel-raduis'); } } else { if (searchPanel) { searchPanel.style.display = 'block'; body.classList.remove('biliplus-hide-hot-search-list-search-panel-raduis'); const clearButton = document.querySelector('.search-panel .history .clear'); if (clearButton) { clearButton.addEventListener('click', () => { searchPanel.style.display = 'none'; body.classList.add('biliplus-hide-hot-search-list-search-panel-raduis'); }); } } } }); // 防止搜索框输入时候样式改变导致下边框圆角不一致 const navSearchInput = document.querySelector('.nav-search-input'); if (navSearchInput) { navSearchInput.addEventListener('input', () => { const suggestions = document.querySelector('.search-panel .suggestions'); const history = document.querySelector('.search-panel .history'); const searchPanel = document.querySelector('.search-panel'); if (!suggestions && !history) { if (searchPanel) { searchPanel.style.display = 'none'; body.classList.add('biliplus-hide-hot-search-list-search-panel-raduis'); } } else { if (searchPanel) { searchPanel.style.display = 'block'; body.classList.remove('biliplus-hide-hot-search-list-search-panel-raduis'); } } }); } } } // 无级视频倍速 function initSteplessVideoRate() { if (!getSetting('stepless-video-rate')) return; let videoRate = 1.0; let hideBoxTimeout = null; var mousePositionY = 0; var initialPositionY = -10; const rateButton = `
无级倍速
1.0
`; document.body.classList.add('biliplus-stepless-video-rate'); // 用 MutationObserver 解决页面初始化时无法找到 bpx-player-ctrl-playbackrate 按钮 const disconnect = _UTILS.observe(document.body, () => { if (document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-playbackrate') == null) { return; } if (document.querySelector('.stepless-video-rate-btn') == null) { const playerControl = document.querySelector('.bpx-player-control-bottom-right'); const oldRateButton = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-playbackrate'); if (playerControl && oldRateButton) { const newRateButton = document.createElement('div'); playerControl.insertBefore(newRateButton, oldRateButton); newRateButton.outerHTML = rateButton; const box = document.querySelector('.stepless-video-rate-box'); const dot = document.querySelector('.stepless-video-rate-box .bui-thumb'); const bar = document.querySelector('.stepless-video-rate-box .bui-bar'); const rate = document.querySelector('.stepless-video-rate-box .stepless-video-rate-number'); // 进入 btn 就显示 box const bilibiliPlayer = document.querySelector('#bilibili-player'); if (bilibiliPlayer) { bilibiliPlayer.addEventListener('mouseover', e => { const target = e.target; if (target.nodeName === 'DIV' && target.parentElement.classList.contains('stepless-video-rate-btn')) { showBox(); if (hideBoxTimeout != null) { clearTimeout(hideBoxTimeout); } } }); } // 离开 btn 就消失 box const steplessVideoRateBtn = document.querySelector('.stepless-video-rate-btn'); if (steplessVideoRateBtn) { steplessVideoRateBtn.addEventListener('mouseleave', e => { // 防抖 400 ms hideBoxTimeout = setTimeout(() => { hideBox(); box.removeEventListener('mousemove', mouseMove); }, 400); }); } // 进度条逻辑 let tempPositionY = 0; function mouseDown(event) { mousePositionY = event.clientY; tempPositionY = initialPositionY; box.addEventListener('mousemove', mouseMove); } function mouseMove(event) { let deltaY = event.clientY - mousePositionY; if (tempPositionY + deltaY < -48 || tempPositionY + deltaY > 0) { return; } initialPositionY = tempPositionY + deltaY; dot.style.transform = `translateY(${initialPositionY}px)`; bar.style.transform = `scaleY(${Math.abs(initialPositionY) / 48})`; videoRate = ((Math.abs(initialPositionY) / 48) * 5).toFixed(1); rate.innerText = videoRate; const video = document.querySelector('video'); if (video) video.playbackRate = videoRate; } function mouseUp() { box.removeEventListener('mousemove', mouseMove); } if (dot) dot.addEventListener('mousedown', mouseDown); if (box) box.addEventListener('mouseup', mouseUp); const steplessBtn = document.querySelector('.stepless-video-rate-btn-result'); if (steplessBtn) { //double click to reset rate steplessBtn.addEventListener('dblclick', () => { const video = document.querySelector('video'); if (video) video.playbackRate = 1.0; videoRate = 1.0; const rateNumber = document.querySelector('.stepless-video-rate-number'); if (rateNumber) rateNumber.innerText = "1.0"; const thumb = document.querySelector('.stepless-video-rate-box .bui-thumb'); if (thumb) thumb.style.transform = 'translateY(-10px)'; const barElement = document.querySelector('.stepless-video-rate-box .bui-bar'); if (barElement) barElement.style.transform = 'scaleY(0.2)'; mousePositionY = 0; initialPositionY = -10; }); } } } else { disconnect(); } }); function showBox() { const rateBox = document.querySelector('.stepless-video-rate-box'); if (rateBox && !rateBox.classList.contains('display')) { rateBox.classList.add('display'); } } function hideBox() { const rateBox = document.querySelector('.stepless-video-rate-box'); if (rateBox && rateBox.classList.contains('display')) { rateBox.classList.remove('display'); } } } // 创建设置面板 function createSettingsPanel() { // 检查是否已存在 if (document.getElementById('biliplus-settings-overlay')) { return; } const overlay = document.createElement('div'); overlay.id = 'biliplus-settings-overlay'; overlay.className = 'biliplus-settings-overlay'; const panel = document.createElement('div'); panel.className = 'biliplus-settings-panel'; const header = document.createElement('div'); header.className = 'biliplus-settings-header'; const title = document.createElement('h2'); title.className = 'biliplus-settings-title'; title.textContent = 'BiliPlus 设置'; const closeBtn = document.createElement('button'); closeBtn.className = 'biliplus-settings-close'; closeBtn.innerHTML = '×'; closeBtn.addEventListener('click', closeSettingsPanel); header.appendChild(title); header.appendChild(closeBtn); // 设置项 const settingsItems = [ { key: 'feed-roll-history-btn', label: '首页"换一换"回溯', desc: '在首页添加前后翻页按钮,可查看之前的推荐内容' }, { key: 'clean-home-page', label: '首页干净模式', desc: '移除首页推荐区的滑动条,优化布局' }, { key: 'hide-hot-search-list', label: '隐藏热搜列表', desc: '隐藏搜索框下方的热搜内容' }, { key: 'stepless-video-rate', label: '无级视频倍速', desc: '支持0.5-5.0倍速的无级调节' } ]; const footer = document.createElement('div'); footer.className = 'biliplus-settings-footer'; footer.textContent = '修改设置后请刷新页面生效'; panel.appendChild(header); // 添加设置项 settingsItems.forEach(item => { const settingItem = document.createElement('div'); settingItem.className = 'biliplus-settings-item'; const labelDiv = document.createElement('div'); const label = document.createElement('div'); label.className = 'biliplus-settings-item-label'; label.textContent = item.label; const desc = document.createElement('div'); desc.className = 'biliplus-settings-item-desc'; desc.textContent = item.desc; labelDiv.appendChild(label); labelDiv.appendChild(desc); const switchBtn = document.createElement('div'); switchBtn.className = 'biliplus-settings-switch' + (getSetting(item.key) ? ' active' : ''); switchBtn.dataset.key = item.key; switchBtn.addEventListener('click', () => { const isActive = switchBtn.classList.contains('active'); switchBtn.classList.toggle('active'); setSetting(item.key, !isActive); }); settingItem.appendChild(labelDiv); settingItem.appendChild(switchBtn); panel.appendChild(settingItem); }); panel.appendChild(footer); overlay.appendChild(panel); document.body.appendChild(overlay); // 点击遮罩关闭 overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeSettingsPanel(); } }); // ESC关闭 const escHandler = (e) => { if (e.key === 'Escape') { closeSettingsPanel(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); } // 关闭设置面板 function closeSettingsPanel() { const overlay = document.getElementById('biliplus-settings-overlay'); if (overlay) { overlay.remove(); } } // 注册油猴菜单命令 if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand('⚙️ BiliPlus 设置', createSettingsPanel); } // 初始化所有功能 function initBiliPlus() { // 首页功能 if (window.location.hostname === 'www.bilibili.com' && window.location.pathname === '/') { initFeedRollHistoryBtn(); initCleanHomePage(); } // 全局功能 initHideHotSearchList(); // 视频页功能 if (window.location.pathname.includes('/video/') || window.location.pathname.includes('/list/') || window.location.pathname.includes('/bangumi/play/')) { initSteplessVideoRate(); } } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initBiliPlus); } else { initBiliPlus(); } // 监听页面变化,处理SPA路由 let lastUrl = location.href; new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; initBiliPlus(); } }).observe(document, { subtree: true, childList: true }); })();