您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Checks if updates were available for the e-books you own.
当前为
// ==UserScript== // @name Kobo e-Books Update Checker // @name:zh-TW Kobo 電子書更新檢查器 // @description Checks if updates were available for the e-books you own. // @description:zh-TW 檢查你購買的電子書是否有更新檔提供。 // @icon https://icons.duckduckgo.com/ip3/www.kobo.com.ico // @author Jason Kwok // @namespace https://jasonhk.dev/ // @version 1.2.1 // @license MIT // @match https://www.kobo.com/*/library/books // @run-at document-end // @grant GM.addStyle // @require https://update.gf.qytechs.cn/scripts/482311/1297431/queue.js // @supportURL https://gf.qytechs.cn/scripts/482410/feedback // ==/UserScript== let MESSAGES; { const PATHNAME_PREFIX_PATTERN = /\/(?<region>[a-z]{2})\/(?<language>[a-z]{2})\//; const I18N = { en: { CHECK_UPDATE_FOR_PAGE: "Check Update for Page", COPY_OUTDATED_BOOKS: "Copy Outdated Books", NO_BOOKS_WERE_OUTDATED: "No books were outdated.", COPIED_N_BOOKS_INTO_CLIPBOARD: "Copied {0} book(s) into the clipboard.", CHECK_UPDATE: "Check Update", PENDING: "Pending...", CHECKING: "Checking...", LATEST: "Latest", OUTDATED: "Outdated", PREVIEW: "Preview", SKIPPED: "Skipped", FAILED: "Failed", BOOK_WAS_UNLISTED: "The book was unlisted, there’s no way to check update for this type of books at the moment.", BOOK_IS_AUDIOBOOK: "The book is an audiobook, there’s no way to check update for this type of books at the moment.", UNKNOWN_ERROR: "Unknown error, please contact the developer for further investigations.", }, zh: { CHECK_UPDATE_FOR_PAGE: "為本頁檢查更新", COPY_OUTDATED_BOOKS: "複製過時書籍", NO_BOOKS_WERE_OUTDATED: "沒有過時的書籍。", COPIED_N_BOOKS_INTO_CLIPBOARD: "已複製 {0} 本書到剪貼簿。", CHECK_UPDATE: "檢查更新", PENDING: "等待中…", CHECKING: "檢查中…", LATEST: "最新", OUTDATED: "過時", PREVIEW: "預覽", SKIPPED: "已略過", FAILED: "檢查失敗", BOOK_WAS_UNLISTED: "該書已下架,目前尚未有方法為這類書籍檢查更新。", BOOK_IS_AUDIOBOOK: "該書為有聲書,目前尚未有方法為這類書籍檢查更新。", UNKNOWN_ERROR: "未知錯誤,請聯絡開發者以進一步調查。", }, }; const { language } = location.pathname.match(PATHNAME_PREFIX_PATTERN).groups; MESSAGES = Object.hasOwn(I18N, language) ? I18N[language] : I18N.en; } GM.addStyle(` .library-container .update-container { text-align: right; } .library-container .update-controls { position: relative; display: inline-block; margin-top: 18px; min-width: 13rem; width: auto; } .library-container .update-button { border-radius: 20px; min-width: 0; max-width: 100%; width: auto; overflow: hidden; background-color: #eee; color: #000; font-size: 1.6rem; font-family: "Rakuten Sans UI", "Trebuchet MS", Trebuchet, Arial, Helvetica, sans-serif; font-weight: 400; text-align: left; text-overflow: ellipsis; white-space: nowrap; position: relative; white-space: nowrap; transition: background-color .3s ease-in-out, color .15s ease-in-out 0s; } .library-container .update-button:not(:first-child) { margin-left: 5px; } .library-container .update-button:not(:last-child) { margin-right: 5px; } .library-container .update-button::before { position: absolute; top: calc(50% - 30px); border-radius: 80px; width: calc(100% - 30px); height: 60px; background-color: rgba(0, 0, 0, .1); content: ""; opacity: 0; transform: scale(0); } .library-container .update-button:hover { background-color: rgba(0, 0, 0, .04); } .library-container .update-button:focus::before { opacity: 1; transform: scale(1); } .library-container .update-button:active { background-color: #000; color: #fff; } @media (max-width: 568px) { .library-container .update-container { text-align: left; } .library-container .update-controls { margin-right: 18px; } .library-container .library-content.grid .more-actions:not(.open) { width: fit-content; transform: translateY(35px); } } .item-wrapper.book[data-check-status=outdated] .product-field.item-status { background: #FE8484; } .item-wrapper.book:is([data-check-status=skipped], [data-check-status=failed]) .product-field.item-status a { text-decoration-line: underline; cursor: pointer; } `); const STATUS_PENDING = "pending"; const STATUS_CHECKING = "checking"; const STATUS_LATEST = "latest"; const STATUS_OUTDATED = "outdated"; const STATUS_PREVIEW = "preview"; const STATUS_SKIPPED = "skipped"; const STATUS_FAILED = "failed"; const READING_URL_PATTERN = /\/ReadNow\/(?<id>[0-9a-f-]{36})/; const STORE_AUDIOBOOK_URL_PATTERN = /\/(?<region>[a-z]{2})\/(?<language>[a-z]{2})\/audiobook\//;; const books = document.querySelectorAll(".item-wrapper.book"); for (const book of books) { const actions = book.querySelector(".item-info + .item-bar .library-actions-list"); const actionContainer = document.createElement("li"); actionContainer.classList.add("library-actions-list-item"); const action = document.createElement("button"); action.classList.add("library-action"); action.textContent = MESSAGES.CHECK_UPDATE; action.addEventListener("click", () => checkUpdate(book)); actionContainer.appendChild(action); actions.appendChild(actionContainer); } { const controls = document.querySelector(".secondary-controls"); const container = document.createElement("div"); container.classList.add("update-container"); const wrapper = document.createElement("div"); wrapper.classList.add("update-controls"); const checkButton = document.createElement("button"); checkButton.classList.add("update-button"); checkButton.textContent = MESSAGES.CHECK_UPDATE_FOR_PAGE; checkButton.addEventListener("click", () => books.forEach(checkUpdate)); const copyButton = document.createElement("button"); copyButton.classList.add("update-button"); copyButton.textContent = MESSAGES.COPY_OUTDATED_BOOKS; copyButton.addEventListener("click", () => { const books = document.querySelectorAll(".item-wrapper.book[data-check-status=outdated]"); navigator.clipboard.writeText(Array.prototype.map.call(books, getBookTitle).join("\n")); alert((books.length === 0) ? MESSAGES.NO_BOOKS_WERE_OUTDATED : MESSAGES.COPIED_N_BOOKS_INTO_CLIPBOARD.replace("{0}", books.length)); }); wrapper.append(checkButton, copyButton); container.appendChild(wrapper); controls.insertBefore(container, controls.firstChild); } function isAudiobook(book) { return STORE_AUDIOBOOK_URL_PATTERN.test(book.querySelector(".product-field.title a").href); } function getBookTitle(book) { return book.querySelector(".product-field.title").innerText; } function getCurrentProductId(book) { const matches = book.querySelector(".library-action.readnow").href.match(READING_URL_PATTERN); return matches?.groups.id; } async function getLatestProductId(book) { const response = await fetch(book.querySelector(".product-field.title a").href); if (!response.ok) { if (response.status === 404) { throw new Error(MESSAGES.BOOK_WAS_UNLISTED); } throw new Error(MESSAGES.UNKNOWN_ERROR); } const html = await response.text(); const parser = new DOMParser(); const page = parser.parseFromString(html, "text/html"); const itemId = page.querySelector("#ratItemId"); if (itemId) { return itemId.value; } throw new Error(MESSAGES.UNKNOWN_ERROR); } const queue = new Queue({ autostart: true, concurrency: 6 }); function checkUpdate(book) { const message = book.querySelector(".product-field.item-status"); book.dataset.checkStatus = STATUS_PENDING; message.replaceChildren(MESSAGES.PENDING); if (book.dataset.koboGizmo === "PreviewLibraryItem") { book.dataset.checkStatus = STATUS_SKIPPED; message.classList.remove("buy-now"); message.replaceChildren(MESSAGES.PREVIEW); return; } if (isAudiobook(book)) { book.dataset.checkStatus = STATUS_SKIPPED; const link = document.createElement("a"); link.textContent = MESSAGES.SKIPPED; link.addEventListener("click", (event) => alert(MESSAGES.BOOK_IS_AUDIOBOOK)); message.replaceChildren(link); return; } queue.push(async () => { book.dataset.checkStatus = STATUS_CHECKING; message.textContent = MESSAGES.CHECKING; try { const currentId = getCurrentProductId(book); const latestId = await getLatestProductId(book); console.debug(`${getBookTitle(book)}\n Current: ${currentId}\n Latest : ${latestId}`); if (currentId === latestId) { book.dataset.checkStatus = STATUS_LATEST; message.replaceChildren(MESSAGES.LATEST); } else { book.dataset.checkStatus = STATUS_OUTDATED; message.replaceChildren(MESSAGES.OUTDATED); } } catch (e) { book.dataset.checkStatus = STATUS_FAILED; const link = document.createElement("a"); link.textContent = MESSAGES.FAILED; link.addEventListener("click", (event) => alert(e.message)); message.replaceChildren(link); } }); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址