鱼比价

提供 duozhuayu.com 的比价功能。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          鱼比价
// @author	      Paranoid_AF
// @namespace     Paranoid_AF.djv
// @version  	  1.0
// @grant GM_xmlhttpRequest
// @description   提供 duozhuayu.com 的比价功能。
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @include       https://www.duozhuayu.com/*
// @connect book.douban.com
// @grant   GM_getValue
// @grant   GM_setValue
// @grant   GM_addValueChangeListener
// ==/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(`&nbsp;<a href="${priceList[i].link}" target="_blank" rel="nofollow">查看 &raquo;</a>`);
      $(infoPrice).find("a").css("color", "rgb(24, 195, 170)");
      $(infoFirst.parentNode).prepend($(cloneNode));
    }
    if(priceList.length > 0){
      $(infoFirst.parentNode).prepend(`<span style="color: #f23737;">以下价格来自豆瓣读书,链接为豆瓣的返利链接,<b style="font-weight: 600">不是脚本作者的返利链接</b>!</span>`);
    }else{
      $(infoFirst.parentNode).prepend(`<span style="color: #f28181;">暂无其它商店的价格信息。</span>`);
    }
  }

  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<priceRaw.length; i++){
        if(priceRaw[i].className === "buylink-title second-hand"){
          break;
        }
        if(priceRaw[i].tagName.toLowerCase() === "li"){
          let infoList = $(priceRaw[i]).find("span");
          if(infoList.length > 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;
  }
})();