// ==UserScript== // @name 【移动端】bilibili优化 // @namespace https://github.com/WhiteSevs/TamperMonkeyScript // @version 2024.8.3 // @author WhiteSevs // @description 移动端专用,免登录(但登录后可以看更多评论)、阻止跳转App、App端推荐视频流、解锁视频画质(番剧解锁需配合其它插件)、美化显示、去广告等 // @license GPL-3.0-only // @icon https://i0.hdslb.com/bfs/static/jinkela/long/images/512.png // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues // @match *://m.bilibili.com/* // @match *://live.bilibili.com/* // @match *://www.bilibili.com/read/* // @require https://update.greasyfork.icu/scripts/494167/1413255/CoverUMD.js // @require https://update.greasyfork.icu/scripts/497907/1413262/QRCodeJS.js // @require https://fastly.jsdelivr.net/npm/qmsg@1.2.1/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/@whitesev/utils@2.1.0/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.3.0/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/@whitesev/pops@1.5.0/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/md5@2.3.0/dist/md5.min.js // @connect * // @connect m.bilibili.com // @connect www.bilibili.com // @connect api.bilibili.com // @connect app.bilibili.com // @connect passport.bilibili.com // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_getValue // @grant GM_info // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_unregisterMenuCommand // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // @downloadURL none // ==/UserScript== (a=>{function e(n){if(typeof n!="string")throw new TypeError("cssText must be a string");let p=document.createElement("style");return p.setAttribute("type","text/css"),p.innerHTML=n,document.head?document.head.appendChild(p):document.body?document.body.appendChild(p):document.documentElement.childNodes.length===0?document.documentElement.appendChild(p):document.documentElement.insertBefore(p,document.documentElement.childNodes[0]),p}if(typeof GM_addStyle=="function"){GM_addStyle(a);return}e(a)})(' @charset "UTF-8";.m-video2-awaken-btn,.openapp-dialog,.m-head .launch-app-btn.m-nav-openapp,.m-head .launch-app-btn.home-float-openapp,.m-home .launch-app-btn.home-float-openapp,.m-space .launch-app-btn.m-space-float-openapp,.m-space .launch-app-btn.m-nav-openapp{display:none!important}#app .video .launch-app-btn.m-video-main-launchapp:has([class^=m-video2-awaken]),#app .video .launch-app-btn.m-nav-openapp,#app .video .mplayer-widescreen-callapp,#app .video .launch-app-btn.m-float-openapp,#app .video .m-video-season-panel .launch-app-btn .open-app{display:none!important}#app.LIVE .open-app-btn.bili-btn-warp,#app .m-dynamic .launch-app-btn.m-nav-openapp,#app .m-dynamic .dynamic-float-openapp.dynamic-float-btn,#app .m-opus .float-openapp.opus-float-btn,#app .m-opus .v-switcher .launch-app-btn.list-more,#app .m-opus .opus-nav .launch-app-btn.m-nav-openapp,#app .topic-detail .launch-app-btn.m-nav-openapp,#app .topic-detail .launch-app-btn.m-topic-float-openapp{display:none!important}#app.main-container bili-open-app.btn-download{display:none!important}#app .read-app-main bili-open-app{display:none!important} '); (function (Qmsg, Utils, DOMUtils, pops, md5) { 'use strict'; var _a; var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); var _monkeyWindow = /* @__PURE__ */ (() => window)(); const HttpxCookieManager = { $data: { /** 是否启用 */ get enable() { return PopsPanel.getValue("httpx-use-cookie-enable"); }, /** 是否使用document.cookie */ get useDocumentCookie() { return PopsPanel.getValue("httpx-use-document-cookie"); }, cookieRule: [ { key: "httpx-cookie-bilibili.com", hostname: /bilibili.com/g } ] }, /** * 补充cookie末尾分号 */ fixCookieSplit(str) { if (utils.isNotNull(str) && !str.trim().endsWith(";")) { str += ";"; } return str; }, /** * 合并两个cookie */ concatCookie(targetCookie, newCookie) { if (utils.isNull(targetCookie)) { return newCookie; } targetCookie = targetCookie.trim(); newCookie = newCookie.trim(); targetCookie = this.fixCookieSplit(targetCookie); if (newCookie.startsWith(";")) { newCookie = newCookie.substring(1); } return targetCookie.concat(newCookie); }, /** * 处理cookie * @param details * @returns */ handle(details) { if (details.fetch) { return; } if (!this.$data.enable) { return; } let ownCookie = ""; let url = details.url; if (url.startsWith("//")) { url = window.location.protocol + url; } let urlObj = new URL(url); if (this.$data.useDocumentCookie && urlObj.hostname.endsWith( window.location.hostname.split(".").slice(-2).join(".") )) { ownCookie = this.concatCookie(ownCookie, document.cookie.trim()); } for (let index = 0; index < this.$data.cookieRule.length; index++) { let rule = this.$data.cookieRule[index]; if (urlObj.hostname.match(rule.hostname)) { let cookie = PopsPanel.getValue(rule.key); if (utils.isNull(cookie)) { break; } ownCookie = this.concatCookie(ownCookie, cookie); } } if (utils.isNotNull(ownCookie)) { if (details.headers && details.headers["Cookie"]) { details.headers.Cookie = this.concatCookie( details.headers.Cookie, ownCookie ); } else { details.headers["Cookie"] = ownCookie; } log.info(["Httpx => 设置cookie:", details]); } if (details.headers && details.headers.Cookie != null && utils.isNull(details.headers.Cookie)) { delete details.headers.Cookie; } } }; const _SCRIPT_NAME_ = "【移动端】bilibili优化"; const utils = Utils.noConflict(); const domutils = DOMUtils.noConflict(); const __pops = pops; const QRCodeJS = _monkeyWindow.QRCode || _unsafeWindow.QRCode; const log = new utils.Log( _GM_info, _unsafeWindow.console || _monkeyWindow.console ); const SCRIPT_NAME = ((_a = _GM_info == null ? void 0 : _GM_info.script) == null ? void 0 : _a.name) || _SCRIPT_NAME_; const GMCookie = new utils.GM_Cookie(); const DEBUG = false; log.config({ debug: DEBUG, logMaxCount: 1e3, autoClearConsole: true, tag: true }); Qmsg.config( Object.defineProperties( { html: true, autoClose: true, showClose: false }, { position: { get() { return PopsPanel.getValue("qmsg-config-position", "bottom"); } }, maxNums: { get() { return PopsPanel.getValue("qmsg-config-maxnums", 5); } }, showReverse: { get() { return PopsPanel.getValue("qmsg-config-showreverse", true); } }, zIndex: { get() { let maxZIndex = Utils.getMaxZIndex(); let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex(maxZIndex).zIndex; return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100; } } } ) ); const GM_Menu = new utils.GM_Menu({ GM_getValue: _GM_getValue, GM_setValue: _GM_setValue, GM_registerMenuCommand: _GM_registerMenuCommand, GM_unregisterMenuCommand: _GM_unregisterMenuCommand }); const httpx = new utils.Httpx(_GM_xmlhttpRequest); httpx.interceptors.request.use((data2) => { HttpxCookieManager.handle(data2); return data2; }); httpx.interceptors.response.use(void 0, (data2) => { log.error(["拦截器-请求错误", data2]); if (data2.type === "onabort") { Qmsg.warning("请求取消"); } else if (data2.type === "onerror") { Qmsg.error("请求异常"); } else if (data2.type === "ontimeout") { Qmsg.error("请求超时"); } else { Qmsg.error("其它错误"); } return data2; }); httpx.config({ logDetails: DEBUG }); const OriginPrototype = { Object: { defineProperty: _unsafeWindow.Object.defineProperty }, Function: { apply: _unsafeWindow.Function.prototype.apply, call: _unsafeWindow.Function.prototype.call }, Element: { appendChild: _unsafeWindow.Element.prototype.appendChild }, setTimeout: _unsafeWindow.setTimeout }; const addStyle = utils.addStyle.bind(utils); const KEY = "GM_Panel"; const ATTRIBUTE_KEY = "data-key"; const ATTRIBUTE_DEFAULT_VALUE = "data-default-value"; const UISwitch = function(text, key, defaultValue, clickCallBack, description) { let result = { text, type: "switch", description, attributes: {}, getValue() { return Boolean(PopsPanel.getValue(key, defaultValue)); }, callback(event, value) { log.success(`${value ? "开启" : "关闭"} ${text}`); if (typeof clickCallBack === "function") { if (clickCallBack(event, value)) { return; } } PopsPanel.setValue(key, Boolean(value)); }, afterAddToUListCallBack: void 0 }; if (result.attributes) { result.attributes[ATTRIBUTE_KEY] = key; result.attributes[ATTRIBUTE_DEFAULT_VALUE] = Boolean(defaultValue); } return result; }; const UITextArea = function(text, key, defaultValue, description, changeCallBack, placeholder = "", disabled) { let result = { text, type: "textarea", attributes: {}, description, placeholder, disabled, getValue() { let localValue = PopsPanel.getValue(key, defaultValue); return localValue; }, callback(event, value) { PopsPanel.setValue(key, value); } }; if (result.attributes) { result.attributes[ATTRIBUTE_KEY] = key; result.attributes[ATTRIBUTE_DEFAULT_VALUE] = defaultValue; } return result; }; const UISelect = function(text, key, defaultValue, data2, callback, description) { let selectData = []; if (typeof data2 === "function") { selectData = data2(); } else { selectData = data2; } let result = { text, type: "select", description, attributes: {}, getValue() { return PopsPanel.getValue(key, defaultValue); }, callback(event, isSelectedValue, isSelectedText) { PopsPanel.setValue(key, isSelectedValue); if (typeof callback === "function") { callback(event, isSelectedValue, isSelectedText); } }, data: selectData }; if (result.attributes) { result.attributes[ATTRIBUTE_KEY] = key; result.attributes[ATTRIBUTE_DEFAULT_VALUE] = defaultValue; } return result; }; const UISlider = function(text, key, defaultValue, min, max, step, changeCallBack, getToolTipContent, description) { let result = { text, type: "slider", description, attributes: {}, getValue() { return PopsPanel.getValue(key, defaultValue); }, getToolTipContent(value) { if (typeof getToolTipContent === "function") { return getToolTipContent(value); } else { return `${value}`; } }, callback(event, value) { if (typeof changeCallBack === "function") { if (changeCallBack(event, value)) { return; } } PopsPanel.setValue(key, value); }, min, max, step }; if (result.attributes) { result.attributes[ATTRIBUTE_KEY] = key; result.attributes[ATTRIBUTE_DEFAULT_VALUE] = defaultValue; } return result; }; const BilibiliPlayer = { get player() { return _unsafeWindow.player; }, init() { BilibiliDanmaku.init(); let videoSpeed = PopsPanel.getValue("bili-video-speed"); this.setVideoSpeed(videoSpeed); }, async playerPromise() { await utils.waitPropertyByInterval( _unsafeWindow, () => { var _a2, _b; return typeof BilibiliPlayer.player === "object" && typeof ((_a2 = BilibiliPlayer.player) == null ? void 0 : _a2.playerPromise) === "object" && ((_b = BilibiliPlayer.player) == null ? void 0 : _b.playerPromise) != null; }, 250, 1e4 ); let playerPromise = await BilibiliPlayer.player.playerPromise; return playerPromise; }, /** * 设置视频播放倍速 * @param value 倍速值 */ setVideoSpeed(value) { this.playerPromise().then(async (playerPromise) => { await utils.waitPropertyByInterval( async () => { playerPromise = await BilibiliPlayer.playerPromise(); return playerPromise; }, () => { return typeof playerPromise.video != null && playerPromise.video instanceof HTMLVideoElement; }, 250, 1e4 ); playerPromise.video.playbackRate = value; log.success(`设置视频播放倍速: ${value}`); BilibiliDanmaku.DanmakuCoreConfig().then(async (config) => { config.videoSpeed = value; log.success(`设置弹幕配置的视频播放倍速: ${value}`); }); }); } }; const BilibiliDanmakuFilter = { key: "bili-danmaku-filter", /** 弹幕类型 */ mode: { 6: "从左往右", 5: "顶部", 4: "底部", 1: "从右往左" }, $player: { async danmakuArray() { var _a2, _b; await utils.waitPropertyByInterval( _unsafeWindow, () => { var _a3; return typeof BilibiliPlayer.player === "object" && typeof ((_a3 = BilibiliPlayer.player) == null ? void 0 : _a3.playerPromise) === "object"; }, 250, 1e4 ); let playerPromise = await BilibiliPlayer.playerPromise(); await utils.waitPropertyByInterval( async () => { playerPromise = await BilibiliPlayer.playerPromise(); }, () => { var _a3, _b2, _c, _d, _e, _f; return typeof ((_b2 = (_a3 = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _a3.danmakuCore) == null ? void 0 : _b2.danmakuArray) === "object" && ((_d = (_c = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _c.danmakuCore) == null ? void 0 : _d.danmakuArray) != null && Array.isArray((_f = (_e = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _e.danmakuCore) == null ? void 0 : _f.danmakuArray); }, 250, 1e4 ); let danmakuArray = (_b = (_a2 = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _a2.danmakuCore) == null ? void 0 : _b.danmakuArray; return danmakuArray; }, async danmakuFilter() { var _a2, _b, _c; await utils.waitPropertyByInterval( _unsafeWindow, () => { var _a3; return typeof BilibiliPlayer.player === "object" && typeof ((_a3 = BilibiliPlayer.player) == null ? void 0 : _a3.playerPromise) === "object"; }, 250, 1e4 ); let playerPromise = await BilibiliPlayer.playerPromise(); await utils.waitPropertyByInterval( async () => { playerPromise = await BilibiliPlayer.playerPromise(); }, () => { var _a3, _b2, _c2; return typeof ((_c2 = (_b2 = (_a3 = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _a3.danmakuCore) == null ? void 0 : _b2.config) == null ? void 0 : _c2.danmakuFilter) === "function"; }, 250, 1e4 ); let danmakuFilter = (_c = (_b = (_a2 = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _a2.danmakuCore) == null ? void 0 : _b.config) == null ? void 0 : _c.danmakuFilter; return danmakuFilter; } }, $data: { danmakuArray: [] }, $fn: { updateDanmakuArray: new utils.LockFunction(async () => { BilibiliDanmakuFilter.$data.danmakuArray = await BilibiliDanmakuFilter.$player.danmakuArray(); }, 250) }, async init() { let totalRule = this.parseRule(); let danmakuFilter = await this.$player.danmakuFilter(); let that = this; if (typeof danmakuFilter == "function") { let playerPromise = await BilibiliPlayer.playerPromise(); playerPromise.danmaku.danmakuCore.config.danmakuFilter = function(danmaConfig) { let isFilter = that.filter(danmaConfig, totalRule); return isFilter; }; } }, /** 更新弹幕列表 */ updateDanmakuArray() { this.$fn.updateDanmakuArray.run(); }, /** * 判断是否需要过滤 * @param danmaConfig * @param totalRule * @param danmakuArray * @returns */ filter(danmaConfig, totalRule) { this.updateDanmakuArray(); let filterFlag = false; if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter-type-roll")) { if (danmaConfig.mode === 1 || danmaConfig.mode === 6) { filterFlag = true; } } } if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter-type-top")) { if (danmaConfig.mode === 5 || danmaConfig.mode === 1 || danmaConfig.mode === 6) { filterFlag = true; } } } if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter-type-bottom")) { if (danmaConfig.mode === 4) { filterFlag = true; } } } if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter-type-colour")) { if (danmaConfig.color !== 16777215) { filterFlag = true; } } } if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter-type-repeat")) { let findIndex = this.$data.danmakuArray.findIndex( (__danmaConfig__, __index__) => { return danmaConfig.text === __danmaConfig__.text && danmaConfig != __danmaConfig__; } ); if (findIndex != -1) { filterFlag = true; console.log("重复:" + findIndex); } } } if (!filterFlag) { if (PopsPanel.getValue("bili-danmaku-filter")) { for (let ruleIndex = 0; ruleIndex < totalRule.length; ruleIndex++) { const rule = totalRule[ruleIndex]; if (typeof danmaConfig.text === "string" && danmaConfig.text.match(rule)) { filterFlag = true; break; } } } } return filterFlag; }, parseRule() { let localRule = this.getValue(); let rule = []; localRule.split("\n").forEach((ruleItemStr) => { let ruleItem = ruleItemStr.trim(); let regExpRule = new RegExp( utils.parseStringToRegExpString(ruleItem), "ig" ); rule.push(regExpRule); }); return rule; }, getValue() { return _GM_getValue(this.key, ""); }, setValue(value = "") { _GM_setValue(this.key, value); } }; const BilibiliDanmaku = { /** 弹幕字体 */ fontFamily: [ { text: "黑体", value: "SimHei, 'Microsoft JhengHei'" }, { text: "宋体", value: "SimSun" }, { text: "新宋体", value: "NSimSun" }, { text: "仿宋", value: "FangSong" }, { text: "微软雅黑", value: "'Microsoft YaHei'" }, { text: "微软雅黑 Light", value: "'Microsoft Yahei UI Light'" }, { text: "Noto Sans DemiLight", value: "'Noto Sans CJK SC DemiLight'" }, { text: "'Noto Sans CJK SC Regular'", value: "'Noto Sans CJK SC Regular'" } ], init() { BilibiliDanmakuFilter.init(); let opacity = PopsPanel.getValue("bili-danmaku-opacity"); let area = PopsPanel.getValue("bili-danmaku-area"); let fontSize = PopsPanel.getValue("bili-danmaku-fontSize"); let duration = PopsPanel.getValue("bili-danmaku-duration"); let bold = PopsPanel.getValue("bili-danmaku-bold"); let fullScreenSync = PopsPanel.getValue( "bili-danmaku-fullScreenSync" ); let speedSync = PopsPanel.getValue("bili-danmaku-speedSync"); let fontFamily = PopsPanel.getValue("bili-danmaku-fontFamily"); this.setOpacity(opacity); this.setArea(area); this.setFontSize(fontSize); this.setDuration(duration); this.setBold(bold); this.setFullScreenSync(fullScreenSync); this.setSpeedSync(speedSync); this.setFontFamily(fontFamily); }, async DanmakuCoreConfig() { let playerPromise = await BilibiliPlayer.playerPromise(); await utils.waitPropertyByInterval( playerPromise, () => { var _a2, _b, _c, _d; return typeof ((_b = (_a2 = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _a2.danmakuCore) == null ? void 0 : _b.config) === "object" && ((_d = (_c = playerPromise == null ? void 0 : playerPromise.danmaku) == null ? void 0 : _c.danmakuCore) == null ? void 0 : _d.config) != null; }, 250, 1e4 ); return playerPromise.danmaku.danmakuCore.config; }, /** * 设置 不透明度 * @param value */ setOpacity(value) { this.DanmakuCoreConfig().then((config) => { config.opacity = value; log.success(`设置-弹幕不透明度: ${value}`); }); }, /** * 设置 显示区域 * @param value */ setArea(value) { let areaMapping = { 25: "1/4屏", 50: "半屏", 75: "3/4屏", 100: "全屏" }; this.DanmakuCoreConfig().then((config) => { config.danmakuArea = value; log.success(`设置-显示区域: ${value} => ${areaMapping[value]}`); }); }, /** * 设置 字体大小 * @param value */ setFontSize(value) { this.DanmakuCoreConfig().then((config) => { config.fontSize = value; log.success(`设置-字体大小: ${value}`); }); }, /** * 设置 持续时间(弹幕速度) * @param value */ setDuration(value) { this.DanmakuCoreConfig().then((config) => { config.duration = value; log.success(`设置-持续时间(弹幕速度): ${value}`); }); }, /** * 设置 粗体 * @param value */ setBold(value) { this.DanmakuCoreConfig().then((config) => { config.bold = value; log.success(`设置-粗体: ${value}`); }); }, /** * 弹幕随屏幕缩放 * @param value */ setFullScreenSync(value) { this.DanmakuCoreConfig().then((config) => { config.fullScreenSync = value; log.success(`设置-弹幕随屏幕缩放: ${value}`); }); }, /** * 弹幕速度同步播放倍数 * @param value */ setSpeedSync(value) { this.DanmakuCoreConfig().then(async (config) => { let playerPromise = await BilibiliPlayer.playerPromise(); await utils.waitPropertyByInterval( async () => { playerPromise = await BilibiliPlayer.playerPromise(); return playerPromise; }, () => { return typeof playerPromise.video === "object" && playerPromise.video != null && playerPromise.video instanceof HTMLVideoElement; }, 250, 1e4 ); let videoSpeed = playerPromise.video.playbackRate; config.videoSpeed = videoSpeed; config.speedSync = value; log.success(`设置-当前视频播放倍速: ${videoSpeed}`); log.success(`设置-弹幕速度同步播放倍数: ${value}`); }); }, /** * 弹幕字体 * @param value */ setFontFamily(value) { this.DanmakuCoreConfig().then((config) => { config.fontFamily = value; log.success(`设置-弹幕字体: ${value}`); }); } }; const SettingUICommon = { id: "panel-common", title: "通用", forms: [ { text: "", type: "forms", forms: [ { text: "功能", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "监听路由-重载所有功能", "bili-listenRouterChange", true, void 0, "用于处理页面跳转(本页)时功能不生效问题" ), UISwitch( "修复点击UP主正确进入空间", "bili-repairEnterUserHome", true, void 0, "可以修复点击UP主进入个人空间但是跳转404的问题" ), UISwitch( "新标签页打开", "bili-go-to-url-blank", false, void 0, "通过开启【覆盖点击事件】相关的设置,通过新标签页打开链接" ), UISelect( "倍速", "bili-video-speed", 1, [ { text: "2.0X", value: 2 }, { text: "1.5X", value: 1.5 }, { text: "1.25X", value: 1.25 }, { text: "1.0X", value: 1 }, { text: "0.75X", value: 0.75 }, { text: "0.25X", value: 0.25 } ], (_, isSelectValue) => { BilibiliPlayer.setVideoSpeed(isSelectValue); } ) ] } ] }, { type: "deepMenu", text: "弹幕", forms: [ { text: "弹幕设置", type: "forms", forms: [ UISlider( "不透明度", "bili-danmaku-opacity", 0.75, 0.2, 1, 0.01, (event, value) => { BilibiliDanmaku.setOpacity(value); }, (value) => { return `${parseInt((value * 100).toString())}%`; } ), UISelect( "显示区域", "bili-danmaku-area", 25, [ { text: "1/4屏", value: 25 }, { text: "半屏", value: 50 }, { text: "3/4屏", value: 75 }, { text: "全屏", value: 100 } ], (event, isSelectValue, isSelectText) => { BilibiliDanmaku.setArea(isSelectValue); } ), UISlider( "字体大小", "bili-danmaku-fontSize", 0.7, 0.2, 2, 0.1, (event, value) => { BilibiliDanmaku.setFontSize(value); }, (value) => { return `${parseInt((value * 100).toString())}%`; } ), UISelect( "弹幕速度", "bili-danmaku-duration", 6, [ { text: "极慢", value: 10 }, { text: "较慢", value: 8 }, { text: "适中", value: 6 }, { text: "较快", value: 4 }, { text: "极快", value: 2 } ], (event, isSelectValue, isSelectText) => { BilibiliDanmaku.setDuration(isSelectValue); } ), UISwitch( "弹幕随屏幕缩放", "bili-danmaku-fullScreenSync", false, (event, value) => { BilibiliDanmaku.setFullScreenSync(value); } ), UISwitch( "弹幕速度同步播放倍数", "bili-danmaku-speedSync", true, (event, value) => { BilibiliDanmaku.setSpeedSync(value); } ) ] }, { type: "forms", text: "", forms: [ UISelect( "弹幕字体", "bili-danmaku-fontFamily", (() => { let findItem = BilibiliDanmaku.fontFamily.find( (item) => item.text === "黑体" ); return findItem.value; })(), BilibiliDanmaku.fontFamily, (event, isSelectValue, isSelectText) => { BilibiliDanmaku.setFontFamily(isSelectValue); } ), UISwitch("粗体", "bili-danmaku-bold", true, (event, value) => { BilibiliDanmaku.setBold(value); }) ] }, { text: "按类型屏蔽", type: "forms", forms: [ UISwitch("滚动", "bili-danmaku-filter-type-roll", false), UISwitch("顶部", "bili-danmaku-filter-type-top", false), UISwitch("底部", "bili-danmaku-filter-type-bottom", false), UISwitch("彩色", "bili-danmaku-filter-type-colour", false), UISwitch("重复", "bili-danmaku-filter-type-repeat", false), // UISwitch("高级", "bili-danmaku-filter-type-senior", false), UISwitch( "屏蔽词", "bili-danmaku-filter", false, void 0, "开启后可使用↓自定义的规则过滤弹幕" ), { type: "own", getLiElementCallBack(liElement) { let textareaDiv = domutils.createElement( "div", { className: "pops-panel-textarea", innerHTML: ` ` }, { style: "width: 100%;" } ); let $textarea = textareaDiv.querySelector( "textarea" ); $textarea.value = BilibiliDanmakuFilter.getValue(); domutils.on( $textarea, ["input", "propertychange"], void 0, utils.debounce(function(event) { BilibiliDanmakuFilter.setValue($textarea.value); }, 200) ); liElement.appendChild(textareaDiv); return liElement; } } ] } ] }, { text: "变量设置", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "isLogin", "bili-setLogin", true, void 0, "$store.state.common.noCallApp=true
$store.state.common.userInfo.isLogin=true
$store.state.loginInfo.isLogin=true" ), UISwitch( "isClient", "bili-setIsClient", true, void 0, "$store.state.video.isClient=true
$store.state.opus.isClient=true
$store.state.playlist.isClient=true
$store.state.ver.bili=true
$store.state.ver.biliVer=2333" ), UISwitch( "tinyApp", "bili-setTinyApp", true, void 0, "$store.state.common.tinyApp=true" ) ] } ] }, { text: "劫持/拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "覆盖.launch-app-btn openApp", "bili-overrideLaunchAppBtn_Vue_openApp", true, void 0, "覆盖.launch-app-btn元素上的openApp函数,可阻止点击唤醒/下载App" ), UISwitch( "劫持setTimeout-autoOpenApp", "bili-hookSetTimeout_autoOpenApp", true, void 0, "阻止自动调用App" ) ] } ] } ] }, { text: "", type: "forms", forms: [ { text: "Toast配置", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISelect( "Toast位置", "qmsg-config-position", "bottom", [ { value: "topleft", text: "左上角" }, { value: "top", text: "顶部" }, { value: "topright", text: "右上角" }, { value: "left", text: "左边" }, { value: "center", text: "中间" }, { value: "right", text: "右边" }, { value: "bottomleft", text: "左下角" }, { value: "bottom", text: "底部" }, { value: "bottomright", text: "右下角" } ], (event, isSelectValue, isSelectText) => { log.info("设置当前Qmsg弹出位置" + isSelectText); }, "Toast显示在页面九宫格的位置" ), UISelect( "最多显示的数量", "qmsg-config-maxnums", 3, [ { value: 1, text: "1" }, { value: 2, text: "2" }, { value: 3, text: "3" }, { value: 4, text: "4" }, { value: 5, text: "5" } ], void 0, "限制Toast显示的数量" ), UISwitch( "逆序弹出", "qmsg-config-showreverse", false, void 0, "修改Toast弹出的顺序" ) ] } ] }, { text: "Cookie配置", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "启用", "httpx-use-cookie-enable", false, void 0, "启用后,将根据下面的配置进行添加cookie" ), UISwitch( "使用document.cookie", "httpx-use-document-cookie", false, void 0, "自动根据请求的域名来获取对应的cookie" ), UITextArea( "bilibili.com", "httpx-cookie-bilibili.com", "", void 0, void 0, "Cookie格式:xxx=xxxx;xxx=xxxx" ) ] } ] } ] } ] }; const BilibiliRouter = { /** * 判断当前路径 * + /video/ */ isVideo() { return window.location.pathname.startsWith("/video/"); }, /** * 判断当前路径 * + /banggumi/ */ isBangumi() { return window.location.pathname.startsWith("/bangumi/"); }, /** * 判断当前路径 * + /search */ isSearch() { return window.location.pathname.startsWith("/search"); }, /** * 判断当前路径 * + live.bilibili.com */ isLive() { return window.location.hostname === "live.bilibili.com"; }, /** * 判断当前路径 * + /opus */ isOpus() { return window.location.pathname.startsWith("/opus"); }, /** * 判断当前路径 * + /topic-detail */ isTopicDetail() { return window.location.pathname.startsWith("/topic-detail"); }, /** * 判断当前路径 * + /dynamic */ isDynamic() { return window.location.pathname.startsWith("/dynamic"); }, /** * 判断当前路径 * + / * + /channel */ isHead() { return window.location.pathname === "/" || window.location.pathname.startsWith("/channel"); } }; const BilibiliPCRouter = { /** * 桌面端 */ isPC() { return window.location.hostname === "www.bilibili.com"; }, /** * 应该是动态? */ isReadMobile() { return this.isPC() && window.location.pathname.startsWith("/read/mobile"); } }; const SettingUIVideo = { id: "panel-video", title: "视频", isDefault() { return BilibiliRouter.isVideo(); }, forms: [ { text: "", type: "forms", forms: [ { text: "功能", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "修复视频底部区域高度", "bili-video-repairVideoBottomAreaHeight", true, void 0, "添加margin-top" ), UISwitch( "自动点击【继续在网页观看】", "bili-video-autoClickContinueToWatchOnTheWebpage", true, void 0, "可避免弹窗出现且自动点击后播放视频" ), UISwitch( "美化显示", "bili-video-beautify", true, void 0, "调整底部推荐视频卡片样式类似哔哩哔哩App" ), UISwitch( "手势返回关闭评论区", "bili-video-gestureReturnToCloseCommentArea", true, void 0, "当浏览器手势触发浏览器回退页面时,关闭评论区" ), UISwitch( "initPlayer", "bili-video-initPlayer", true, void 0, "自动执行初始化播放器" ), UISwitch( "强制本页刷新跳转", "bili-video-forceThisPageToRefreshAndRedirect", false, void 0, "用于解决跳转播放视频时,播放当前视频会有上一个播放视频的声音的情况" ) ] } ] }, { text: "变量设置", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "playBtnNoOpenApp", "bili-video-setVideoPlayer", true, void 0, "playBtnNoOpenApp=true
playBtnOpenApp=false
coverOpenApp=false" ), UISwitch( "解锁充电限制", "bili-video-unlockUpower", false, void 0, "is_upower_exclusive=true
is_upower_play=false
is_upower_preview=false" ) ] } ] }, { text: "覆盖点击事件", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "相关视频", "bili-video-cover-bottomRecommendVideo", true, void 0, "点击下面的相关视频可正确跳转至该视频" ), UISwitch( "选集", "bili-video-cover-seasonNew", true, void 0, "点击下面的选集列表内的视频可正确跳转至该视频" ) ] } ] }, { text: "网络拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "解锁清晰度", "bili-video-xhr-unlockQuality", true, void 0, "最高清晰度为720P" ) ] } ] }, { text: "劫持/拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "阻止调用App", "bili-video-hook-callApp", true, void 0, "处理函数: PlayerAgent" ) ] } ] } ] } ] }; const SettingUIBangumi = { id: "panel-bangumi", title: "番剧", isDefault() { return BilibiliRouter.isBangumi(); }, forms: [ { text: "", type: "forms", forms: [ { text: "变量设置", type: "deepMenu", forms: [ { text: "变量设置", type: "forms", forms: [ UISwitch( "pay", "bili-bangumi-setPay", true, void 0, "$store.state.userStat.pay=1
$store.state.mediaInfo.user_status.pay=1" ) ] } ] }, { text: "覆盖点击事件", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "【选集】", "bili-bangumi-cover-clicl-event-chooseEp", true, void 0, "让【选集】的视频列表可点击跳转" ), UISwitch( "【其它】", "bili-bangumi-cover-clicl-event-other", true, void 0, "让【PV&其他】、【预告】、【主题曲】、【香境剧场】等的视频列表可点击跳转" ), UISwitch( "【更多推荐】", "bili-bangumi-cover-clicl-event-recommend", true, void 0, "让【更多推荐】的视频列表可点击跳转" ) ] } ] }, { text: "网络拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "解锁清晰度", "bili-bangumi-xhr-unlockQuality", true, void 0, "最高清晰度为720P" ) ] } ] }, { text: "劫持/拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "阻止调用App", "bili-bangumi-hook-callApp", true, void 0, "" ) ] } ] } ] } ] }; const SettingUISearch = { id: "panel-search", title: "搜索", isDefault() { return BilibiliRouter.isSearch(); }, forms: [ { type: "forms", text: "", forms: [ { type: "deepMenu", text: "覆盖点击事件", forms: [ { type: "forms", text: "", forms: [ UISwitch( "取消", "bili-search-cover-cancel", false, void 0, "点击取消按钮回退至上一页" ) ] } ] } ] } ] }; const SettingUILive = { id: "panel-live", title: "直播", isDefault() { return BilibiliRouter.isLive(); }, forms: [ { text: "", type: "forms", forms: [ { text: "屏蔽", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "【屏蔽】聊天室", "bili-live-block-chatRoom", false, void 0, "直接不显示底部的聊天室" ), UISwitch( "【屏蔽】xxx进入直播间", "bili-live-block-brush-prompt", false, void 0, "直接不显示底部的xxx进入直播间" ), UISwitch( "【屏蔽】控制面板", "bili-live-block-control-panel", false, void 0, "屏蔽底部的发个弹幕、送礼" ) ] } ] }, { text: "劫持/拦截", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "阻止open-app-btn元素点击事件触发", "bili-live-prevent-openAppBtn", true, void 0, "开启后可不跳转至唤醒App页面" ) ] } ] } ] } ] }; const SettingUIOpus = { id: "panel-opus", title: "专栏", isDefault() { return BilibiliRouter.isOpus(); }, forms: [ { text: "", type: "forms", forms: [ { text: "功能", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "自动展开阅读全文", "bili-opus-automaticallyExpandToReadFullText", true, void 0, "屏蔽【展开阅读全文】按钮并自动处理全文高度" ) ] } ] }, { text: "覆盖点击事件", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "话题", "bili-opus-cover-topicJump", true, void 0, "点击话题正确跳转" ), UISwitch( "header用户", "bili-opus-cover-header", true, void 0, "点击内容上的发布本动态的用户正确跳转个人空间" ) ] } ] } ] } ] }; const SettingUIDynamic = { id: "panel-dynamic", title: "动态", isDefault() { return BilibiliRouter.isDynamic(); }, forms: [ { text: "", type: "forms", forms: [ { text: "覆盖点击事件", type: "deepMenu", forms: [ { text: "", type: "forms", forms: [ UISwitch( "话题", "bili-dynamic-cover-topicJump", true, void 0, "点击话题正确跳转" ), UISwitch( "header用户", "bili-dynamic-cover-header", true, void 0, "点击内容上的发布本动态的用户正确跳转个人空间" ), UISwitch( "@用户", "bili-dynamic-cover-atJump", true, void 0, "点击@用户正确跳转个人空间" ), UISwitch( "引用", "bili-dynamic-cover-referenceJump", true, void 0, "点击引用的视频|用户正确跳转" ) ] } ] } ] } ] }; const TVKeyInfo = { appkey: "4409e2ce8ffd12b8", appsec: "59b43e04ad6965f34319062b478f83dd" }; function appSign(params, appkey, appsec) { params.appkey = appkey; const searchParams = new URLSearchParams(params); searchParams.sort(); return md5(searchParams.toString() + appsec); } const BilibiliUtils = { /** * 获取元素上的__vue__属性 * @param element * @returns */ getVue(element) { return element == null ? void 0 : element.__vue__; }, /** * 等待vue属性并进行设置 */ waitVuePropToSet($target, needSetList) { function getTarget() { let __target__ = null; if (typeof $target === "string") { __target__ = document.querySelector($target); } else if (typeof $target === "function") { __target__ = $target(); } else if ($target instanceof HTMLElement) { __target__ = $target; } return __target__; } needSetList.forEach((needSetOption) => { if (typeof needSetOption.msg === "string") { log.info(needSetOption.msg); } function checkVue() { let target = getTarget(); if (target == null) { return false; } let vueObj = BilibiliUtils.getVue(target); if (vueObj == null) { return false; } let needOwnCheck = needSetOption.check(vueObj); return Boolean(needOwnCheck); } utils.waitVueByInterval( () => { return getTarget(); }, checkVue, 250, 1e4 ).then((result) => { if (!result) { return; } let target = getTarget(); let vueObj = BilibiliUtils.getVue(target); if (vueObj == null) { return; } needSetOption.set(vueObj); }); }); }, /** * 前往网址 * @param path * @param [useRouter=false] 是否强制使用Router */ goToUrl(path, useRouter = false) { let $app = document.querySelector("#app"); if ($app == null) { Qmsg.error("跳转Url: 获取根元素#app失败"); log.error("跳转Url: 获取根元素#app失败:" + path); return; } let vueObj = BilibiliUtils.getVue($app); if (vueObj == null) { log.error("获取#app的vue属性失败"); Qmsg.error("获取#app的vue属性失败"); return; } let $router = vueObj.$router; let isGoToUrlBlank = PopsPanel.getValue("bili-go-to-url-blank"); log.info("即将跳转URL:" + path); if (useRouter) { isGoToUrlBlank = false; } if (isGoToUrlBlank) { window.open(path, "_blank"); } else { if (path.startsWith("http") || path.startsWith("//")) { if (path.startsWith("//")) { path = window.location.protocol + path; } let urlObj = new URL(path); if (urlObj.origin === window.location.origin) { path = urlObj.pathname + urlObj.search + urlObj.hash; } else { log.info("不同域名,直接本页打开,不用Router:" + path); window.location.href = path; return; } } log.info("$router push跳转Url:" + path); $router.push(path); } }, /** * 前往登录 */ goToLogin(fromUrl = "") { window.open( `https://passport.bilibili.com/h5-app/passport/login?gourl=${encodeURIComponent( fromUrl )}` ); }, /** * 转换时长为显示的时长 * * + 30 => 0:30 * + 120 => 2:00 * + 14400 => 4:00:00 * @param duration 秒 */ parseDuration(duration) { if (typeof duration !== "number") { duration = parseInt(duration); } if (isNaN(duration)) { return duration.toString(); } function zeroPadding(num) { if (num < 10) { return `0${num}`; } else { return num; } } if (duration < 60) { return `0:${zeroPadding(duration)}`; } else if (duration >= 60 && duration < 3600) { return `${Math.floor(duration / 60)}:${zeroPadding(duration % 60)}`; } else { return `${Math.floor(duration / 3600)}:${zeroPadding( Math.floor(duration / 60) % 60 )}:${zeroPadding(duration % 60)}`; } }, /** * 手势返回 */ hookGestureReturnByVueRouter(option) { function popstateEvent() { log.success("触发popstate事件"); resumeBack(true); } function banBack() { log.success("监听地址改变"); option.vueObj.$router.history.push(option.hash); domutils.on(window, "popstate", popstateEvent); } async function resumeBack(isFromPopState = false) { domutils.off(window, "popstate", popstateEvent); let callbackResult = option.callback(isFromPopState); if (callbackResult) { return; } while (1) { if (option.vueObj.$router.history.current.hash === option.hash) { log.info("后退!"); option.vueObj.$router.back(); await utils.sleep(250); } else { return; } } } banBack(); return { resumeBack }; }, /** * 加载