修复 哔哩哔哩(www.bilibili.com) 失效的收藏。(可查看av号、简介、标题、封面)
// ==UserScript== // @name 哔哩哔哩(B站|Bilibili)收藏夹Fix // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description 修复 哔哩哔哩(www.bilibili.com) 失效的收藏。(可查看av号、简介、标题、封面) // @author Mr.Po // @match https://space.bilibili.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js // @resource iconError https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/error.png // @resource iconSuccess https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/success.png // @resource iconInfo https://cdn.jsdelivr.net/gh/Mr-Po/bilibili-favorites-fix/media/info.png // @connect biliplus.com // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_setClipboard // @grant GM_getResourceURL // ==/UserScript== /*jshint esversion: 8 */ (function() { 'use strict'; /** * 失效收藏标题颜色(默认为灰色)。 * @type {String} */ const invalTitleColor = "#999"; /** * 是否启用调试模式。 * 启用后,浏览器控制台会显示此脚本运行时的调试数据。 * @type {Boolean} */ const isDebug = false; /** * 重试延迟[秒]。 * @type {Number} */ const retryDelay = 5; /** * 每隔 space [毫秒]检查一次,是否有新的收藏被加载出来。 * 此值越小,检查越快;过小会造成浏览器卡顿。 * @type {Number} */ const space = 2000; /******************************************************/ /** * 收藏夹地址正则 * @type {RegExp} */ const favlistRegex = /https:\/\/space\.bilibili\.com\/\d+\/favlist.*/; /** * 处理收藏 */ function handleFavorites() { const flag = favlistRegex.test(window.location.href); if (flag) { // 当前页面是收藏地址 // 失效收藏节点集 const $lis = $("ul.fav-video-list.content li.small-item.disabled"); if ($lis.size() > 0) { console.info(`${$lis.size()}个收藏待修复...`); $lis.each(function(i, it) { const bv = $(it).attr("data-aid"); const aid = bv2aid(bv); // 多个超链接 const $as = $(it).find("a"); $as.attr("href", `https://www.biliplus.com/video/av${aid}/`); $as.attr("target", "_blank"); addCopyAVCodeButton($(it), aid); fixTitleAndPic($(it), $($as[1]), aid); // 移除禁用样式 $(it).removeClass("disabled"); $as.removeClass("disabled"); }); showDetail($lis); } } } function addOperation($item, name, fun) { const $ul = $item.find(".be-dropdown-menu").first(); const lastChild = $ul.children().last(); // 未添加过扩展 if (!lastChild.hasClass('be-dropdown-item-extend')) { lastChild.addClass("be-dropdown-item-delimiter"); } const $li = $(`<li class="be-dropdown-item be-dropdown-item-extend">${name}</li>`); $li.click(fun); $ul.append($li); } function addCopyAVCodeButton($item, aid) { addOperation($item, "复制av号", function() { GM_setClipboard(`av${aid}`, "text"); tipSuccess("av号复制成功!"); }); } function addCopyInfoButton($item, content) { addOperation($item, "复制简介", function() { GM_setClipboard(content, "text"); tipSuccess("简介复制成功!"); }); } /** * 标记失效的收藏 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 */ function signInval($it, $a) { // 收藏时间 const $pubdate = $it.find("div.meta.pubdate"); // 增加 删除线 $pubdate.attr("style", "text-decoration:line-through"); // 增加 删除线 + 置(灰) $a.attr("style", `text-decoration:line-through;color:${invalTitleColor};`); } /** * 绑定重新加载 * @param {$节点} $a 标题链接 * @param {函数} fun 重试方法 */ function bindReload($a, fun) { $a.text("->手动加载<-"); $a.click(function() { $(this).unbind("click"); $a.text("Loading..."); fun(); }); } /** * 再次尝试加载 * @param {$节点} $a 标题链接 * @param {数字} aid AV号 * @param {布尔} delayRetry 延迟重试 * @param {函数} fun 重试方法 */ function retryLoad($a, aid, delayRetry, fun) { console.warn(`查询:av${aid},请求过快!`); if (delayRetry) { // 延迟绑定 $a.text(`请求过快,${retryDelay}秒后再试!`); setTimeout(bindReload, retryDelay * 1000, $a, fun); countdown($a, retryDelay); } else { // 首次,立即绑定 $a.attr("href", "javascript:void(0);"); bindReload($a, fun); } } /** * 重新绑定倒计时 * @param {$节点} $a 标题链接 * @param {数字} second 秒 */ function countdown($a, second) { if ($a.text().indexOf("请求过快") === 0) { $a.text(`请求过快,${second}秒后再试!`); if (second > 1) { setTimeout(countdown, 1000, $a, second - 1); } } } /** * 修复收藏 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 * @param {字符串} title 标题 * @param {字符串} pic 海报 * @param {字符串} history 历史归档,若无时,使用空字符串 */ function fixFavorites($it, $a, aid, title, pic, history) { // 设置标题 $a.text(title); $a.attr("title", $a.text()); // 多个超链接 const $as = $it.find("a"); $as.attr("href", `https://www.biliplus.com/${history}video/av${aid}/`); signInval($it, $a); // 判断海报链接是否有效,有效时进行替换 isLoad(pic, function() { const $img = $it.find("img"); $img.attr("src", pic); }); } /** * 修复标题和海报 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 */ function fixTitleAndPic($it, $a, aid) { $a.text("Loading..."); fixTitleAndPicEnhance3($it, $a, aid); } /** * 修复标题和海报 增强 - 0 * 使用公开的API * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 * @param {布尔} delayRetry 延迟重试 */ function fixTitleAndPicEnhance0($it, $a, aid, delayRetry) { GM_xmlhttpRequest({ method: 'GET', url: `https://www.biliplus.com/api/view?id=${aid}`, responseType: "json", onload: function(response) { const res = response.response; if (isDebug) { console.log("0---->:"); console.log(res); } // 找到了 if (res.title) { fixFavorites($it, $a, aid, res.title, res.pic, ""); } else if (res.code == -503) { // 请求过快 retryLoad($a, aid, delayRetry, function() { fixTitleAndPicEnhance0($it, $a, aid, true); }); } else { // 未找到 fixTitleAndPicEnhance1($it, $a, aid); } }, onerror: function(e) { console.log("出错啦"); console.log(e); } }); } /** * 修复标题和海报 增强 - 1 * 使用cache库 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 */ function fixTitleAndPicEnhance1($it, $a, aid) { GM_xmlhttpRequest({ method: 'GET', url: `https://www.biliplus.com/all/video/av${aid}/`, onload: function(response) { if (isDebug) { console.log("1---->:"); console.log(response.response); } const params = response.responseText.match(/getjson\('(\/api\/view_all.+)'/); fixTitleAndPicEnhance2($it, $a, aid, params[1]); } }); } /** * 修复标题和海报 增强 - 2 * 使用cache库,第一段,需与fixTitleAndPicEnhance1连用 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 * @param {字符串} param 待拼接参数 * @param {布尔} delayRetry 延迟重试 */ function fixTitleAndPicEnhance2($it, $a, aid, param, delayRetry) { GM_xmlhttpRequest({ method: 'GET', url: `https://www.biliplus.com${param}`, responseType: "json", onload: function(response) { const res = response.response; if (isDebug) { console.log("2---->:"); console.log(res); } // 找到了 if (res.code === 0) { fixFavorites($it, $a, aid, res.data.info.title, res.data.info.pic, "all/"); } else if (res.code == -503) { // 请求过快 retryLoad($a, aid, delayRetry, function() { fixTitleAndPicEnhance2($it, $a, aid, param, true); }); } else { // 未找到 $a.text(`已失效(${aid})`); $a.attr("title", $a.text()); } } }); } /** * 修复标题和海报 增强 - 3 * 模拟常规查询 * @param {$节点} $it 当前收藏Item * @param {$节点} $a 标题链接 * @param {数字} aid av号 */ function fixTitleAndPicEnhance3($it, $a, aid) { let jsonRegex; GM_xmlhttpRequest({ method: 'GET', url: `https://www.biliplus.com/video/av${aid}/`, onload: function(response) { try { if (isDebug) { console.log("3---->:"); console.log(response.response); } jsonRegex = response.responseText.match(/window\.addEventListener\('DOMContentLoaded',function\(\){view\((.+)\);}\);/); if (isDebug) { console.log(jsonRegex); } const jsonStr = jsonRegex[1]; if (isDebug) { console.log(jsonStr); } const res = $.parseJSON(jsonStr); if (res.title) { // 存在 fixFavorites($it, $a, aid, res.title, res.pic, ""); } else if (res.code == -503) { // 请求过快 retryLoad($a, aid, null, function() { fixTitleAndPicEnhance0($it, $a, aid, true); }); } else { // 不存在 fixTitleAndPicEnhance1($it, $a, aid); } } catch (err) { console.error(err); console.log(jsonRegex); // 当出现错误时,出现手动加载 retryLoad($a, aid, null, function() { fixTitleAndPicEnhance0($it, $a, aid, true); }); } } }); } /** * 判断一个url是否可以访问 * @param {字符串} url http地址 * @param {函数} fun 有效时的回调 */ function isLoad(url, fun) { $.ajax({ url: url, type: 'GET', success: function(response) { fun(); }, error: function(e) {} }); } /** * 显示详细 * @param {$节点} $lis 失效收藏节点集 */ function showDetail($lis) { const fidRegex = window.location.href.match(/fid=(\d+)/); let fid; if (fidRegex) { fid = fidRegex[1]; } else { fid = $("div.fav-item.cur").attr("fid"); } const pn = $("ul.be-pager li.be-pager-item.be-pager-item-active").text(); $.ajax({ 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`, success: function(json) { const $medias = json.data.medias; $lis.each(function(i, it) { const bv = $(it).attr("data-aid"); const $mediaF = $medias.filter(function(it) { if (it.bvid == bv) { return it; } }); const $media = $mediaF[0]; const $a = $(it).find("a"); let titles = ""; if ($media.pages) { const $titlesM = $media.pages.map(function(it, i, arry) { return it.title; }); titles = $titlesM.join("、"); } const aid = bv2aid(bv); const content = `av:${aid}\nP数:${$media.page}\n子P:${titles}\n简介:${$media.intro}`; $($a[0]).attr("title", content); addCopyInfoButton($(it), content); }); } }); } const bvTable = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"; const bvArray = [ { bvIndex: 11, bvTimes: 1 }, { bvIndex: 10, bvTimes: 58 }, { bvIndex: 3, bvTimes: 3364 }, { bvIndex: 8, bvTimes: 195112 }, { bvIndex: 4, bvTimes: 11316496 }, { bvIndex: 6, bvTimes: 656356768 }, ]; const bvXor = 177451812; const bvAdd = 8728348608; /** * BV号转aid * @param {字符串} bv BV号 * @return {数字} av号 */ function bv2aid(bv) { const value = bvArray .map((it, i) => { return bvTable.indexOf(bv[it.bvIndex]) * it.bvTimes; }) .reduce((total, num) => { return total + num; }); return (value - bvAdd) ^ bvXor; } function tip(text, iconName) { GM_notification({ text: text, image: GM_getResourceURL(iconName) }); } function tipInfo(text) { tip(text, "iconInfo"); } function tipError(text) { tip(text, "iconError"); } function tipSuccess(text) { tip(text, "iconSuccess"); } setInterval(handleFavorites, space); })();