您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Searches for the book you are looking at on Goodreads across all your libby libraries
// ==UserScript== // @name Goodreads Libby Results // @namespace https://github.com/Dylancyclone/goodreads-libby-userscript // @version 1.2.1 // @description Searches for the book you are looking at on Goodreads across all your libby libraries // @author Dylancyclone // @match https://libbyapp.com/interview/menu // @include /^https?://.*\.goodreads\.com/book/show.*$/ // @icon https://www.google.com/s2/favicons?sz=64&domain=libbyapp.com // @grant GM.setValue // @grant GM.getValue // @license MIT // ==/UserScript== window.addEventListener( "load", function () { ;(function () { "use strict" const syncLibraries = () => { // Grab libraries from libby and remove circular references let libraries = unsafeWindow.APP.libraries.all.map((library) => { return { baseKey: library.baseKey, _: { activeKey: library._.activeKey, name: library._.name }, } }) libraries = JSON.stringify(libraries) GM.setValue("libraries", libraries) } const createLibbyButton = () => { let builderDiv = document.createElement("div") builderDiv.innerHTML = ` <div class="summary-list-action"> <button class="summary-list-action-add-library halo" role="button" type="button"> <span role="text">Save Libraries (userscript)</span> </button> </div> `.trim() let libbySyncButton = builderDiv.firstChild libbySyncButton.onclick = syncLibraries return libbySyncButton } const renderSearchResults = async ( libraries, searchString, targetDiv, openInOverdrive ) => { targetDiv.innerHTML = "" if (libraries.length === 0) { targetDiv.innerHTML = `No libraries found, please visit <a href="https://libbyapp.com/interview/menu" target="_blank">here</a> to sync your libraries.` } libraries.map((library) => { let libraryKey = library._.activeKey || library.baseKey let url = `https://thunder.api.overdrive.com/v2/libraries/${libraryKey}/media?query=${searchString}` fetch(url) .then((response) => response.json()) .then((result) => { let ebookCount = result.items.filter( (item) => item.type.id === "ebook" ).length let audiobookCount = result.items.filter( (item) => item.type.id === "audiobook" ).length let link = openInOverdrive ? `https://${library.baseKey}.overdrive.com/search?query=${searchString}` : `https://libbyapp.com/search/${library.baseKey}/search/query-${searchString}/page-1` targetDiv.innerHTML += ` <tr> <td style="padding-right: 20px;">${library._.name}</td> <td> <a href="${link}" target="_blank"> ${ebookCount || "-"} 📕 / ${audiobookCount || "-"} 🎧 </a> </td> </tr>` }) }) } const createGoodreadsResults = async () => { let builderDiv = document.createElement("div") let bookTitle = document .querySelector("[data-testid='bookTitle']") .innerHTML.trim() let bookAuthor = document .querySelector("[data-testid='name']") .innerHTML.trim() let searchString = encodeURIComponent(`${bookTitle} ${bookAuthor}`) let libraries = JSON.parse(await GM.getValue("libraries", "[]")) let openInOverdrive = JSON.parse( await GM.getValue("openInOverdrive", "false") ) builderDiv.innerHTML = ` <div style=" background-color: #ececec; border: 1px solid black; margin-top: 25px; padding: 1em;" > <h3>Libby results</h3> <table id="libby-results"> <tr> <th style="margin-right: 20px;">Library</th> <th>Results</th> </tr> </table> <div style="margin-top: 10px;"> <p> Open links in: </p> <input type="radio" id="openInLibbyButton" name="openLinksIn" value="libby" ${ !openInOverdrive ? "checked" : "" }> <label for="libby">Libby</label> <input type="radio" id="openInOverdriveButton" name="openLinksIn" value="overdrive" ${ openInOverdrive ? "checked" : "" }> <label for="overdrive">Overdrive</label> </div> </div> `.trim() let libbyResults = builderDiv.querySelector("#libby-results") let openInLibbyButton = builderDiv.querySelector("#openInLibbyButton") openInLibbyButton.onclick = () => { GM.setValue("openInOverdrive", "false") renderSearchResults(libraries, searchString, libbyResults, false) } let openInOverdriveButton = builderDiv.querySelector( "#openInOverdriveButton" ) openInOverdriveButton.onclick = () => { GM.setValue("openInOverdrive", "true") renderSearchResults(libraries, searchString, libbyResults, true) } renderSearchResults( libraries, searchString, libbyResults, openInOverdrive ) return builderDiv.firstChild } /** * Add the buttons * Might outrun the rest of the dom, * so keep retrying until the container is ready */ const addLibbyButton = () => { let container = document.getElementsByClassName("summary-list-section-actions") if (container && container[0]) { container[0].parentNode.insertBefore( createLibbyButton(), container[0].nextSibling ) } else { setTimeout(addLibbyButton, 10) } } const addGoodreadsResults = async () => { let container = document.getElementsByClassName("BookDetails") if (container && container[0]) { createGoodreadsResults().then((goodreadsResults) => { let test = container[0].parentNode.insertBefore( goodreadsResults, container[0].nextSibling ) }) } else { setTimeout(addGoodreadsResults, 10) } } if (unsafeWindow.location.host == "libbyapp.com") { addLibbyButton() } else if (unsafeWindow.location.host == "www.goodreads.com") { addGoodreadsResults() } })() }, false )