Greasy Fork is available in English.

Bilibili AntiBV

自动在地址栏中将 bv 还原为 av,非重定向,不会导致页面刷新,顺便清除 search string 中所有无用参数

Bu scripti kur?
Yazarın tavsiye ettiği betik

Siz bunuda beğenebilirsiniz: Bilibili 哔哩哔哩视频点踩.

Bu scripti kur
  1. // ==UserScript==
  2. // @name Bilibili AntiBV
  3. // @icon https://www.bilibili.com/favicon.ico
  4. // @namespace https://moe.best/
  5. // @version 1.9.6
  6. // @description 自动在地址栏中将 bv 还原为 av,非重定向,不会导致页面刷新,顺便清除 search string 中所有无用参数
  7. // @author 神代绮凛
  8. // @include /^https:\/\/www\.bilibili\.com\/(s\/)?video\/[BbAa][Vv]/
  9. // @require https://code.bdstatic.com/npm/simple-query-string@1.3.2/src/simplequerystring.min.js
  10. // @license WTFPL
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_registerMenuCommand
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. let REDIRECT_S_LINK = GM_getValue('redirect_s_link', true);
  21. GM_registerMenuCommand('自动重定向 /s/video/xxx', () => {
  22. REDIRECT_S_LINK = confirm(`自动将 /s/video/xxx 重定向至 /video/xxx
  23. “确定”开启,“取消”关闭
  24. 当前:${REDIRECT_S_LINK ? '开启' : '关闭'}`);
  25. GM_setValue('redirect_s_link', REDIRECT_S_LINK);
  26. });
  27.  
  28. if (REDIRECT_S_LINK && location.pathname.startsWith('/s/video/')) {
  29. location.pathname = location.pathname.replace(/^\/s/, '');
  30. return;
  31. }
  32.  
  33. const win = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
  34.  
  35. const last = arr => arr[arr.length - 1];
  36.  
  37. const wrapHistory = method => {
  38. const fn = win.history[method];
  39. const e = new Event(method);
  40. return function () {
  41. setTimeout(() => window.dispatchEvent(e));
  42. return fn.apply(this, arguments);
  43. };
  44. };
  45. win.history.pushState = wrapHistory('pushState');
  46.  
  47. win.history.replaceState = (() => {
  48. const fn = win.history.replaceState;
  49. return function (state, unused, url, isMe) {
  50. if (isMe) return fn.apply(this, [state, unused, url]);
  51. try {
  52. state = { ...history.state, ...state };
  53. const urlObj = new URL(url.startsWith('/') ? `${location.origin}${url}` : url);
  54. urlObj.search = purgeSearchString(urlObj.search);
  55. const bvid = getBvidFromUrl(urlObj.pathname);
  56. if (bvid) urlObj.pathname = location.pathname;
  57. url = urlObj.href.replace(urlObj.origin, '');
  58. } catch (e) {
  59. console.error(e);
  60. }
  61. return fn.apply(this, [state, unused, url]);
  62. };
  63. })();
  64.  
  65. const getBvidFromUrl = pathname => {
  66. const lastPath = last(pathname.split('/').filter(v => v));
  67. return /^bv/i.test(lastPath) ? lastPath : null;
  68. };
  69. const getUrl = id => `/video/${id}/${purgeSearchString(location.search)}${location.hash}`;
  70.  
  71. // https://github.com/mrhso/IshisashiWebsite/blob/master/%E4%B9%B1%E5%86%99%E7%A8%8B%E5%BC%8F/BV%20%E5%8F%B7%E8%B7%8B%E6%89%88%E3%80%80%EF%BD%9E%20Who%20done%20it!.js
  72. const bv2av = (() => {
  73. const charset = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
  74. const bvReg = new RegExp(`^[Bb][Vv]1[${charset}]{9}$`);
  75. const base = BigInt(charset.length);
  76. const table = {};
  77. for (let i = 0; i < charset.length; i++) table[charset[i]] = i;
  78. const xor = 23442827791579n;
  79. const rangeLeft = 1n;
  80. const rangeRight = 2n ** 51n;
  81.  
  82. /**
  83. * @param {string} bv
  84. */
  85. return bv => {
  86. if (!bvReg.test(bv)) {
  87. throw new Error(`Unexpected bv: ${bv}`);
  88. }
  89.  
  90. const chars = bv.split('');
  91. [chars[3], chars[9]] = [chars[9], chars[3]];
  92. [chars[4], chars[7]] = [chars[7], chars[4]];
  93.  
  94. let result = 0n;
  95. for (let i = 3; i < 12; i++) {
  96. result = result * base + BigInt(table[chars[i]]);
  97. }
  98. if (result < rangeRight || result >= rangeRight * 2n) {
  99. throw new RangeError(`Unexpected av result: ${result}`);
  100. }
  101. result = result % rangeRight ^ xor;
  102. if (result < rangeLeft) {
  103. throw new RangeError(`Unexpected av result: ${result}`);
  104. }
  105.  
  106. return result;
  107. };
  108. })();
  109.  
  110. const purgeSearchString = search => {
  111. const { p, t } = simpleQueryString.parse(search);
  112. const result = simpleQueryString.stringify({ p, t });
  113. return result ? `?${result}` : '';
  114. };
  115.  
  116. const replaceUrl = () => {
  117. const bvid = getBvidFromUrl(location.pathname);
  118. const aid = bv2av(bvid);
  119. if (!aid) return;
  120. const avUrl = getUrl(`av${aid}`);
  121. const BABKey = `BAB-${avUrl}`;
  122. if (sessionStorage.getItem(BABKey)) {
  123. console.warn('[Bilibili AntiBV] abort');
  124. return;
  125. }
  126. sessionStorage.setItem(BABKey, 1);
  127. setTimeout(() => sessionStorage.removeItem(BABKey), 1000);
  128. history.replaceState({ ...history.state, aid, bvid }, '', avUrl, true);
  129. };
  130.  
  131. const replaceBack = ({ state }) => {
  132. const { aid, bvid } = state;
  133. if (!bvid) return;
  134. history.replaceState(state, '', getUrl(bvid), true);
  135. setTimeout(() => history.replaceState(state, '', getUrl(`av${aid}`), true));
  136. };
  137.  
  138. window.addEventListener('load', replaceUrl, { once: true });
  139. window.addEventListener('pushState', replaceUrl);
  140. window.addEventListener('popstate', replaceBack);
  141. })();