// ==UserScript== // @name 鱼比价 // @author Paranoid_AF // @namespace Paranoid_AF.djv // @version 1.0 // @grant GM_xmlhttpRequest // @description 提供 duozhuayu.com 的比价功能。 // @require https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js // @include https://www.duozhuayu.com/* // @connect book.douban.com // @grant GM_getValue // @grant GM_setValue // @grant GM_addValueChangeListener // @downloadURL https://update.greasyfork.icu/scripts/398609/%E9%B1%BC%E6%AF%94%E4%BB%B7.user.js // @updateURL https://update.greasyfork.icu/scripts/398609/%E9%B1%BC%E6%AF%94%E4%BB%B7.meta.js // ==/UserScript== (function() { const processDetail = (params) => { setTimeout(()=>{ getPriceInfo($(document)); }, 100); } const getPriceInfo = (page) => { let redirLink = $(page).find(".outer-link")[0].href; let doubanLink = redirLink.split("=")[1]; GM_xmlhttpRequest({ method: "GET", url: doubanLink, onload: (e) => { let priceList = extractPriceList($.parseHTML(e.response)); injectDetailPage(priceList); } }); } const injectDetailPage = (priceList) => { let infoFirst = $(".info-row")[0]; for(let i in priceList){ let cloneNode = $(infoFirst).clone(); let infoSubject = $(cloneNode).find("span"); let infoPrice = $(cloneNode).find("dd"); $(infoSubject).text(priceList[i].store+"价"); $(cloneNode).find("dt").css("width", priceList[i].store.split("").length * 18); $(infoPrice).text(priceList[i].price); $(infoPrice).css("color", "#f23737"); $(infoPrice).append(` 查看 »`); $(infoPrice).find("a").css("color", "rgb(24, 195, 170)"); $(infoFirst.parentNode).prepend($(cloneNode)); } if(priceList.length > 0){ $(infoFirst.parentNode).prepend(`以下价格来自豆瓣读书,链接为豆瓣的返利链接,不是脚本作者的返利链接!`); }else{ $(infoFirst.parentNode).prepend(`暂无其它商店的价格信息。`); } } const extractPriceList = (page) => { let priceInfo = []; let priceSection = $(page).find(".bs.noline.more-after"); // New books prices for sure. if(priceSection.length > 0){ priceSection = priceSection[priceSection.length - 1]; let priceRaw = $(priceSection).children(); for(let i=1; i 0){ priceInfo.push({ store: infoList[0].textContent, price: infoList[1].textContent.replace(/[\\n\s]+/g, ' '), link: $(priceRaw[i]).find("a")[0].href }); } } } } return priceInfo; } // CONSTANTS: Just to avoid stupid errors. const pageTypes = Object.freeze({ DETAIL: { key: "djv_detail", handler: processDetail }, CART: { key: "djv_cart", handler: null }, OTHER: { key: "djv_other", handler: null } }); // HOOK: Injects history.pushState, to listen for URL change issued by page. Found on https://stackoverflow.com/questions/10419898/is-there-a-callback-for-history-pushstate/10419974#10419974 // P.S. It's year 2020, and SPA are everywhere. However we still have to use a hack for this?? var pushState = history.pushState; history.pushState = function () { if(arguments[2] !== undefined){ handleUrlChange(arguments[2]); } pushState.apply(history, arguments); }; // HOOK: Listen for URL change issued by user, mostly for navigating. window.onpopstate = (e) => { handleUrlChange(e.target.location.pathname); } $(document).ready(() => { setTimeout(() => { handleUrlChange(document.location.href); }, 1000); }); // HANDLER: Handle URL changes. const handleUrlChange = (pathname) =>{ pageInfo = getPageInfo(pathname); if(!!pageInfo.type.handler){ pageInfo.type.handler(pageInfo.params); } } /* UTIL: Get page info from pathname. Return: { type: pageTypes.*, params: { } } */ const getPageInfo = (pathname) => { let pathParts = pathname.split("/"); let pageInfo = { type: pageTypes.OTHER, params: null } if(pathParts.length > 1){ if(pathParts[pathParts.length - 1] === "cart"){ pageInfo.type = pageTypes.CART; } } if(pathParts.length > 2){ if(pathParts[pathParts.length - 2] === "books"){ let bookId = pathParts[pathParts.length - 1]; pageInfo.type = pageTypes.DETAIL; pageInfo.params = { id: bookId }; } } return pageInfo; } })();