Make BiliBili Great Again

useful tweaks for bilibili.com

目前为 2024-12-26 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Make BiliBili Great Again
  3. // @namespace https://www.kookxiang.com/
  4. // @version 1.5.10
  5. // @description useful tweaks for bilibili.com
  6. // @author kookxiang
  7. // @match https://*.bilibili.com/*
  8. // @run-at document-body
  9. // @grant unsafeWindow
  10. // @grant GM_addStyle
  11. // @grant GM_notification
  12. // ==/UserScript==
  13.  
  14. // 去掉叔叔去世时的全站黑白效果
  15. GM_addStyle("html, body { -webkit-filter: none !important; filter: none !important; }");
  16.  
  17. // 没用的 URL 参数
  18. const uselessUrlParams = [
  19. 'buvid',
  20. 'is_story_h5',
  21. 'launch_id',
  22. 'live_from',
  23. 'mid',
  24. 'session_id',
  25. 'timestamp',
  26. 'up_id',
  27. 'vd_source',
  28. /^share/,
  29. /^spm/,
  30. ];
  31.  
  32. // Block WebRTC,CNM 陈睿你就缺这点棺材钱?
  33. try {
  34. class _RTCPeerConnection {
  35. addEventListener() { }
  36. createDataChannel() { }
  37. }
  38. class _RTCDataChannel { }
  39. Object.defineProperty(unsafeWindow, 'RTCPeerConnection', { value: _RTCPeerConnection, enumerable: false, writable: false });
  40. Object.defineProperty(unsafeWindow, 'RTCDataChannel', { value: _RTCDataChannel, enumerable: false, writable: false });
  41. Object.defineProperty(unsafeWindow, 'webkitRTCPeerConnection', { value: _RTCPeerConnection, enumerable: false, writable: false });
  42. Object.defineProperty(unsafeWindow, 'webkitRTCDataChannel', { value: _RTCDataChannel, enumerable: false, writable: false });
  43. } catch (e) { }
  44.  
  45. // 移除鸿蒙字体,系统自带它不香吗?
  46. Array.from(document.querySelectorAll('link[href*=\\/jinkela\\/long\\/font\\/]')).forEach(x => x.remove());
  47. GM_addStyle("html, body { font-family: initial !important; }");
  48.  
  49. // 首页优化
  50. if (location.host === "www.bilibili.com") {
  51. GM_addStyle('.feed2 .feed-card:has(a[href*="cm.bilibili.com"]), .feed2 .feed-card:has(.bili-video-card:empty) { display: none } .feed2 .container > * { margin-top: 0 !important }');
  52. }
  53.  
  54. // 动态页面优化
  55. if (location.host === "t.bilibili.com") {
  56. GM_addStyle("html[wide] #app { display: flex; } html[wide] .bili-dyn-home--member { box-sizing: border-box;padding: 0 10px;width: 100%;flex: 1; } html[wide] .bili-dyn-content { width: initial; } html[wide] main { margin: 0 8px;flex: 1;overflow: hidden;width: initial; } #wide-mode-switch { margin-left: 0;margin-right: 20px; } .bili-dyn-list__item:has(.bili-dyn-card-goods), .bili-dyn-list__item:has(.bili-rich-text-module.goods) { display: none !important }");
  57. if (!localStorage.WIDE_OPT_OUT) {
  58. document.documentElement.setAttribute('wide', 'wide');
  59. }
  60. window.addEventListener('load', function () {
  61. const tabContainer = document.querySelector('.bili-dyn-list-tabs__list');
  62. const placeHolder = document.createElement('div');
  63. placeHolder.style.flex = 1;
  64. const switchButton = document.createElement('a');
  65. switchButton.id = 'wide-mode-switch';
  66. switchButton.className = 'bili-dyn-list-tabs__item';
  67. switchButton.textContent = '宽屏模式';
  68. switchButton.addEventListener('click', function (e) {
  69. e.preventDefault();
  70. if (localStorage.WIDE_OPT_OUT) {
  71. localStorage.removeItem('WIDE_OPT_OUT');
  72. document.documentElement.setAttribute('wide', 'wide');
  73. } else {
  74. localStorage.setItem('WIDE_OPT_OUT', '1');
  75. document.documentElement.removeAttribute('wide');
  76. }
  77. })
  78. tabContainer.appendChild(placeHolder);
  79. tabContainer.appendChild(switchButton);
  80. })
  81. }
  82.  
  83. // 去广告
  84. GM_addStyle('.ad-report, a[href*="cm.bilibili.com"] { display: none !important; }');
  85. if (unsafeWindow.__INITIAL_STATE__?.adData) {
  86. for (const key in unsafeWindow.__INITIAL_STATE__.adData) {
  87. if (!Array.isArray(unsafeWindow.__INITIAL_STATE__.adData[key])) continue;
  88. for (const item of unsafeWindow.__INITIAL_STATE__.adData[key]) {
  89. item.name = 'B 站未来有可能会倒闭,但绝不会变质';
  90. item.pic = 'https://static.hdslb.com/images/transparent.gif';
  91. item.url = 'https://space.bilibili.com/208259';
  92. }
  93. }
  94. }
  95.  
  96. // 去充电列表(叔叔的跳过按钮越做越小了,就尼玛离谱)
  97. if (unsafeWindow.__INITIAL_STATE__?.elecFullInfo) {
  98. unsafeWindow.__INITIAL_STATE__.elecFullInfo.list = [];
  99. }
  100.  
  101. // 修复文章区复制
  102. if (location.href.startsWith('https://www.bilibili.com/read/cv')) {
  103. unsafeWindow.original.reprint = "1";
  104. document.querySelector('.article-holder').classList.remove("unable-reprint");
  105. document.querySelector('.article-holder').addEventListener('copy', e => e.stopImmediatePropagation(), true);
  106. }
  107.  
  108. // 去 P2P CDN
  109. Object.defineProperty(unsafeWindow, 'PCDNLoader', { value: class { }, enumerable: false, writable: false });
  110. Object.defineProperty(unsafeWindow, 'BPP2PSDK', { value: class { on() { } }, enumerable: false, writable: false });
  111. Object.defineProperty(unsafeWindow, 'SeederSDK', { value: class { }, enumerable: false, writable: false });
  112. if (location.href.startsWith('https://www.bilibili.com/video/') || location.href.startsWith('https://www.bilibili.com/bangumi/play/')) {
  113. let cdnDomain;
  114.  
  115. function replaceP2PUrl(url) {
  116. cdnDomain ||= document.head.innerHTML.match(/up[\w-]+\.bilivideo\.com/)?.[0];
  117.  
  118. try {
  119. const urlObj = new URL(url);
  120. const hostName = urlObj.hostname;
  121. if (urlObj.hostname.endsWith(".mcdn.bilivideo.cn")) {
  122. urlObj.host = cdnDomain || 'upos-sz-mirrorcoso1.bilivideo.com';
  123. urlObj.port = 443;
  124. console.warn(`更换视频源: ${hostName} -> ${urlObj.host}`);
  125. return urlObj.toString();
  126. } else if (urlObj.hostname.endsWith(".szbdyd.com")) {
  127. urlObj.host = urlObj.searchParams.get('xy_usource');
  128. urlObj.port = 443;
  129. console.warn(`更换视频源: ${hostName} -> ${urlObj.host}`);
  130. return urlObj.toString();
  131. }
  132. return url;
  133. } catch (e) {
  134. return url;
  135. }
  136. }
  137.  
  138. function replaceP2PUrlDeep(obj) {
  139. for (const key in obj) {
  140. if (typeof obj[key] === 'string') {
  141. obj[key] = replaceP2PUrl(obj[key]);
  142. } else if (typeof obj[key] === 'array' || typeof obj[key] === 'object') {
  143. replaceP2PUrlDeep(obj[key]);
  144. }
  145. }
  146. }
  147.  
  148. replaceP2PUrlDeep(unsafeWindow.__playinfo__);
  149.  
  150. (function (HTMLMediaElementPrototypeSrcDescriptor) {
  151. Object.defineProperty(unsafeWindow.HTMLMediaElement.prototype, 'src', {
  152. ...HTMLMediaElementPrototypeSrcDescriptor,
  153. set: function (value) {
  154. HTMLMediaElementPrototypeSrcDescriptor.set.call(this, replaceP2PUrl(value));
  155. },
  156. });
  157. })(Object.getOwnPropertyDescriptor(unsafeWindow.HTMLMediaElement.prototype, 'src'));
  158.  
  159. (function (open) {
  160. unsafeWindow.XMLHttpRequest.prototype.open = function () {
  161. try {
  162. arguments[1] = replaceP2PUrl(arguments[1]);
  163. } finally {
  164. return open.apply(this, arguments);
  165. }
  166. }
  167. })(unsafeWindow.XMLHttpRequest.prototype.open);
  168. }
  169.  
  170. // 真·原画直播
  171. if (location.href.startsWith('https://live.bilibili.com/')) {
  172. if (!localStorage.getItem('forceHighestQuality')) localStorage.setItem('forceHighestQuality', 'false');
  173.  
  174. unsafeWindow.disableMcdn = true;
  175. unsafeWindow.disableSmtcdns = true;
  176. unsafeWindow.forceHighestQuality = Boolean(localStorage.getItem('forceHighestQuality'));
  177. let recentErrors = 0;
  178. setInterval(() => recentErrors > 0 ? recentErrors / 2 : null, 10000);
  179.  
  180. const oldFetch = unsafeWindow.fetch;
  181. unsafeWindow.fetch = function (url) {
  182. try {
  183. const mcdnRegexp = /[xy0-9]+\.mcdn\.bilivideo\.cn:\d+/;
  184. const smtcdnsRegexp = /[\w\.]+\.smtcdns.net\/([\w\-]+\.bilivideo.com\/)/
  185. const qualityRegexp = /(live-bvc\/\d+\/live_\d+_\d+)_\w+/;
  186. if (mcdnRegexp.test(arguments[0]) && unsafeWindow.disableMcdn) {
  187. return Promise.reject();
  188. }
  189. if (smtcdnsRegexp.test(arguments[0]) && unsafeWindow.disableSmtcdns) {
  190. arguments[0] = arguments[0].replace(smtcdnsRegexp, '$1');
  191. }
  192. if (qualityRegexp.test(arguments[0]) && unsafeWindow.forceHighestQuality) {
  193. arguments[0] = arguments[0]
  194. .replace(qualityRegexp, '$1')
  195. .replace(/(\d+)_(mini|pro)hevc/g, '$1');
  196. }
  197. const promise = oldFetch.apply(this, arguments);
  198. promise.then(response => {
  199. if (!url.match(/\.(m3u8|m4s)/)) return;
  200. if ([403, 404].includes(response.status)) recentErrors++;
  201. if (recentErrors >= 5 && unsafeWindow.forceHighestQuality) {
  202. recentErrors = 0;
  203. unsafeWindow.forceHighestQuality = false;
  204. GM_notification({ title: '最高清晰度可能不可用', text: '已为您自动切换至播放器上选择的清晰度.', timeout: 3000, silent: true });
  205. }
  206. });
  207. return promise;
  208. } catch (e) { }
  209. return oldFetch.apply(this, arguments);
  210. }
  211.  
  212. // 还得帮叔叔修 bug,唉
  213. GM_addStyle("div[data-cy=EvaRenderer_LayerWrapper]:has(.player) { z-index: 999999; } .fixedPageBackground_root { z-index: 999999 !important; }");
  214.  
  215. // 干掉些直播间没用的东西
  216. GM_addStyle("#welcome-area-bottom-vm, .web-player-icon-roomStatus { display: none !important; }");
  217. }
  218.  
  219. // 视频裁切
  220. if (location.href.startsWith('https://www.bilibili.com/video/')) {
  221. GM_addStyle("body[video-fit] #bilibili-player video { object-fit: cover; } .bpx-player-ctrl-setting-fit-mode { display: flex;width: 100%;height: 32px;line-height: 32px; } .bpx-player-ctrl-setting-box .bui-panel-wrap, .bpx-player-ctrl-setting-box .bui-panel-item { min-height: 172px !important; }");
  222. let timer;
  223. function toggleMode(enabled) {
  224. if (enabled) {
  225. document.body.setAttribute('video-fit', '');
  226. } else {
  227. document.body.removeAttribute('video-fit');
  228. }
  229. }
  230. function injectButton() {
  231. if (!document.querySelector('.bpx-player-ctrl-setting-menu-left')) {
  232. return;
  233. }
  234. clearInterval(timer);
  235. const parent = document.querySelector('.bpx-player-ctrl-setting-menu-left');
  236. const item = document.createElement('div');
  237. item.className = 'bpx-player-ctrl-setting-fit-mode bui bui-switch';
  238. item.innerHTML = '<input class="bui-switch-input" type="checkbox"><label class="bui-switch-label"><span class="bui-switch-name">裁切模式</span><span class="bui-switch-body"><span class="bui-switch-dot"><span></span></span></span></label>';
  239. parent.insertBefore(item, document.querySelector('.bpx-player-ctrl-setting-more'));
  240. document.querySelector('.bpx-player-ctrl-setting-fit-mode input').addEventListener('change', e => toggleMode(e.target.checked));
  241. document.querySelector('.bpx-player-ctrl-setting-box .bui-panel-item').style.height = '';
  242. }
  243. timer = setInterval(injectButton, 200);
  244. }
  245.  
  246. // 去除地址栏多余参数
  247. unsafeWindow.history.replaceState(undefined, undefined, removeTracking(location.href));
  248. const pushState = unsafeWindow.history.pushState;
  249. unsafeWindow.history.pushState = function (state, unused, url) {
  250. return pushState.apply(this, [state, unused, removeTracking(url)]);
  251. }
  252. const replaceState = unsafeWindow.history.replaceState;
  253. unsafeWindow.history.replaceState = function (state, unused, url) {
  254. return replaceState.apply(this, [state, unused, removeTracking(url)]);
  255. }
  256.  
  257. function removeTracking(url) {
  258. if (!url) return url;
  259. try {
  260. const urlObj = new URL(url, location.href);
  261. if (!urlObj.search) return url;
  262. const searchParams = urlObj.searchParams;
  263. const keys = Array.from(searchParams.keys());
  264. for (const key of keys) {
  265. uselessUrlParams.forEach(item => {
  266. if (typeof item === 'string') {
  267. if (item === key) searchParams.delete(key);
  268. } else if (item instanceof RegExp) {
  269. if (item.test(key)) searchParams.delete(key);
  270. }
  271. });
  272. }
  273. urlObj.search = searchParams.toString();
  274. return urlObj.toString();
  275. } catch (e) {
  276. console.error(e);
  277. return url;
  278. }
  279. }
  280.  
  281. // 去掉 B 站的傻逼上报
  282. !function () {
  283. const oldFetch = unsafeWindow.fetch;
  284. unsafeWindow.fetch = function (url) {
  285. if (typeof url === 'string' && url.match(/(?:cm|data)\.bilibili\.com/))
  286. return new Promise(function () { });
  287. return oldFetch.apply(this, arguments);
  288. }
  289. const oldOpen = unsafeWindow.XMLHttpRequest.prototype.open;
  290. unsafeWindow.XMLHttpRequest.prototype.open = function (method, url) {
  291. if (typeof url === 'string' && url.match(/(?:cm|data)\.bilibili\.com/)) {
  292. this.send = function () { };
  293. }
  294. return oldOpen.apply(this, arguments);
  295. }
  296.  
  297. unsafeWindow.navigator.sendBeacon = () => Promise.resolve();
  298.  
  299. unsafeWindow.MReporterInstance = new Proxy(function () { }, {
  300. get(target, prop) {
  301. debugLog(`MReporterInstance.${prop} called with`, arguments);
  302. return () => { };
  303. }
  304. });
  305.  
  306. unsafeWindow.MReporter = new Proxy(function () { }, {
  307. construct() {
  308. return MReporterInstance;
  309. },
  310. get(target, prop) {
  311. debugLog(`MReporter.${prop} called with`, arguments);
  312. return () => { };
  313. }
  314. });
  315.  
  316. const sentryHub = class { bindClient() { } }
  317. const fakeSentry = {
  318. SDK_NAME: 'sentry.javascript.browser',
  319. SDK_VERSION: '0.0.0',
  320. BrowserClient: class { },
  321. Hub: sentryHub,
  322. Integrations: {
  323. Vue: class { },
  324. GlobalHandlers: class { },
  325. InboundFilters: class { },
  326. },
  327. init() { },
  328. configureScope() { },
  329. getCurrentHub: () => new sentryHub(),
  330. setContext() { },
  331. setExtra() { },
  332. setExtras() { },
  333. setTag() { },
  334. setTags() { },
  335. setUser() { },
  336. wrap() { },
  337. }
  338. if (!unsafeWindow.Sentry || unsafeWindow.Sentry.SDK_VERSION !== fakeSentry.SDK_VERSION) {
  339. if (unsafeWindow.Sentry) { delete unsafeWindow.Sentry }
  340. Object.defineProperty(unsafeWindow, 'Sentry', { value: fakeSentry, enumerable: false, writable: false });
  341. }
  342.  
  343. unsafeWindow.ReporterPbInstance = new Proxy(function () { }, {
  344. get(target, prop) {
  345. debugLog(`ReporterPbInstance.${prop} called with`, arguments);
  346. return () => { };
  347. }
  348. });
  349. unsafeWindow.ReporterPb = new Proxy(function () { }, {
  350. construct() {
  351. return ReporterPbInstance;
  352. },
  353. });
  354.  
  355. Object.defineProperty(unsafeWindow, '__biliUserFp__', {
  356. get() { return { init() { }, queryUserLog() { return [] } } },
  357. set() { },
  358. });
  359. Object.defineProperty(unsafeWindow, '__USER_FP_CONFIG__', { get() { return undefined }, set() { } });
  360. Object.defineProperty(unsafeWindow, '__MIRROR_CONFIG__', { get() { return undefined }, set() { } });
  361. }()
  362.  
  363. function debugLog() {
  364. if (unsafeWindow.__MBGA_DEBUG__)
  365. console.log.apply(this, arguments);
  366. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址