您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
改编自https://github.com/OldPanda/douban-book-plus-homepage
// ==UserScript== // @name 豆瓣书籍一键跳转 // @description 改编自https://github.com/OldPanda/douban-book-plus-homepage // @namespace namespace // @match *://book.douban.com/subject/* // @version 1.5.1 // @author - // @license WTFPL // @grant GM.getValue // @grant GM.setValue // @grant GM.xmlhttpRequest // @grant GM.getResourceURL // @resource logo_douban https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/douban-logo.svg // @resource logo_duokan https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/duokan-logo.png // @resource logo_snail https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/snailreader-logo.png // @resource logo_dedao https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/dedao-logo.png // @resource logo_zlibrary https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/zlibrary-logo.png // @resource logo_anna https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/anna-logo.svg // @resource logo_weread https://github.com/OldPanda/douban-book-plus-homepage/raw/refs/heads/master/docs/public/weread-logo.png // ==/UserScript== const vendors = [ "weread", // "kindle", "duokan", "snail", "douban", "dedao", "zlibrary", "anna" ]; const preferencesKey = "douban-book-plus-preferences"; const apiEndpoint = "https://api.old-panda.com/GetEBookUrls"; const dedaoPrefix1 = "https://www.dedao.cn/reader"; const dedaoPrefix2 = "https://www.dedao.cn/ebook/reader"; const doubanPrefix = "https://read.douban.com/reader/ebook"; function simple_fetch(url, method = "GET") { return new Promise((resolve, reject) => { GM.xmlhttpRequest({ url: url, method: method, responseType: "json", onload(res) { try { if (res.status == 200) { resolve(res.response); } else { reject(new DOMException("Failed to fetch: response code is " + res.status)); } } catch (e) { reject(e); } }, onabort() { reject(new DOMException("Aborted", "AbortError")); }, ontimeout() { reject(new TypeError("Network request failed, timeout")); }, onerror(err) { reject(new TypeError("Failed to fetch: " + err.finalUrl)); }, }); }) } async function background(message) { let ebookLinks = message.ebooks; let dedao = null; let douban = null; for (let link of ebookLinks) { if (link.startsWith(dedaoPrefix1) || link.startsWith(dedaoPrefix2)) { dedao = link; } else if (link.startsWith(doubanPrefix)) { douban = link; } } let params = new URLSearchParams({ "isbn": message.isbn, "title": message.title, "authors": message.authors, "publisher": message.publisher, "douban_url": message.doubanURL, }); if (message.subtitle !== null) { params.append("subtitle", message.subtitle); } if (message.translators !== null) { params.append("translators", message.translators); } if (dedao !== null) { params.append("dedao", dedao); } if (douban !== null) { params.append("douban", douban); } let url = apiEndpoint + "?" + params.toString(); // fetch preferences first, then remove the vendors // which are turned off by users let resp = await simple_fetch(url); let settings = await GM.getValue("vendors", { 'weread': false }); for (let [vendor, checked] of Object.entries(settings)) { if (!checked) { delete resp[vendor]; } } return resp; } const nonAsciiChecker = str => [...str].some(char => char.charCodeAt(0) > 127); const imgSizes = { "weread": [117, 32], "duokan": [109, 32], "snail": [129, 32], "douban": [99, 32], "dedao": [80, 32], "zlibrary": [126, 32], "anna": [200, 32], "nobook": [150, 32], }; let getValue = (item) => { return item.split(":")[1].trim(); }; let parseNames = (names) => { return names.split("/") .map((item) => item.trim()) .filter((item) => item.length > 0) .map((item) => { let newItem = item; if (item.startsWith("[") || item.startsWith("【")) { let idx = 0; while (idx < item.length) { if (item[idx] == "]" || item[idx] == "】") { break; } idx++; } newItem = item.substring(idx + 1, item.length); } return newItem.trim().replace(/ /g, ""); }) .join(","); } async function main() { if (window.location.href.indexOf("book.douban.com/subject/") != -1 && window.location.href.indexOf("/comments/") === -1) { // parse basic info of the book let title = document .querySelectorAll("[property='v:itemreviewed']")[0] .textContent.trim(); if (nonAsciiChecker(title)) { title = title.replaceAll(" ", "").trim(); } let bookInfo = document.getElementById("info").innerText.split("\n"); let isbn, publisher, authors, subtitle, translators; for (let i = 0; i < bookInfo.length; i++) { if (bookInfo[i].trim().startsWith("ISBN:")) { isbn = getValue(bookInfo[i]); } else if (bookInfo[i].trim().startsWith("出版社:")) { publisher = getValue(bookInfo[i]); } else if (bookInfo[i].trim().startsWith("作者:")) { authors = getValue(bookInfo[i]); } else if (bookInfo[i].trim().startsWith("副标题:")) { subtitle = getValue(bookInfo[i]); } else if (bookInfo[i].trim().startsWith("译者:")) { translators = getValue(bookInfo[i]); } } authors = parseNames(authors); if (translators !== null && translators !== undefined) { translators = parseNames(translators); } // parse link of Douban and Dedao, if available let ebooksOnPage = document.getElementsByClassName("online-read-or-audio"); let ebookLinks = []; for (let ebook of ebooksOnPage) { let link = ebook.getElementsByTagName("a").item(0).getAttribute("href"); if (link !== null) { ebookLinks.push(link); } } if (isbn !== null && title !== null && publisher !== null && authors !== null) { let message = await background( { isbn: isbn, title: title, subtitle: subtitle, publisher: publisher, authors: authors, translators: translators, doubanURL: window.location.href, ebooks: ebookLinks } ); let found = false; if (message.hasOwnProperty("weread")) { showLink("weread", message.weread, "img/weread-logo.png"); found = true; } // if (message.hasOwnProperty("kindle")) { // showLink(message.kindle, "img/kindle-logo.png"); // } if (message.hasOwnProperty("duokan")) { showLink("duokan", message.duokan, "img/duokan-logo.png"); found = true; } if (message.hasOwnProperty("snail")) { showLink("snail", message.snail, "img/snail-logo.png"); found = true; } if (message.hasOwnProperty("douban")) { showLink("douban", message.douban, "img/douban-logo.svg"); found = true; } if (message.hasOwnProperty("dedao")) { showLink("dedao", message.dedao, "img/dedao-logo.png"); found = true; } if (message.hasOwnProperty("zlibrary")) { showLink("zlibrary", message.zlibrary, "img/zlibrary-logo.png"); found = true; } if (message.hasOwnProperty("anna")) { showLink("anna", message.anna, "img/anna-logo.svg"); found = true; } if (!found) { showLink("nobook", "", "img/no-book.png"); } } } } function initDivElement() { let ul = document.getElementById("douban-book-plus-list"); if (ul === null) { let div = document.createElement("div"); div.id = "douban-book-plus"; div.style.padding = "18px 16px"; div.style.backgroundColor = "#F6F6F2"; div.style.margin = "20px auto"; let componentTitle = document.createElement("h2"); componentTitle.innerHTML = ` <span>在线阅读</span> · · · · · · `; componentTitle.style.fontSize = "15px"; div.append(componentTitle); ul = document.createElement("ul"); ul.id = "douban-book-plus-list"; div.append(ul); let footer = document.createElement("p"); footer.style = "text-align: center; color: grey;"; footer.innerHTML = `Powered by <a href="https://doubanbook.plus/" target="_blank">Douban Book+</a>`; div.append(footer); let element = document.getElementsByClassName("aside"); element.item(0).insertBefore(div, element.item(0).firstChild); } return ul; } // add ebook logo function showLink(name, url, imgUrl) { if (url) { let ul = initDivElement(); let li = document.createElement("li"); li.style.borderBottom = "1px solid rgba(0,0,0,0.08)"; li.style.margin = "10px auto"; let a = document.createElement("a"); a.href = url; a.target = "_blank"; a.style.backgroundColor = "transparent"; let img = new Image(); if (imgUrl.startsWith("http")) { img.src = imgUrl; } else { img.src = GM.getResourceURL(`logo_${name}`); } [img.width, img.height] = imgSizes[name]; a.append(img); li.append(a); ul.append(li); } else if (name == "nobook") { let ul = initDivElement(); let li = document.createElement("li"); li.style.borderBottom = "1px solid rgba(0,0,0,0.08)"; li.style.margin = "10px auto"; let img = new Image(); if (imgUrl.startsWith("http")) { img.src = imgUrl; } else { img.src = chrome.runtime.getURL(imgUrl); } img.style = "display: block; margin-left: auto; margin-right: auto;"; [img.width, img.height] = imgSizes[name]; li.append(img); ul.append(li); } } main().then();