Raw Source
elviskang / Douban Book Bar

// ==UserScript==
// @name        Douban Book Bar
// @namespace   DoubanBookBar
// @license		MIT
// @author      ElvisKang<elviskang@foxmail.com>
// @description 以最优惠的价格买到最心仪的书
// @downloadURL https://greasyfork.org/scripts/3737-douban-book-bar/code/Douban%20Book%20Bar.user.js
// @updateURL   https://greasyfork.org/scripts/3737-douban-book-bar/code/Douban%20Book%20Bar.meta.js
// @include     *://www.amazon.cn/*
// @include     *://item.jd.com/*
// @include     *://product.dangdang.com/*
// @include     *://product.china-pub.com/*
// @include     *://product.suning.com/*
// @include     *://www.suning.com/emall/*
// @include     *://www.duokan.com/book/*
// @include     *://www.winxuan.com/product/*
// @include     *://www.ituring.com.cn/*
// @include     *://www.epubit.com.cn/*
// @include     *://detail.tmall.com/*
// @include     *://item.taobao.com/*
// @version     ver 1.2.20
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// ==/UserScript==

//用于解决greasemonkey在iframe中运行的问题,避免脚本多次运行
if (window.top === window.self) {
    (function () {
        "use strict";

        //全局变量(构造函数)声明区

        //包含支持的网站列表以及当前网站对象
        var sitesContainer = {

            //以下划线开头的属性为访问器属性
            _siteList: [],    //插件支持的网页
            _curSite: null,  //当前网页
            _siteNameList: [],
            addSites: function (sitesArray) {
                if (Array.isArray(sitesArray)) {
                    this._siteList = this.list.concat(sitesArray);
                    var that = this;
                    sitesArray.forEach(function (value) {
                        that.nameList.push(value.name);
                    });
                }
            },
            get list() {
                return this._siteList;
            },
            get nameList() {
                return this._siteNameList;
            },
            get curSite() {
                return this._curSite;
            },
            set curSite(href) {
                //通过对象中的checker属性匹配网站
                var i,
                    len = this.list.length;
                for (i = 0; i < len; i++) {
                    var checker = this.list[i].checker;
                    if (checker.test && checker.test(href)) {
                        this._curSite = this.list[i];
                    }
                }
            }
        };

        function SupportSite(siteInfo) {
            if (typeof siteInfo === "object") {
                for (var props in siteInfo) {
                    if (siteInfo.hasOwnProperty(props)) {
                        this[props] = siteInfo[props];
                    }
                }
            }
        }

        SupportSite.prototype.createDoubanBar = function (bookInfo, priceList) {
            var insertPosition = document.querySelector(this.insertPosition);
            if (!insertPosition) {
                log("-Error: 网页发生变动,插入位置没有找到");
                return;
            }
            var infoRowContainer = createInfoRowContainer(),
                priceRowContainer = createPriceRowContainer();
            var scoreSpan = createScoreSpan(bookInfo),
                contrastPriceInfo = createContrastPriceInfo(priceList);
            priceRowContainer.appendChild(contrastPriceInfo);
            infoRowContainer.appendChild(scoreSpan);
            setBaseCss();
            var bar = createBar(infoRowContainer, priceRowContainer);
            insertBar(bar, insertPosition);
        };

        var Amazon = new SupportSite({

            name: "亚马逊",

            checker: /(https?:\/\/)?(www)?\.amazon\.(com|cn)\/.*/,

            logo: "https://www.amazon.cn/favicon.ico",

            getISBN: function () {
                var contents = document.querySelectorAll("div.content b");
                for (var i = 0; i < contents.length; i++) {
                    var info = contents[i];
                    if (info && info.textContent === "ISBN:") {
                        return info.nextSibling.data.split(",")[0].substring(1);
                    }
                }
            },
            insertPosition: "#tmmSwatches"
        });

        var TMall = new SupportSite({

            name: "天猫",

            checker: /(https?:\/\/)?(detail)?\.tmall\.com\/.*/,

            logo: "https://www.tmall.com/favicon.ico",

            getISBN: function () {
                var items = document.querySelectorAll("#J_AttrUL li");
                for (var i = 0; i < items.length; i++) {
                    var info = items[i];
                    if (info.innerText) {
                        var result = info.innerText.match(/ISBN[^0-9]*(\d+)/i)
                        if (result) {
                            return result[1]
                        }
                    }
                }
            },
            insertPosition: ".tb-meta"
        });

        var Taobao = new SupportSite({

            name: "淘宝",

            checker: /(https?:\/\/)?(item)?\.taobao\.com\/.*/,

            logo: "https://www.taobao.com/favicon.ico",

            getISBN: function () {
                var items = document.querySelectorAll(".attributes-list li");
                for (var i = 0; i < items.length; i++) {
                    var info = items[i];
                    if (info.innerText) {
                        var result = info.innerText.match(/ISBN[^0-9]*(\d+)/i)
                        if (result) {
                            return result[1]
                        }
                    }
                }
            },
            insertPosition: ".tb-meta"
        });

        var JD = new SupportSite({
            name: "京东商城",

            checker: /(https?:\/\/)?(www|item)?\.jd\.com\/.*/,

            logo: "https://www.jd.com/favicon.ico",

            getISBN: function () {
                var liContent = document.querySelector("#parameter2 > li:nth-child(2)").innerHTML;
                if (liContent.match(/^ISBN/) !== null) {
                    return liContent.split(":")[1];
                }
                return null;
            },
            insertPosition: "#summary-price"
        });

        var Dangdang = new SupportSite({

            name: "当当网",

            checker: /(https?:\/\/)?(www|product)?\.dangdang\.com\/.*/,

            logo: "https://www.dangdang.com/favicon.ico",

            getISBN: function () {
                var scanItems = document.querySelectorAll("#detail_describe li");
                for (var i = 0; i < scanItems.length; i++) {
                    var splitResult = scanItems[i].innerText.split('ISBN:');
                    if (splitResult.length === 2) {
                        return splitResult[1];
                    }
                }
            },
            insertPosition: ".messbox_info"
        });

        var Chinapub = new SupportSite({

            name: "China-pub",

            checker: /(https?:\/\/)?(product|www)\.china\-pub\.com\/.*/,

            logo: "https://www.china-pub.com/favicon.ico",

            getISBN: function () {
                var list = document.querySelectorAll("#con_a_1 li");
                for (var i = 0; i < list.length; i++) {
                    if (list[i].innerHTML.split(":")[0] === "ISBN") {
                        return list[i].innerHTML.split(":")[1].replace(/<.*?>/g, "").match(/[0-9]*/g).join("");
                    }
                }
            },
            insertPosition: ".pro_buy_star"
        });

        var Suning = new SupportSite({

            name: "苏宁易购",

            checker: /(https?:\/\/)?(product)\.suning\.com\/.*/,

            logo: "https://www.suning.com/favicon.ico",

            getISBN: function () {
                var isbn = document.querySelector("#bookParameterField > dl:nth-child(4) > dd:nth-child(2)");
                return isbn.innerHTML;
            },
            insertPosition: "#existPrice"
        });
        //苏宁第三方(文轩等)
        var SuningThird = new SupportSite({

            name: "苏宁易购第三方",

            checker: /(https?:\/\/)?(www)?\.suning\.com\/emall\/.*/,

            logo: "https://www.suning.com/favicon.ico",

            getISBN: function () {
                var isbn = document.querySelector("li.li-b:nth-child(11) > span:nth-child(2)");
                return isbn.innerHTML;
            },
            insertPosition: "#productInfoUl"
        });

        var WinXuan = new SupportSite({
            name: "文轩网",
            checker: /(https?:\/\/)?(www)\.winxuan\.com\/.*/,
            logo: "https://www.winxuan.com/favicon.ico",
            insertPosition: "div.name:nth-child(1)",
            getISBN: function () {
                var contents = document.querySelectorAll(".list-text-3>li");
                //目前文轩网的ISBN位于列表的最后一项,考虑到以后的位置可能发生改变,所以从后往前遍历列表
                var i = contents.length;
                for (; i--;) {
                    var text = contents[i].textContent;
                    var textChecker = /^I S B N:/;
                    if (text && textChecker.test(text)) {
                        return text.split(":")[1];
                    }
                }
            }
        });
        var DuoKan = new SupportSite({

            name: "多看阅读",

            isEBook: true,

            checker: /(https?:\/\/)?(www)\.duokan\.com\/.*/,

            logo: "https://www.duokan.com/favicon.ico",

            getISBN: function () {
                var isbn = document.querySelector("span.isbn");
                return isbn.innerHTML;
            },
            insertPosition: "div[itemprop=aggregateRating]"
        });

        var ituring = new SupportSite({
            name: "图灵社区",

            checker: /(http?:\/\/)?(www)?\.ituring\.com\.cn\/book\/.*/,

            logo: "https://www.ituring.com.cn/favicon.ico",

            getISBN: function () {
                var tlContent = document.querySelectorAll(".publish-info li");
                var patt = /(\d+-){3,4}(?:\d+|X)/;
                for (var i = 0; i < tlContent.length; i++) {
                    if (patt.test(tlContent[i].textContent)) {
                        return tlContent[i].innerHTML.match(patt)[0].replace(new RegExp(/(-)/g), '');
                    }
                }
                return null;
            },
            insertPosition: "div.book-status"
        });

        var epubit = new SupportSite({
            name: "异步社区",

            checker: /(http?:\/\/)?(www)?\.epubit\.com\.cn\/book\/details\/.*/,

            logo: "https://www.epubit.com.cn/favicon.ico",

            getISBN: function () {
                var ybContent = document.querySelectorAll("#publish-info ul li");
                var ybPatt = /(\d+-){3,4}(?:\d+|X)/;
                for (var i = 0; i < ybContent.length; i++) {
                    if (ybPatt.test(ybContent[i].textContent)) {
                        return ybContent[i].innerHTML.match(ybPatt)[0].replace(new RegExp(/(-)/g), '');
                    }
                }
                return null;
            },
            insertPosition: "div.share"
        });

        //全局函数声明区

        function log(msg) {
            console.log("DoubanBookBar: " + msg);
        }

        //调用API设置比价栏的CSS属性
        function setBaseCss() {
            var baseCSS = [
                "#bookbar-container a {color:#228A31 !important;text-decoration:none;font-size: 13px;}",
                "#bookbar-container .bookbar-title {font-size: 14px;width:70px;}",
                "#bookbar-container span {margin-right: 5px;display: inline-block;font-size: 13px;font-weight: bold;}",
                "#bookbar-container ul {padding-left: 0;margin: 4px 0;}",
                "#bookbar-container li {list-style: none none outside; display: inline;padding: 0 4px;}",
                "#bookbar-container img{ height:16px; width:16px;}",
                "#avgScore {color:#DC0000 !important;}"
            ].join("");
            GM_addStyle(baseCSS);
        }

        // create* ----生成比价栏相关的函数
        //创建比价栏的调用控制函数在SupportSite对象的原型链中
        //星级信息
        function createStarSpan(score) {
            var starSpan = document.createElement("span");
            starSpan.id = "bookbar-star";
            var bgPosition = "0 " + (-15) * (10 - Math.floor(parseFloat(score) + 0.8)) + "px";
            var starSpanCSS = [
                "#bookbar-star { ",
                "background :url(https://img3.doubanio.com/f/shire/680a4bc4c384199245b080c7104da5be8ed717d3/pics/rating_icons/ic_rating_m.png) no-repeat scroll " + bgPosition + " ; ",
                "width:75px; height: 15px; position: relative; top: 1px;}"
            ].join("");
            GM_addStyle(starSpanCSS);
            return starSpan;
        }

        //得分与评价信息
        function createScoreSpan(bookInfo) {
            var bookID = bookInfo.id,
                bookRating = bookInfo.rating || {},
                numRaters = bookRating.numRaters,
                averageScore = bookRating.average;
            var scoreSpan = document.createElement("span"),
                infoUl = document.createElement("ul"),
                commentsLink = document.createElement("li"),
                bookInfoLink = document.createElement("li");
            if (numRaters === 0) {
                commentsLink.innerHTML = "<span>没有人评价这本书</span>";
            } else if (numRaters < 10) {
                commentsLink.innerHTML = "<span>少于10人评价这本书</span>";
            } else if (numRaters >= 10) {
                commentsLink.innerHTML = '<a  href="https://book.douban.com/subject/' + bookID + '/collections" target="_blank">(共' + numRaters + '人评价)</a>';
            } else {
                //获取信息出现错误
                commentsLink.innerHTML = "";
            }
            if (commentsLink.innerHTML !== "") {
                var avgLi = document.createElement("li"),
                    starLi = document.createElement("li"),
                    starSpan = createStarSpan(averageScore);
                avgLi.innerHTML = '<span>' + averageScore + '</span>';
                avgLi.id = "avgScore";
                starLi.appendChild(starSpan);
                infoUl.appendChild(avgLi);
                bookInfoLink.innerHTML = '<a href="https://book.douban.com/subject/' + bookID + '/" target="_blank" >(去豆瓣看这本书)</a>';
                infoUl.appendChild(starLi);
                infoUl.appendChild(commentsLink);
            } else {
                bookInfoLink.innerHTML = '<a href="https://book.douban.com" target="_blank">没在豆瓣找到这本书,去豆瓣逛逛?</a>';
            }
            infoUl.appendChild(bookInfoLink);
            scoreSpan.appendChild(infoUl);
            return scoreSpan;
        }

        //由 createContrastPriceInfo 函数调用
        //生成每一个价格<li>标签
        function createPriceLi(iconLink, priceData, sitePrice) {
            var priceLi = document.createElement("li"),
                link = document.createElement("a"),
                img = document.createElement("img");
            img.src = iconLink;
            link.href = priceData.href || "#";
            link.target = "_blank";
            if (priceData.price === undefined) {
                link.textContent = "[没找到]";
            } else {
                link.textContent = "¥" + priceData.price;
                if (sitePrice !== -1) {
                    var difference = parseFloat(priceData.price) - parseFloat(sitePrice);
                    var sgn = difference > 0 ? "+" : "";
                    link.textContent += "( " + sgn + difference.toFixed(2) + " )";
                }
            }
            priceLi.appendChild(img);
            priceLi.appendChild(link);
            return priceLi;
        }

        //价格(其他网站)信息
        function createContrastPriceInfo(priceList) {
            var contrastPriceInfo = document.createElement("span"),
                infoContainer = document.createElement("ul");
            if (priceList.length === 0) {
                contrastPriceInfo.innerHTML = "<span>豆瓣上没有购买信息</span>";
            } else {
                for (var i = 0, len = priceList.length; i < len; i++) {
                    var matchIndex = sitesContainer.nameList.indexOf(priceList[i].name);
                    var priceLi = createPriceLi(sitesContainer.list[matchIndex].logo, priceList[i], priceList.curSitePrice);
                    infoContainer.appendChild(priceLi);
                }
            }
            contrastPriceInfo.appendChild(infoContainer);
            return contrastPriceInfo;
        }

        function createBar(infoRow, priceRow) {
            var bar = document.createElement("div");
            bar.id = "bookbar-container";
            bar.appendChild(infoRow);
            bar.appendChild(priceRow);
            return bar;

        }

        function createPriceRowContainer() {
            var container = document.createElement("div"),
                label = document.createElement("span");
            container.id = "bookbar-priceInfo";
            label.innerHTML = "比价:";
            label.className = "bookbar-title";
            container.appendChild(label);
            return container;
        }

        function createInfoRowContainer() {
            var container = document.createElement("div"),
                label = document.createElement("span");
            container.id = "bookbar-doubanInfo";
            label.className = "bookbar-title";
            label.innerHTML = "豆瓣评分:";
            container.appendChild(label);
            return container;
        }

        function insertBar(bar, position) {
            var parent = position.parentNode;
            if (parent.lastChild === position) {
                parent.appendChild(bar);
            }
            else {
                parent.insertBefore(bar, position.nextSibling);
            }
        }

        // 获取书籍信息
        function getBookInfo(isbn) {
            if (!isbn) {
                return null;
            }
            GM_xmlhttpRequest({
                method: "get",
                url: "https://api.douban.com/v2/book/isbn/" + isbn,
                onload: function (result) {
                    var bookInfo = JSON.parse(result.responseText);
                    getBookPrice(bookInfo);
                },
                onerror: function (result) {
                    log("-Error:获取书籍信息失败");
                }
            });
        }

        function getBookPrice(bookInfo) {
            var doubanID = bookInfo.id || null;
            var doubanLink = "https://book.douban.com/subject/" + doubanID + "/buylinks";
            var priceList = [];
            GM_xmlhttpRequest({
                method: "get",
                url: doubanLink,
                onload: function (result) {
                    var container = document.createElement("div");
                    container.innerHTML = result.responseText;
                    var list = document.evaluate('//table[@id="buylink-table"]/tbody/tr', container, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                    var priceChecker = /[0-9]+(\.[0-9]+)?/;
                    var curSitePrice = -1;
                    for (var i = 1, len = list.snapshotLength; i < len; i++) {
                        var part = list.snapshotItem(i);
                        var link_info = part.querySelectorAll("td.pl2");
                        var siteName = link_info[0].textContent.trim();
                        if (sitesContainer.nameList.indexOf(siteName) !== -1) {
                            if (siteName === sitesContainer.curSite.name) {
                                curSitePrice = priceChecker.exec(link_info[1].textContent.trim())[0];
                            } else {
                                var priceInfo = {
                                    "name": siteName,
                                    "href": link_info[0].getElementsByTagName("a")[0].href,
                                    "price": priceChecker.exec(link_info[1].textContent.trim())[0]
                                };
                                priceList.push(priceInfo);
                            }
                            priceList.curSitePrice = curSitePrice; // 如果为-1,则说明没有获得当前网页的书本价格
                        }
                    }
                    sitesContainer.curSite.createDoubanBar(bookInfo, priceList);
                },
                onerror: function (result) {
                    log("-Error:获取书籍价格失败");
                }
            });

        }

        function run() {
            sitesContainer.addSites([Amazon, TMall, Taobao, JD, Dangdang, Chinapub, Suning, SuningThird, DuoKan, WinXuan, ituring, epubit]);
            sitesContainer.curSite = location.href;
            if (!sitesContainer.curSite) {
                log("-Error: 不支持当前的页面");
                return;
            }
            var isbn = sitesContainer.curSite.getISBN();
            if (!isbn) {
                log("-Error: 无法获取ISBN");
                return;
            } else {
                log("-Success: 成功获取ISBN");

                //异步调用的Ajax ,所以之后需要调用的函数都封装到ajax当中
                getBookInfo(isbn);
            }
        }

        //代码执行区
        run();
    })();
}