Make AV number, not BV code

Make AV number, not BV code. F**k you BiliBili

  1. // ==UserScript==
  2. // @name Make AV number, not BV code
  3. // @name:en Make AV number, not BV code
  4. // @name:zh-CN 要AV号,不要BV码
  5. // @namespace https://pilipili.com/bv10492
  6. // @version 0.1.5
  7. // @description Make AV number, not BV code. F**k you BiliBili
  8. // @description:en Make AV number, not BV code. F**k you BiliBili
  9. // @description:zh-CN 要AV号,不要BV码,屑站飞了
  10. // @author jk1551
  11. // @require https://unpkg.com/ajax-hook@2.0.0/dist/ajaxhook.min.js
  12. // @nocompat Chrome
  13. // @nocompat Firefox
  14. // @nocompat Opera
  15. // @nocompat Edge
  16. // @match https://www.bilibili.com/video/*
  17. // @match http://www.bilibili.com/video/*
  18. // @match https://www.bilibili.com/bangumi/play/*
  19. // @match http://www.bilibili.com/bangumi/play/*
  20. // @match https://acg.tv/*
  21. // @match http://acg.tv/*
  22. // @match https://b23.tv/*
  23. // @match http://b23.tv/*
  24. // @grant none
  25. // ==/UserScript==
  26.  
  27. /* jshint esversion: 6 */
  28.  
  29. const CONFIG = {
  30. autoJump: false
  31. };
  32.  
  33. // GM代码构思来自 https://greasyfork.org/zh-CN/scripts/398526-give-me-av-not-bv
  34.  
  35. (() => {
  36. if (typeof BigInt !== "function") {
  37. console.warn("Your browser does not support BigInt. AV/BV conversion is disabled.");
  38. return;
  39. }
  40.  
  41. const bv2av = (() => {
  42. // https://www.zhihu.com/question/381784377/answer/1099438784
  43. const table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF";
  44. const tr = Object.create(null);
  45.  
  46. for (let i = 0; i < 58; i += 1) {
  47. tr[table[i]] = BigInt(i);
  48. }
  49.  
  50. const s = [11, 10, 3, 8, 4, 6/* ←今ここ */, 2, 9, 5, 7/* 最大値 */];
  51. // 不用BigInt的话后面做位运算就爆炸了
  52. const xor = BigInt(177451812);
  53. const add = BigInt(8728348608);
  54. const pow58 = [];
  55.  
  56. // 58进制下2^32的最大位数(=6),也就是在此之前变动的最多位数,也就是s表的长度
  57. // 以后用随机av号,超过27个二进制位的时候,就可以把上面的封印解除啦
  58. const highestDigit = 6;
  59.  
  60. for (let i = 0; i < highestDigit; i += 1) {
  61. pow58.push(BigInt(Math.pow(58, i)));
  62. }
  63.  
  64. /**
  65. * @param s {string}
  66. */
  67. function list(s) {
  68. return s.split("");
  69. }
  70.  
  71. /**
  72. * @param x {string} BV string; including the beginning "BV" (e.g. "BV17x411w7KC")
  73. */
  74. function dec(x) {
  75. let r = BigInt(0);
  76.  
  77. for (let i = 0; i < highestDigit; i += 1) {
  78. r += tr[x[s[i]]] * pow58[i];
  79. }
  80.  
  81. return Number((r - add) ^ xor);
  82. }
  83.  
  84. /**
  85. * @param x {number} Exsiting AV number
  86. */
  87. function enc(x) {
  88. x = BigInt(x);
  89. x = (x ^ xor) + add;
  90.  
  91. const r = list("BV1 4 1 7 ");
  92.  
  93. for (let i = 0; i < highestDigit; i += 1) {
  94. const index = (x / pow58[i]) % BigInt(58);
  95. r[s[i]] = table[index];
  96. }
  97.  
  98. return r.join("");
  99. }
  100.  
  101. return {
  102. enc: enc,
  103. dec: dec
  104. };
  105. })();
  106.  
  107. /**
  108. * @param avNumber {number | string | bigint}
  109. */
  110. function getAvUrl(avNumber) {
  111. return "/video/av" + avNumber.toString();
  112. }
  113.  
  114. /**
  115. * @param avNumber {number | string | bigint}
  116. */
  117. function getAvText(avNumber) {
  118. return "av" + avNumber.toString();
  119. }
  120.  
  121. /**
  122. * @param avNumber {number | string}
  123. */
  124. function appendOrUpdateAvNumberLink(avNumber) {
  125. const avInfoLabels = document.getElementsByClassName("video-data");
  126. // 第一行:分区和日期
  127. const infoBlock1 = avInfoLabels[0];
  128.  
  129. // 投稿日期span
  130. // 直接塞到和infoBlock1同级会导致谜之排版错误,只好借壳咯(其实是Vue的锅
  131. const info1 = infoBlock1.children[1];
  132.  
  133. // 我放弃创建新的元素了……干脆直接黑魔法换个a
  134. const avreg = /av\d+/;
  135. const oldContent = info1.textContent;
  136. if (!avreg.test(oldContent)) {
  137. const avUrl = getAvUrl(avNumber);
  138. const avText = getAvText(avNumber);
  139. info1.outerHTML = `<a href="${avUrl}" title="${avText}" target="_blank">${oldContent}\u00a0${avText}</a>`;
  140. // 以下两行本来是调整样式,让其显示跟原来差不多的,但是Vue似乎劫持了样式设定,所以没出效果也就算了
  141. info1.style.color = "#999";
  142. info1.style.height = "16px";
  143. }
  144. }
  145.  
  146. /**
  147. * @param avNumber {number | string}
  148. */
  149. function modifyBangumiAvNumberLink(avNumber) {
  150. const pubs = document.getElementsByClassName("pub-wrapper");
  151.  
  152. if (!pubs || pubs.length === 0) {
  153. return;
  154. }
  155.  
  156. const avLink = pubs[0].querySelector("a.av-link");
  157.  
  158. if (!avLink) {
  159. return;
  160. }
  161.  
  162. avLink.textContent = getAvText(avNumber);
  163. avLink.href = getAvUrl(avNumber);
  164. }
  165.  
  166. (() => {
  167. const bvUrlRE = /\/video\/(BV[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)/;
  168. const bvmatch = bvUrlRE.exec(window.location.href);
  169.  
  170. if (bvmatch) {
  171. console.log("F**k You BV Number!");
  172.  
  173. if (CONFIG.autoJump) {
  174. const bvstr = bvmatch[1];
  175. const avNumber = bv2av.dec(bvstr);
  176. const url = getAvUrl(avNumber);
  177. window.location.href = url;
  178. }
  179. }
  180. })();
  181.  
  182. let isContentRefreshed = false;
  183. let avNumber = Number.NaN;
  184. let pageType = Number.NaN;
  185.  
  186. const PageType = {
  187. video: 0,
  188. bangumi: 1
  189. };
  190.  
  191. // TODO: 处理 HTML5 History 相关
  192.  
  193. const bvidRE = /bvid=(BV[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)/;
  194. const aidRE = /aid=(\d+)/;
  195. const avidRE = /avid=(\d+)/;
  196.  
  197. ah.proxy({
  198. //请求发起前进入
  199. onRequest: (config, handler) => {
  200. let match;
  201.  
  202. if (config.url.indexOf("api.bilibili.com/x/web-interface/view") > 0) {
  203. match = bvidRE.exec(config.url);
  204.  
  205. if (match) {
  206. // https://api.bilibili.com/x/web-interface/view?cid=167028524&bvid=BV1TE411A7VJ
  207. avNumber = bv2av.dec(match[1]);
  208. isContentRefreshed = true;
  209. pageType = PageType.video;
  210. } else {
  211. match = aidRE.exec(config.url);
  212.  
  213. if (match) {
  214. // https://api.bilibili.com/x/web-interface/view?cid=167028524&aid=97836354
  215. avNumber = Number.parseInt(match[1]);
  216. isContentRefreshed = true;
  217. pageType = PageType.video;
  218. }
  219. }
  220. } else if (config.url.indexOf("api.bilibili.com/pgc/player/web/playurl") > 0) {
  221. match = avidRE.exec(config.url);
  222.  
  223. if (match) {
  224. // https://api.bilibili.com/pgc/player/web/playurl?cid=122062626&qn=0&type=&otype=json&avid=70455036&ep_id=286039&fourk=1&fnver=0&fnval=16&session=xxx
  225. avNumber = Number.parseInt(match[1]);
  226. isContentRefreshed = true;
  227. pageType = PageType.bangumi;
  228. }
  229. }
  230.  
  231. handler.next(config);
  232. },
  233.  
  234. //请求成功后进入
  235. onResponse: (response, handler) => {
  236. if (response.config.url.indexOf("api.bilibili.com") > 0) {
  237. if (isContentRefreshed) {
  238. switch (pageType) {
  239. case PageType.video: {
  240. if (!Number.isNaN(avNumber)) {
  241. setTimeout(() => appendOrUpdateAvNumberLink(avNumber));
  242. }
  243.  
  244. break;
  245. }
  246. case PageType.bangumi: {
  247. if (!Number.isNaN(avNumber)) {
  248. setTimeout(() => modifyBangumiAvNumberLink(avNumber));
  249. }
  250.  
  251. break;
  252. }
  253. default:
  254. break;
  255. }
  256. }
  257. }
  258.  
  259. handler.next(response);
  260. }
  261. });
  262. })();