Greasy Fork is available in English.

哔哩哔哩(B站|Bilibili)收藏夹Fix

修复 哔哩哔哩(www.bilibili.com) 失效的收藏。(可查看av号、简介、标题、封面)

  1. // ==UserScript==
  2. // @name 哔哩哔哩(B站|Bilibili)收藏夹Fix
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.1
  5. // @description 修复 哔哩哔哩(www.bilibili.com) 失效的收藏。(可查看av号、简介、标题、封面)
  6. // @author Mr.Po
  7. // @match https://space.bilibili.com/*
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js
  9. // @resource iconError https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/error.png
  10. // @resource iconSuccess https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/success.png
  11. // @resource iconInfo https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/info.png
  12. // @connect biliplus.com
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_notification
  15. // @grant GM_setClipboard
  16. // @grant GM_getResourceURL
  17. // ==/UserScript==
  18.  
  19. /*jshint esversion: 8 */
  20. (function() {
  21. 'use strict';
  22.  
  23. /**
  24. * 失效收藏标题颜色(默认为灰色)。
  25. * @type {String}
  26. */
  27. const invalTitleColor = "#999";
  28.  
  29. /**
  30. * 是否启用调试模式。
  31. * 启用后,浏览器控制台会显示此脚本运行时的调试数据。
  32. * @type {Boolean}
  33. */
  34. const isDebug = false;
  35.  
  36. /**
  37. * 重试延迟[秒]。
  38. * @type {Number}
  39. */
  40. const retryDelay = 5;
  41.  
  42. /**
  43. * 每隔 space [毫秒]检查一次,是否有新的收藏被加载出来。
  44. * 此值越小,检查越快;过小会造成浏览器卡顿。
  45. * @type {Number}
  46. */
  47. const space = 2000;
  48.  
  49. /******************************************************/
  50.  
  51. /**
  52. * 收藏夹地址正则
  53. * @type {RegExp}
  54. */
  55. const favlistRegex = /https:\/\/space\.bilibili\.com\/\d+\/favlist.*/;
  56.  
  57. /**
  58. * 处理收藏
  59. */
  60. function handleFavorites() {
  61.  
  62. const flag = favlistRegex.test(window.location.href);
  63.  
  64. if (flag) { // 当前页面是收藏地址
  65.  
  66. // 失效收藏节点集
  67. const $lis = $("ul.fav-video-list.content li.small-item.disabled");
  68.  
  69. if ($lis.size() > 0) {
  70.  
  71. console.info(`${$lis.size()}个收藏待修复...`);
  72.  
  73. $lis.each(function(i, it) {
  74.  
  75. const bv = $(it).attr("data-aid");
  76.  
  77. const aid = bv2aid(bv);
  78.  
  79. // 多个超链接
  80. const $as = $(it).find("a");
  81.  
  82. $as.attr("href", `https://www.biliplus.com/video/av${aid}/`);
  83. $as.attr("target", "_blank");
  84.  
  85. addCopyAVCodeButton($(it), aid);
  86.  
  87. fixTitleAndPic($(it), $($as[1]), aid);
  88.  
  89. // 移除禁用样式
  90. $(it).removeClass("disabled");
  91. $as.removeClass("disabled");
  92. });
  93.  
  94. showDetail($lis);
  95. }
  96. }
  97. }
  98.  
  99. function addOperation($item, name, fun) {
  100.  
  101. const $ul = $item.find(".be-dropdown-menu").first();
  102.  
  103. const lastChild = $ul.children().last();
  104.  
  105. // 未添加过扩展
  106. if (!lastChild.hasClass('be-dropdown-item-extend')) {
  107. lastChild.addClass("be-dropdown-item-delimiter");
  108. }
  109.  
  110. const $li = $(`<li class="be-dropdown-item be-dropdown-item-extend">${name}</li>`);
  111.  
  112. $li.click(fun);
  113.  
  114. $ul.append($li);
  115. }
  116.  
  117. function addCopyAVCodeButton($item, aid) {
  118.  
  119. addOperation($item, "复制av号", function() {
  120.  
  121. GM_setClipboard(`av${aid}`, "text");
  122.  
  123. tipSuccess("av号复制成功!");
  124. });
  125. }
  126.  
  127. function addCopyInfoButton($item, content) {
  128.  
  129. addOperation($item, "复制简介", function() {
  130.  
  131. GM_setClipboard(content, "text");
  132.  
  133. tipSuccess("简介复制成功!");
  134. });
  135. }
  136.  
  137. /**
  138. * 标记失效的收藏
  139. * @param {$节点} $it 当前收藏Item
  140. * @param {$节点} $a 标题链接
  141. */
  142. function signInval($it, $a) {
  143.  
  144. // 收藏时间
  145. const $pubdate = $it.find("div.meta.pubdate");
  146.  
  147. // 增加 删除线
  148. $pubdate.attr("style", "text-decoration:line-through");
  149.  
  150. // 增加 删除线 + 置(灰)
  151. $a.attr("style", `text-decoration:line-through;color:${invalTitleColor};`);
  152. }
  153.  
  154. /**
  155. * 绑定重新加载
  156. * @param {$节点} $a 标题链接
  157. * @param {函数} fun 重试方法
  158. */
  159. function bindReload($a, fun) {
  160.  
  161. $a.text("->手动加载<-");
  162.  
  163. $a.click(function() {
  164.  
  165. $(this).unbind("click");
  166.  
  167. $a.text("Loading...");
  168.  
  169. fun();
  170. });
  171. }
  172.  
  173. /**
  174. * 再次尝试加载
  175. * @param {$节点} $a 标题链接
  176. * @param {数字} aid AV号
  177. * @param {布尔} delayRetry 延迟重试
  178. * @param {函数} fun 重试方法
  179. */
  180. function retryLoad($a, aid, delayRetry, fun) {
  181.  
  182. console.warn(`查询:av${aid},请求过快!`);
  183.  
  184. if (delayRetry) { // 延迟绑定
  185.  
  186. $a.text(`请求过快,${retryDelay}秒后再试!`);
  187.  
  188. setTimeout(bindReload, retryDelay * 1000, $a, fun);
  189.  
  190. countdown($a, retryDelay);
  191.  
  192. } else { // 首次,立即绑定
  193.  
  194. $a.attr("href", "javascript:void(0);");
  195.  
  196. bindReload($a, fun);
  197. }
  198. }
  199.  
  200. /**
  201. * 重新绑定倒计时
  202. * @param {$节点} $a 标题链接
  203. * @param {数字} second 秒
  204. */
  205. function countdown($a, second) {
  206.  
  207. if ($a.text().indexOf("请求过快") === 0) {
  208.  
  209. $a.text(`请求过快,${second}秒后再试!`);
  210.  
  211. if (second > 1) {
  212. setTimeout(countdown, 1000, $a, second - 1);
  213. }
  214. }
  215. }
  216.  
  217. /**
  218. * 修复收藏
  219. * @param {$节点} $it 当前收藏Item
  220. * @param {$节点} $a 标题链接
  221. * @param {数字} aid av号
  222. * @param {字符串} title 标题
  223. * @param {字符串} pic 海报
  224. * @param {字符串} history 历史归档,若无时,使用空字符串
  225. */
  226. function fixFavorites($it, $a, aid, title, pic, history) {
  227.  
  228. // 设置标题
  229. $a.text(title);
  230. $a.attr("title", $a.text());
  231.  
  232. // 多个超链接
  233. const $as = $it.find("a");
  234. $as.attr("href", `https://www.biliplus.com/${history}video/av${aid}/`);
  235.  
  236. signInval($it, $a);
  237.  
  238. // 判断海报链接是否有效,有效时进行替换
  239. isLoad(pic, function() {
  240. const $img = $it.find("img");
  241. $img.attr("src", pic);
  242. });
  243. }
  244.  
  245. /**
  246. * 修复标题和海报
  247. * @param {$节点} $it 当前收藏Item
  248. * @param {$节点} $a 标题链接
  249. * @param {数字} aid av号
  250. */
  251. function fixTitleAndPic($it, $a, aid) {
  252.  
  253. $a.text("Loading...");
  254.  
  255. fixTitleAndPicEnhance3($it, $a, aid);
  256. }
  257.  
  258. /**
  259. * 修复标题和海报 增强 - 0
  260. * 使用公开的API
  261. * @param {$节点} $it 当前收藏Item
  262. * @param {$节点} $a 标题链接
  263. * @param {数字} aid av号
  264. * @param {布尔} delayRetry 延迟重试
  265. */
  266. function fixTitleAndPicEnhance0($it, $a, aid, delayRetry) {
  267.  
  268. GM_xmlhttpRequest({
  269. method: 'GET',
  270. url: `https://www.biliplus.com/api/view?id=${aid}`,
  271. responseType: "json",
  272. onload: function(response) {
  273.  
  274. const res = response.response;
  275.  
  276. if (isDebug) {
  277. console.log("0---->:");
  278. console.log(res);
  279. }
  280.  
  281. // 找到了
  282. if (res.title) {
  283.  
  284. fixFavorites($it, $a, aid, res.title, res.pic, "");
  285.  
  286. } else if (res.code == -503) { // 请求过快
  287.  
  288. retryLoad($a, aid, delayRetry, function() {
  289. fixTitleAndPicEnhance0($it, $a, aid, true);
  290. });
  291.  
  292. } else { // 未找到
  293.  
  294. fixTitleAndPicEnhance1($it, $a, aid);
  295. }
  296. },
  297. onerror: function(e) {
  298. console.log("出错啦");
  299. console.log(e);
  300. }
  301. });
  302. }
  303.  
  304. /**
  305. * 修复标题和海报 增强 - 1
  306. * 使用cache库
  307. * @param {$节点} $it 当前收藏Item
  308. * @param {$节点} $a 标题链接
  309. * @param {数字} aid av号
  310. */
  311. function fixTitleAndPicEnhance1($it, $a, aid) {
  312.  
  313. GM_xmlhttpRequest({
  314. method: 'GET',
  315. url: `https://www.biliplus.com/all/video/av${aid}/`,
  316. onload: function(response) {
  317.  
  318. if (isDebug) {
  319. console.log("1---->:");
  320. console.log(response.response);
  321. }
  322.  
  323. const params = response.responseText.match(/getjson\('(\/api\/view_all.+)'/);
  324.  
  325. fixTitleAndPicEnhance2($it, $a, aid, params[1]);
  326. }
  327. });
  328. }
  329.  
  330. /**
  331. * 修复标题和海报 增强 - 2
  332. * 使用cache库,第一段,需与fixTitleAndPicEnhance1连用
  333. * @param {$节点} $it 当前收藏Item
  334. * @param {$节点} $a 标题链接
  335. * @param {数字} aid av号
  336. * @param {字符串} param 待拼接参数
  337. * @param {布尔} delayRetry 延迟重试
  338. */
  339. function fixTitleAndPicEnhance2($it, $a, aid, param, delayRetry) {
  340.  
  341. GM_xmlhttpRequest({
  342. method: 'GET',
  343. url: `https://www.biliplus.com${param}`,
  344. responseType: "json",
  345. onload: function(response) {
  346.  
  347. const res = response.response;
  348.  
  349. if (isDebug) {
  350. console.log("2---->:");
  351. console.log(res);
  352. }
  353.  
  354. // 找到了
  355. if (res.code === 0) {
  356.  
  357. fixFavorites($it, $a, aid, res.data.info.title, res.data.info.pic, "all/");
  358.  
  359. } else if (res.code == -503) { // 请求过快
  360.  
  361. retryLoad($a, aid, delayRetry, function() {
  362. fixTitleAndPicEnhance2($it, $a, aid, param, true);
  363. });
  364.  
  365. } else { // 未找到
  366.  
  367. $a.text(`已失效(${aid})`);
  368. $a.attr("title", $a.text());
  369. }
  370. }
  371. });
  372. }
  373.  
  374. /**
  375. * 修复标题和海报 增强 - 3
  376. * 模拟常规查询
  377. * @param {$节点} $it 当前收藏Item
  378. * @param {$节点} $a 标题链接
  379. * @param {数字} aid av号
  380. */
  381. function fixTitleAndPicEnhance3($it, $a, aid) {
  382.  
  383. let jsonRegex;
  384.  
  385. GM_xmlhttpRequest({
  386. method: 'GET',
  387. url: `https://www.biliplus.com/video/av${aid}/`,
  388. onload: function(response) {
  389.  
  390. try {
  391.  
  392. if (isDebug) {
  393. console.log("3---->:");
  394. console.log(response.response);
  395. }
  396.  
  397. jsonRegex = response.responseText.match(/window\.addEventListener\('DOMContentLoaded',function\(\){view\((.+)\);}\);/);
  398.  
  399. if (isDebug) {
  400. console.log(jsonRegex);
  401. }
  402.  
  403. const jsonStr = jsonRegex[1];
  404.  
  405. if (isDebug) {
  406. console.log(jsonStr);
  407. }
  408.  
  409. const res = $.parseJSON(jsonStr);
  410.  
  411. if (res.title) { // 存在
  412.  
  413. fixFavorites($it, $a, aid, res.title, res.pic, "");
  414.  
  415. } else if (res.code == -503) { // 请求过快
  416.  
  417. retryLoad($a, aid, null, function() {
  418. fixTitleAndPicEnhance0($it, $a, aid, true);
  419. });
  420.  
  421. } else { // 不存在
  422.  
  423. fixTitleAndPicEnhance1($it, $a, aid);
  424. }
  425.  
  426. } catch (err) {
  427.  
  428. console.error(err);
  429. console.log(jsonRegex);
  430.  
  431. // 当出现错误时,出现手动加载
  432. retryLoad($a, aid, null, function() {
  433. fixTitleAndPicEnhance0($it, $a, aid, true);
  434. });
  435. }
  436. }
  437. });
  438. }
  439.  
  440. /**
  441. * 判断一个url是否可以访问
  442. * @param {字符串} url http地址
  443. * @param {函数} fun 有效时的回调
  444. */
  445. function isLoad(url, fun) {
  446. $.ajax({
  447. url: url,
  448. type: 'GET',
  449. success: function(response) {
  450. fun();
  451. },
  452. error: function(e) {}
  453. });
  454. }
  455.  
  456. /**
  457. * 显示详细
  458. * @param {$节点} $lis 失效收藏节点集
  459. */
  460. function showDetail($lis) {
  461.  
  462. const fidRegex = window.location.href.match(/fid=(\d+)/);
  463.  
  464. let fid;
  465.  
  466. if (fidRegex) {
  467. fid = fidRegex[1];
  468. } else {
  469. fid = $("div.fav-item.cur").attr("fid");
  470. }
  471.  
  472. const pn = $("ul.be-pager li.be-pager-item.be-pager-item-active").text();
  473.  
  474. $.ajax({
  475. url: `https://api.bilibili.com/medialist/gateway/base/spaceDetail?media_id=${fid}&pn=${pn}&ps=20&keyword=&order=mtime&type=0&tid=0&jsonp=jsonp`,
  476. success: function(json) {
  477.  
  478. const $medias = json.data.medias;
  479.  
  480. $lis.each(function(i, it) {
  481.  
  482. const bv = $(it).attr("data-aid");
  483.  
  484. const $mediaF = $medias.filter(function(it) {
  485. if (it.bvid == bv) {
  486. return it;
  487. }
  488. });
  489.  
  490. const $media = $mediaF[0];
  491.  
  492. const $a = $(it).find("a");
  493.  
  494. let titles = "";
  495.  
  496. if ($media.pages) {
  497.  
  498. const $titlesM = $media.pages.map(function(it, i, arry) {
  499. return it.title;
  500. });
  501.  
  502. titles = $titlesM.join("、");
  503. }
  504.  
  505. const aid = bv2aid(bv);
  506.  
  507. const content = `av${aid}\nP数:${$media.page}\nP${titles}\n简介:${$media.intro}`;
  508.  
  509. $($a[0]).attr("title", content);
  510.  
  511. addCopyInfoButton($(it), content);
  512. });
  513. }
  514. });
  515. }
  516.  
  517. const bvTable = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF";
  518. const bvArray = [
  519. { bvIndex: 11, bvTimes: 1 },
  520. { bvIndex: 10, bvTimes: 58 },
  521. { bvIndex: 3, bvTimes: 3364 },
  522. { bvIndex: 8, bvTimes: 195112 },
  523. { bvIndex: 4, bvTimes: 11316496 },
  524. { bvIndex: 6, bvTimes: 656356768 },
  525. ];
  526. const bvXor = 177451812;
  527. const bvAdd = 8728348608;
  528.  
  529.  
  530. /**
  531. * BV号转aid
  532. * @param {字符串} bv BV号
  533. * @return {数字} av号
  534. */
  535. function bv2aid(bv) {
  536.  
  537. const value = bvArray
  538. .map((it, i) => {
  539. return bvTable.indexOf(bv[it.bvIndex]) * it.bvTimes;
  540. })
  541. .reduce((total, num) => {
  542. return total + num;
  543. });
  544.  
  545. return (value - bvAdd) ^ bvXor;
  546. }
  547.  
  548. function tip(text, iconName) {
  549. GM_notification({
  550. text: text,
  551. image: GM_getResourceURL(iconName)
  552. });
  553. }
  554.  
  555. function tipInfo(text) {
  556. tip(text, "iconInfo");
  557. }
  558.  
  559. function tipError(text) {
  560. tip(text, "iconError");
  561. }
  562.  
  563. function tipSuccess(text) {
  564. tip(text, "iconSuccess");
  565. }
  566.  
  567. setInterval(handleFavorites, space);
  568. })();