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.11
  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. unsafeWindow.disableMcdn = true;
  173. unsafeWindow.disableSmtcdns = true;
  174. unsafeWindow.forceHighestQuality = localStorage.getItem('forceHighestQuality') === 'true';
  175. let recentErrors = 0;
  176. setInterval(() => recentErrors > 0 ? recentErrors / 2 : null, 10000);
  177.  
  178. const oldFetch = unsafeWindow.fetch;
  179. unsafeWindow.fetch = function (url) {
  180. try {
  181. const mcdnRegexp = /[xy0-9]+\.mcdn\.bilivideo\.cn:\d+/;
  182. const smtcdnsRegexp = /[\w\.]+\.smtcdns.net\/([\w\-]+\.bilivideo.com\/)/
  183. const qualityRegexp = /(live-bvc\/\d+\/live_\d+_\d+)_\w+/;
  184. if (mcdnRegexp.test(arguments[0]) && unsafeWindow.disableMcdn) {
  185. return Promise.reject();
  186. }
  187. if (smtcdnsRegexp.test(arguments[0]) && unsafeWindow.disableSmtcdns) {
  188. arguments[0] = arguments[0].replace(smtcdnsRegexp, '$1');
  189. }
  190. if (qualityRegexp.test(arguments[0]) && unsafeWindow.forceHighestQuality) {
  191. arguments[0] = arguments[0]
  192. .replace(qualityRegexp, '$1')
  193. .replace(/(\d+)_(mini|pro)hevc/g, '$1');
  194. }
  195. const promise = oldFetch.apply(this, arguments);
  196. promise.then(response => {
  197. if (!url.match(/\.(m3u8|m4s)/)) return;
  198. if ([403, 404].includes(response.status)) recentErrors++;
  199. if (recentErrors >= 5 && unsafeWindow.forceHighestQuality) {
  200. recentErrors = 0;
  201. unsafeWindow.forceHighestQuality = false;
  202. GM_notification({ title: '最高清晰度可能不可用', text: '已为您自动切换至播放器上选择的清晰度.', timeout: 3000, silent: true });
  203. }
  204. });
  205. return promise;
  206. } catch (e) { }
  207. return oldFetch.apply(this, arguments);
  208. }
  209.  
  210. // 还得帮叔叔修 bug,唉
  211. GM_addStyle("div[data-cy=EvaRenderer_LayerWrapper]:has(.player) { z-index: 999999; } .fixedPageBackground_root { z-index: 999999 !important; }");
  212.  
  213. // 干掉些直播间没用的东西
  214. GM_addStyle("#welcome-area-bottom-vm, .web-player-icon-roomStatus { display: none !important; }");
  215. }
  216.  
  217. // 视频裁切
  218. if (location.href.startsWith('https://www.bilibili.com/video/')) {
  219. 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; }");
  220. let timer;
  221. function toggleMode(enabled) {
  222. if (enabled) {
  223. document.body.setAttribute('video-fit', '');
  224. } else {
  225. document.body.removeAttribute('video-fit');
  226. }
  227. }
  228. function injectButton() {
  229. if (!document.querySelector('.bpx-player-ctrl-setting-menu-left')) {
  230. return;
  231. }
  232. clearInterval(timer);
  233. const parent = document.querySelector('.bpx-player-ctrl-setting-menu-left');
  234. const item = document.createElement('div');
  235. item.className = 'bpx-player-ctrl-setting-fit-mode bui bui-switch';
  236. 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>';
  237. parent.insertBefore(item, document.querySelector('.bpx-player-ctrl-setting-more'));
  238. document.querySelector('.bpx-player-ctrl-setting-fit-mode input').addEventListener('change', e => toggleMode(e.target.checked));
  239. document.querySelector('.bpx-player-ctrl-setting-box .bui-panel-item').style.height = '';
  240. }
  241. timer = setInterval(injectButton, 200);
  242. }
  243.  
  244. // 去除地址栏多余参数
  245. unsafeWindow.history.replaceState(undefined, undefined, removeTracking(location.href));
  246. const pushState = unsafeWindow.history.pushState;
  247. unsafeWindow.history.pushState = function (state, unused, url) {
  248. return pushState.apply(this, [state, unused, removeTracking(url)]);
  249. }
  250. const replaceState = unsafeWindow.history.replaceState;
  251. unsafeWindow.history.replaceState = function (state, unused, url) {
  252. return replaceState.apply(this, [state, unused, removeTracking(url)]);
  253. }
  254.  
  255. function removeTracking(url) {
  256. if (!url) return url;
  257. try {
  258. const urlObj = new URL(url, location.href);
  259. if (!urlObj.search) return url;
  260. const searchParams = urlObj.searchParams;
  261. const keys = Array.from(searchParams.keys());
  262. for (const key of keys) {
  263. uselessUrlParams.forEach(item => {
  264. if (typeof item === 'string') {
  265. if (item === key) searchParams.delete(key);
  266. } else if (item instanceof RegExp) {
  267. if (item.test(key)) searchParams.delete(key);
  268. }
  269. });
  270. }
  271. urlObj.search = searchParams.toString();
  272. return urlObj.toString();
  273. } catch (e) {
  274. console.error(e);
  275. return url;
  276. }
  277. }
  278.  
  279. // 去掉 B 站的傻逼上报
  280. !function () {
  281. const oldFetch = unsafeWindow.fetch;
  282. unsafeWindow.fetch = function (url) {
  283. if (typeof url === 'string' && url.match(/(?:cm|data)\.bilibili\.com/))
  284. return new Promise(function () { });
  285. return oldFetch.apply(this, arguments);
  286. }
  287. const oldOpen = unsafeWindow.XMLHttpRequest.prototype.open;
  288. unsafeWindow.XMLHttpRequest.prototype.open = function (method, url) {
  289. if (typeof url === 'string' && url.match(/(?:cm|data)\.bilibili\.com/)) {
  290. this.send = function () { };
  291. }
  292. return oldOpen.apply(this, arguments);
  293. }
  294.  
  295. unsafeWindow.navigator.sendBeacon = () => Promise.resolve();
  296.  
  297. unsafeWindow.MReporterInstance = new Proxy(function () { }, {
  298. get(target, prop) {
  299. debugLog(`MReporterInstance.${prop} called with`, arguments);
  300. return () => { };
  301. }
  302. });
  303.  
  304. unsafeWindow.MReporter = new Proxy(function () { }, {
  305. construct() {
  306. return MReporterInstance;
  307. },
  308. get(target, prop) {
  309. debugLog(`MReporter.${prop} called with`, arguments);
  310. return () => { };
  311. }
  312. });
  313.  
  314. const sentryHub = class { bindClient() { } }
  315. const fakeSentry = {
  316. SDK_NAME: 'sentry.javascript.browser',
  317. SDK_VERSION: '0.0.0',
  318. BrowserClient: class { },
  319. Hub: sentryHub,
  320. Integrations: {
  321. Vue: class { },
  322. GlobalHandlers: class { },
  323. InboundFilters: class { },
  324. },
  325. init() { },
  326. configureScope() { },
  327. getCurrentHub: () => new sentryHub(),
  328. setContext() { },
  329. setExtra() { },
  330. setExtras() { },
  331. setTag() { },
  332. setTags() { },
  333. setUser() { },
  334. wrap() { },
  335. }
  336. if (!unsafeWindow.Sentry || unsafeWindow.Sentry.SDK_VERSION !== fakeSentry.SDK_VERSION) {
  337. if (unsafeWindow.Sentry) { delete unsafeWindow.Sentry }
  338. Object.defineProperty(unsafeWindow, 'Sentry', { value: fakeSentry, enumerable: false, writable: false });
  339. }
  340.  
  341. unsafeWindow.ReporterPbInstance = new Proxy(function () { }, {
  342. get(target, prop) {
  343. debugLog(`ReporterPbInstance.${prop} called with`, arguments);
  344. return () => { };
  345. }
  346. });
  347. unsafeWindow.ReporterPb = new Proxy(function () { }, {
  348. construct() {
  349. return ReporterPbInstance;
  350. },
  351. });
  352.  
  353. Object.defineProperty(unsafeWindow, '__biliUserFp__', {
  354. get() { return { init() { }, queryUserLog() { return [] } } },
  355. set() { },
  356. });
  357. Object.defineProperty(unsafeWindow, '__USER_FP_CONFIG__', { get() { return undefined }, set() { } });
  358. Object.defineProperty(unsafeWindow, '__MIRROR_CONFIG__', { get() { return undefined }, set() { } });
  359. }()
  360.  
  361. function debugLog() {
  362. if (unsafeWindow.__MBGA_DEBUG__)
  363. console.log.apply(this, arguments);
  364. }

QingJ © 2025

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