您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
共读 @ Bangumi。Ref: https://github.com/bangumi/scripts/tree/b0113743743dba35accb28e9b7b9da8cbbea6952/yonjar#%E7%94%A8%E6%88%B7%E8%AF%A6%E6%83%85%E7%88%AC%E5%8F%96
当前为
// ==UserScript== // @name Bangumi shared book collections // @namespace http://tampermonkey.net/ // @version 1.0.0 // @author txfs19260817 // @source https://github.com/txfs19260817/bangumi-shared-book-collections // @license WTFPL // @icon https://bangumi.tv/img/favicon.ico // @match http*://*.bangumi.tv/ // @match http*://*.bgm.tv/ // @match http*://*.chii.in/ // @grant GM.xmlHttpRequest // @run-at document-end // @description 共读 @ Bangumi。Ref: https://github.com/bangumi/scripts/tree/b0113743743dba35accb28e9b7b9da8cbbea6952/yonjar#%E7%94%A8%E6%88%B7%E8%AF%A6%E6%83%85%E7%88%AC%E5%8F%96 // ==/UserScript== /******/ (() => { // webpackBootstrap /******/ "use strict"; var __webpack_exports__ = {}; ;// CONCATENATED MODULE: ./src/TabItem.ts function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class TabItem { constructor() { _defineProperty(this, "states", { loading: { text: "⏳", cursor: "wait" }, done: { text: "共读", cursor: "pointer" } }); _defineProperty(this, "li", document.createElement("li")); _defineProperty(this, "a", document.createElement("a")); // initialize this.applyState(this.states.loading); this.li.appendChild(this.a); document.getElementById('timelineTabs').appendChild(this.li); } applyState(state) { this.a.text = state.text; this.a.style.cursor = state.cursor; } loaded() { this.applyState(this.states.done); } } ;// CONCATENATED MODULE: ./src/utils.ts const parseTimestamp = s => { var _s$match, _s$match2, _s$match3; if (!s.includes("ago")) { return new Date(s); } const now = new Date(); const d = ((_s$match = s.match(/(\d+)d/i)) === null || _s$match === void 0 ? void 0 : _s$match[1]) || "0"; const h = ((_s$match2 = s.match(/(\d+)h/i)) === null || _s$match2 === void 0 ? void 0 : _s$match2[1]) || "0"; const m = ((_s$match3 = s.match(/(\d+)m/i)) === null || _s$match3 === void 0 ? void 0 : _s$match3[1]) || "0"; now.setDate(now.getDate() - +d); now.setHours(now.getHours() - +h); now.setMinutes(now.getMinutes() - +m); return now; }; const getUID = () => { return document.querySelector("#headerNeue2 > div > div.idBadgerNeue > a").href.split("user/")[1]; }; const fetchHTMLDocument = function (url) { let fetchMethod = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "GET"; return fetch(url, { method: fetchMethod, credentials: "include" }).then(r => r.text(), err => Promise.reject(err)).then(t => { const parser = new DOMParser(); return parser.parseFromString(t, "text/html"); }); }; const defaultAvatarElem = userUrl => { const avatar = document.createElement("span"); const userAvatarAnchor = document.createElement("a"); const userAvatarSpan = document.createElement("span"); userAvatarSpan.classList.add("avatarNeue", "avatarReSize40", "ll"); userAvatarSpan.style.backgroundImage = 'url("//lain.bgm.tv/pic/user/l/icon.jpg")'; userAvatarAnchor.href = userUrl; userAvatarAnchor.appendChild(userAvatarSpan); avatar.classList.add("avatar"); avatar.appendChild(userAvatarAnchor); return avatar; }; const MAX_PAGES = 5; const fetchComments = async uid => { const url = `${location.origin}/book/list/${uid}/collect`; const firstPage = await fetchHTMLDocument(url); // TODO: latest pages const maxPageNum = Math.min(MAX_PAGES, firstPage.getElementsByClassName('page_inner')[0].childElementCount - 1); const pageURLs = Array.from({ length: maxPageNum }, (_, i) => i + 1).map(i => `${url}?page=${i}`); pageURLs.shift(); // [2, ..., maxPageNum] const pages = await Promise.all(pageURLs.map(u => fetchHTMLDocument(u))); pages.unshift(firstPage); const subjectUrls = pages.flatMap(page => Array.from(page.getElementById("browserItemList").children).map(c => c.firstElementChild.href + "/comments")); const subjectCovers = pages.flatMap(page => Array.from(page.getElementById("browserItemList").children).map(c => c.getElementsByTagName('img')[0].src)); const commentPageDOMs = await Promise.all(subjectUrls.map(u => fetchHTMLDocument(u))); const commentElements = commentPageDOMs.map(p => Array.from(p.getElementsByClassName("text"))); const avatarElements = commentPageDOMs.flatMap(p => Array.from(p.querySelectorAll('#comment_box > div > .avatar'))); const uid2avatar = new Map(); avatarElements.forEach(e => { // adjust avatar span class e.firstElementChild.classList.replace("rr", "ll"); e.firstElementChild.classList.replace("avatarSize32", "avatarReSize40"); e.firstElementChild.style.marginLeft = '6px'; // parse username from href const username = e.href.split('/').at(-1); uid2avatar.set(username, e); }); const subjectTitles = pages.flatMap(page => Array.from(page.getElementById("browserItemList").children).map(c => c.getElementsByTagName('h3')[0].textContent.trim())); const data = commentElements.flatMap((cs, i) => { return cs.map(c => { const userAnchor = c.firstElementChild; return { subjectUrl: subjectUrls[i], subjectTitle: subjectTitles[i], subjectCover: subjectCovers[i], userAvatarElement: uid2avatar.get(userAnchor.href.split('/').at(-1)) ?? defaultAvatarElem(userAnchor.href), userUrl: userAnchor.href, username: userAnchor.text, date: parseTimestamp(c.getElementsByTagName('small')[0].textContent.slice(2)), comment: c.getElementsByTagName('p')[0].textContent }; }).filter(c => !c.userUrl.includes(uid)); }); data.sort((a, b) => +b.date - +a.date); return data; }; const commentDataToTLList = data => { const ul = document.createElement("ul"); const lis = data.map(d => { const li = document.createElement("li"); li.classList.add("clearit", "tml_item"); // avatar console.log(d.userAvatarElement); li.appendChild(d.userAvatarElement); // info const info = document.createElement("span"); info.classList.add("clearit", "info"); // info - cover const coverAnchor = document.createElement("a"); const coverImg = document.createElement("img"); coverImg.classList.add("rr"); coverImg.src = d.subjectCover; coverImg.height = 48; coverImg.width = 48; coverAnchor.appendChild(coverImg); info.appendChild(coverAnchor); // info - username const userAnchor = document.createElement("a"); userAnchor.href = d.userUrl; userAnchor.textContent = d.username; userAnchor.classList.add("l"); info.appendChild(userAnchor); const connector = document.createElement("span"); connector.textContent = " 读过 "; info.appendChild(connector); // info - subject const subjectAnchor = document.createElement("a"); subjectAnchor.href = d.subjectUrl; subjectAnchor.textContent = d.subjectTitle; subjectAnchor.classList.add("l"); info.appendChild(subjectAnchor); // info - comment const collectInfo = document.createElement("div"); collectInfo.classList.add("collectInfo"); const quoteDiv = document.createElement("div"); quoteDiv.classList.add("quote"); const quoteQ = document.createElement("q"); quoteQ.textContent = d.comment; quoteDiv.appendChild(quoteQ); collectInfo.appendChild(quoteDiv); info.appendChild(collectInfo); // info - date const dateP = document.createElement("p"); dateP.classList.add("date"); dateP.textContent = d.date.toLocaleString(); info.appendChild(dateP); // info done li.appendChild(info); return li; }); lis.forEach(l => { ul.appendChild(l); }); return ul; }; ;// CONCATENATED MODULE: ./src/index.ts async function main() { const uid = getUID(); const tabItem = new TabItem(); fetchComments(uid).then(r => { tabItem.loaded(); const tl = document.getElementById("timeline"); // TODO: pagination? // FIXME: ugly access tabItem.a.onclick = function () { tabItem.a.classList.add("focus"); tl.replaceChildren(commentDataToTLList(r.slice(0, 100))); }; }); } main().catch(e => { console.log(e); }); /******/ })() ;
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址