Goodreads Libby Results

Searches for the book you are looking at on Goodreads across all your libby libraries

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
)