B 币#取提醒、稍后再看列表导出为 *.url 等
// ==UserScript== // @name 「bilibili」水水自用 | B 币#取提醒、播放进度更新至网址等; // @namespace wdssmq.com // @version 1.0.1 // @author 沉冰浮水 // @description B 币#取提醒、稍后再看列表导出为 *.url 等 // @url https://greasyfork.org/scripts/398415 // @null ---------------------------- // @contributionURL https://github.com/wdssmq#%E4%BA%8C%E7%BB%B4%E7%A0%81 // @contributionAmount 5.93 // @null ---------------------------- // @link https://github.com/wdssmq/userscript // @link https://afdian.net/@wdssmq // @link https://greasyfork.org/zh-CN/users/6865-wdssmq // @null ---------------------------- // @include https://www.bilibili.com/* // @include https://t.bilibili.com/* // @include https://manga.bilibili.com/account-center* // @include https://account.bilibili.com/account/big/myPackage // @match https://space.bilibili.com/44744006/fans/follow* // @icon https://www.bilibili.com/favicon.ico // @noframes // @run-at document-end // @grant GM_setClipboard // @grant GM_notification // @grant GM.openInTab // ==/UserScript== /* jshint esversion: 6 */ /* eslint-disable */ (function () { 'use strict'; const gm_name = "later"; // 初始常量或函数 const curUrl = window.location.href; const curDate = new Date(); // --------------------------------------------------- const _curUrl = () => { return window.location.href }; const _getDateStr = (date = curDate) => { const options = { year: "numeric", month: "2-digit", day: "2-digit" }; return date.toLocaleDateString("zh-CN", options).replace(/\//g, "-"); }; // --------------------------------------------------- const _log = (...args) => console.log(`[${gm_name}]\n`, ...args); const _warn = (...args) => console.warn(`[${gm_name}]\n`, ...args); // --------------------------------------------------- // const $ = window.$ || unsafeWindow.$; function $n(e) { return document.querySelector(e); } function $na(e) { return document.querySelectorAll(e); } // --------------------------------------------------- // 添加内容到指定元素后面 function fnAfter($ne, e) { const $e = typeof e === "string" ? $n(e) : e; $e.parentNode.insertBefore($ne, $e.nextSibling); } // 指定元素内查找子元素 function fnFindDom(el, selector) { el = typeof el === "string" ? $n(el) : el; const queryList = el.querySelectorAll(selector); if (queryList.length === 1) { return queryList[0]; } return queryList.length > 1 ? queryList : null; } // --------------------------------------------------- // 元素变化监听 const fnElChange = (el, fn = () => { }, onetime = true) => { const observer = new MutationObserver((mutationRecord, mutationObserver) => { // _log('mutationRecord = ', mutationRecord); // _log('mutationObserver === observer', mutationObserver === observer); fn(mutationRecord, mutationObserver); if (onetime) { mutationObserver.disconnect(); // 取消监听,正常应该在回调函数中根据条件决定是否取消 } }); observer.observe(el, { // attributes: false, // attributeFilter: ["class"], childList: true, // characterData: false, subtree: true, }); }; // 点击指定元素复制内容 function fnCopy(eTrig, content, fnCB = () => { }) { // 判断 eTrig 是否为字符串 const $eTrig = typeof eTrig === "string" ? $n(eTrig) : eTrig; $eTrig.addEventListener("click", function (e) { GM_setClipboard(content); fnCB(e); this.style.color = "gray"; }); } // cookie 封装 const ckeObj = { setItem: function (key, value) { const Days = 137; const exp = new Date(); exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); document.cookie = key + "=" + encodeURIComponent(value) + ";path=/;domain=.bilibili.com;expires=" + exp.toGMTString(); }, getItem: function (key, def = "") { const reg = new RegExp("(^| )" + key + "=([^;]*)(;|$)"); const arr = document.cookie.match(reg); if (arr) { return decodeURIComponent(arr[2]); } return def; }, }; /* global GM_notification GM */ // 日期转字符串 const getDateStr = (date) => { const options = { year: "numeric", month: "2-digit", day: "2-digit" }; return date.toLocaleDateString("zh-CN", options); }; // 判断日期间隔 const diffDateDays = (date1, date2) => { const diff = date1.getTime() - date2.getTime(); return diff / (1000 * 60 * 60 * 24); }; // B 币#取提醒 (() => { const ckeName = "bilibili-helper-bcoin-nxtDateStr"; const curDateStr = getDateStr(curDate); const nxtDateStr = ckeObj.getItem(ckeName, curDateStr); const bcoinUrl = "https://account.bilibili.com/account/big/myPackage"; _log(`当前日期: ${curDateStr}`); _log(`下次#取: ${nxtDateStr}`); // 通知事件封装 const notify = (title, body) => { _log(`通知标题: ${title}`); GM_notification({ title: title, text: body, timeout: 0, onclick: () => { // window.location.href = bcoinUrl; GM.openInTab(bcoinUrl, false); }, }); }; // 判断是否已经#取过 const fnCheckByDOM = () => { const $bcoin = $n(".bcoin-wrapper"); // _log("fnCheckByDOM", $bcoin); // $bcoin && _log("fnCheckByDOM", $bcoin.innerHTML); if ($bcoin && $bcoin.innerText.includes("本次已#")) { const match = $bcoin.innerText.match(/\d{4}\/\d+\/\d+/); if (match && match[0] !== nxtDateStr) { ckeObj.setItem(ckeName, match[0]); _log("已#取过,更新下次#取日期", match[0]); return true; } } else { fnElChange($n("body"), fnCheckByDOM); } return false; }; // _log($n("body").innerHTML); // _log(nxtDateStr, curMonth); // 对比日期 const iniDiff = diffDateDays(curDate, new Date(nxtDateStr)); if (iniDiff > 0) { _log(curUrl, "\n", bcoinUrl); if (curUrl.indexOf(bcoinUrl) > -1) { fnCheckByDOM(); } else { notify("B 币#取提醒", "点击查看 B 币#取情况"); } } })(); // 番剧链接改为我的追番 (() => { let isDone = false; // 获取 uid const getUidByUrlOrCookie = (url) => { let uid = null; const match = url.match(/\d+/); if (match) { uid = match[0]; ckeObj.setItem("bilibili-helper-uid", uid); } else { uid = ckeObj.getItem("bilibili-helper-uid"); } return uid; }; // 更新链接 const fnCheckByDOM = () => { if (!isDone) { fnElChange($n("body"), fnCheckByDOM); } const $pick = $n("a[href*='/anime/']"); const $pick2 = $n("a.header-entry-avatar"); const uid = $pick2 ? getUidByUrlOrCookie($pick2.href) : null; if (!$pick || !$pick2 || !uid) { return; } // debug // _warn($pick, $pick2); const url = `https://space.bilibili.com/${uid}/bangumi`; $pick.href = url; _warn("番剧链接改为我的追番", url); isDone = true; }; fnCheckByDOM(); })(); _log("_later2url.js", "开始"); // 构造 Bash Shell 脚本 function fnMKShell(arrList, prefix = "") { const curDateStr = _getDateStr(); let strRlt = "if [ ! -d \"prefix-date\" ]; then\n" + "mkdir prefix-date\n" + "fi\n" + "cd prefix-date\n\n"; strRlt = strRlt.replace(/prefix/g, prefix); strRlt = strRlt.replace(/date/g, curDateStr); /** * e {title:"", href:""} */ arrList.forEach(function (e, i) { const serial = i + 1; // _log(e); // 移除不能用于文件名的字符 let title = e.title || e.innerText; title = title.replace(/\\|\/|:|\*|!|\?]|<|>/g, ""); title = title.replace(/["'\s]/g, ""); // _log(title); const lenTitle = title.length; if (lenTitle >= 155) { title = `标题过长丨${lenTitle}`; } // 获取文章链接 const href = e.href || e.url; // url 文件名 const urlFileName = `${serial}丨${title}.url`; strRlt += `echo [InternetShortcut] > "${urlFileName}"\n`; strRlt += `echo "URL=${href}" >> "${urlFileName}"\n`; strRlt += "\n"; }); { strRlt += "exit\n\n"; } return strRlt; } // Ajax 封装 function fnGetAjax(callback = function () { }) { $.ajax({ url: "https://api.bilibili.com/x/v2/history/toview/web", type: "GET", xhrFields: { withCredentials: true, // 这里设置了withCredentials }, success: function (data) { // _log(); callback(data.data.list); }, error: function (err) { console.error(err); }, }); } // 分 p 链接获取 (() => { const selectorMap = { epListBox: ".eplist_ep_list_wrapper__PzLHa", epListTitle: ".eplist_list_title__JwP3d h4", // epList: ".imageList_wrap__pDHvN", epList: ".imageList_wrap__pDHvN .imageListItem_wrap__HceXs a", }; const $$epListBox = $na(selectorMap.epListBox); if ($$epListBox.length === 0) { return; } const fnGetLinkList = ($epList) => { // _log("fnOnCLick", $epList); const arrList = Array.prototype.map.call($epList, ($item, index) => { const href = $item.href; const title = $item.innerText.replace(/\n.+/g, ""); return { href, title, }; }); _log("arrList", arrList); return arrList; }; const elListDom = []; // 遍历 NodeList,不能直接使用 forEach Array.prototype.forEach.call($$epListBox, ($epListBox, index) => { const $epListTitle = fnFindDom($epListBox, selectorMap.epListTitle); const $epList = fnFindDom($epListBox, selectorMap.epList); const textTitle = $epListTitle.innerText; // 注册点击复制 fnCopy($epListTitle, fnMKShell(fnGetLinkList($epList), textTitle)); elListDom.push({ box: $epListBox, title: $epListTitle, list: $epList, }); }); console.log(elListDom); })(); // 导出稍后再看为 .lnk 文件 (function () { // 跳转到标准播放页 const urlMatch = /list\/watchlater\?bvid=(\w+)/.exec(location.href); if (urlMatch) { const bvid = urlMatch[1]; location.href = `https://www.bilibili.com/video/${bvid}`; return; } if (/watchlater/.test(location.href)) { let tmpHTML = $("span.t").html(); fnGetAjax(function (list) { const arrRlt = []; list.forEach((item, index) => { arrRlt.push({ title: item.title, href: `https://www.bilibili.com/video/${item.bvid}`, bvid: item.bvid, }); // _log(item, index); }); // _log("稍后再看", arrRlt.length); tmpHTML = tmpHTML.replace(/0\//g, arrRlt.length + "/"); $("span.t").html(tmpHTML + "「点击这里复制 bash shell 命令」"); let appCon = "「已复制」"; if (arrRlt.length > 37) { appCon = "「已复制,数量过多建议保存为 .sh 文件执行」"; } // 注册点击复制 fnCopy("span.t", fnMKShell(arrRlt, "bilibili"), () => { $("span.t").html(tmpHTML + appCon); }); }); return false; } })(); _log("_later2url.js", "结束"); // 关注列表增强 (() => { const gob = { done: false, _rssUrl: (uid) => { let RSSHub = localStorage.RSSHub ? localStorage.RSSHub : "https://rsshub.app/bilibili/user/video/:uid/"; localStorage.RSSHub = RSSHub; return RSSHub.replace(":uid", uid); }, }; // 获取关注列表 const fnGetFollowList = () => { let $list = $na("li.list-item"); return $list; }; // 针对每个关注列表项 const fnUpView = ($up) => { const url = $up.querySelector("a.cover").href; const name = $up.querySelector("span.fans-name").innerText; const { uid, uname } = ((url, name) => { const uid = url.match(/\/(\d+)\/$/)[1]; let uname = name.replace(/\b/g, "_").replace(/_+/g, "_"); uname = uname.replace(/^_|_$/g, ""); // _log(`${uid} ${uname}`); return { uid, uname }; })(url, name); const $desc = $up.querySelector("p.auth-description") || $up.querySelector("p.desc"); if ($desc && $desc.dataset.fnUpView === "done") { return; } $desc.dataset.fnUpView = "done"; // _log($desc); // 创建新 p 元素 const $p = document.createElement("p"); const RSSUrl = gob._rssUrl(uid); $p.className = "desc"; $p.innerHTML = ` <hr> <a href="${RSSUrl}" target="_blank">「RSS」</a> | ${uid} ${uname} `; fnAfter($p, $desc); }; // 页面元素更新监听 const fnCheckByDOM = () => { const $ul = $n("ul.relation-list"); const $list = fnGetFollowList(); if ($list.length > 0 && !$ul.dataset.fnCheckByDOM !== "done") { $ul.dataset.fnCheckByDOM = "done"; _log("$list.length = ", $list.length); // nodeList to array const $listArr = Array.from($list); $listArr.forEach(($li) => { fnUpView($li); }); } fnElChange($n("body"), fnCheckByDOM); }; fnCheckByDOM(); })(); // 姑且引入一些大概会用到的函数 ↑ const $body = $n("body"); // 设置一个标记 let isDone = false; // 用来实现实际功能的函数 const fnMain = () => { // 视频播放时页面一直在变化,所以这里会一直调用 _warn("_cover => fnMain"); const $listCover = $na(".header-history-video__image"); // isDone 则控制这里只执行一次 if ($listCover.length > 0 && !isDone) { isDone = true; Array.from($listCover).forEach(($cover, index) => { // _warn(`.header-history-video__image[${index}] = `, $cover); // _warn("---"); const $coverImg = $cover.querySelector("img"); const $coverSource = $cover.querySelector("source"); // src 或 srcset 属性 let imgUrl = $coverImg.src; let imgSrcset = $coverSource.getAttribute("srcset"); _warn("imgUrl = ", imgUrl); _warn("imgSrcset = ", imgSrcset); // 使用正则替换去除后边的内容 imgUrl = imgUrl.replace(/@.+$/, ""); imgSrcset = imgSrcset.replace(/@.+$/, ""); // 设置回去 $coverImg.src = imgUrl; $coverSource.setAttribute("srcset", imgSrcset); }); } }; // 当页面内容产生变化时,触发函数 fnMain fnElChange($body, fnMain, false); // 时间轴书签 const gob = { lstTime: 0, curTime: 0, curPart: 0, isRunning: false, title: "", step: 73, }; const fnUpTitle = (part, time) => { if (gob.title === "") { gob.title = $n("h1.video-title").innerHTML; } const strAdd = ((obj) => { let str = ""; for (const key in obj) { if (Object.hasOwnProperty.call(obj, key)) { const val = obj[key]; if (!val) { continue; } const s = key === "part" ? "P" : "播放至 "; str += `_${s}${val}`; } } return str; })({ part, time }); $n("title").innerHTML = `${gob.title}${strAdd}_bilibili`; // debug _warn(`title: ${$n("title").innerHTML}`); }; const fnUpUrl = (p, time) => { let url = _curUrl(); let urlNew = url; // 清理参数 url = url.split("?")[0]; // _warn("fnUpUrl", { p, time }); // 新参数构造 const params = new URLSearchParams(); for (const key in { p, time }) { if (Object.hasOwnProperty.call({ p, time }, key)) { const item = { p, time }[key]; if (item) { params.append(key, item); } } } // 拼接参数 urlNew = `${url}?${params.toString()}`; // 更新至地址栏 window.history.pushState(null, null, urlNew); // debug _warn(`url: ${urlNew}`); }; const fnGetTime = () => { const $timLabel = $n(".bpx-player-ctrl-time-label"); const $curTime = $n(".bpx-player-ctrl-time-current"); if ($timLabel && $curTime) { const str = $curTime.innerHTML; const arr = str.split(":"); if (arr.length === 3) { return parseInt(arr[0]) * 3600 + parseInt(arr[1]) * 60 + parseInt(arr[2]); } else { return parseInt(arr[0]) * 60 + parseInt(arr[1]); } } return 0; }; const fnGetPart = () => { const $list = $n("ul.list-box"); const $cur = $list?.querySelector("li.on"); const $curHref = $cur?.querySelector("a"); if ($cur && $curHref) { // _warn($cur, $curHref); const str = $curHref.href; const arr = str.split("?p="); if (arr.length === 2) { return parseInt(arr[1]); } } return null; }; const fnDelay = (time, fnc = () => { }) => { if (gob.isRunning) { return; } gob.isRunning = true; setTimeout(() => { fnc(); gob.isRunning = false; }, time); }; document.addEventListener( "mouseover", function (e) { // const $target = e.target; fnDelay(1000, () => { // bpx-player-container bpx-state-no-cursor const $container = $n(".bpx-player-container"); let bolFlag = false; if ($container && !$container.classList.contains("bpx-state-no-cursor")) { gob.curTime = fnGetTime(); gob.curPart = fnGetPart(); if (gob.curTime > 0 && gob.curTime - gob.lstTime > gob.step) { bolFlag = true; } if (gob.curTime < gob.lstTime) { bolFlag = true; } if (gob.lstTime <= parseInt(gob.step / 4)) { bolFlag = true; } if (bolFlag) { fnUpTitle(gob.curPart, gob.curTime); fnUpUrl(gob.curPart, gob.curTime); gob.lstTime = gob.curTime; } // _warn("进度条触发", e.target); // _warn("进度条触发", gob); return; } }); }, false, ); })();