优书网 <=> 知轩藏书

[知轩藏书/早安电子书/书荒网/柚子书]添加优书网评分和直链,[优书网/柚子书]书籍详情页添加[知轩藏书/早安电子书/龙凤互联/书荒网]下载链接

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         优书网 <=> 知轩藏书
// @namespace    http://tampermonkey.net/
// @description  [知轩藏书/早安电子书/书荒网/柚子书]添加优书网评分和直链,[优书网/柚子书]书籍详情页添加[知轩藏书/早安电子书/龙凤互联/书荒网]下载链接
// @require      https://greasyfork.org/scripts/40003-pajhome-md5-min/code/PajHome-MD5-min.js
// @require      https://unpkg.com/gbk.js@0.3.0/dist/gbk.min.js
// @require      https://greasyfork.org/scripts/446257-waitforkeyelements-utility-function/code/waitForKeyElements%20utility%20function.js?version=1059316
// @match        *://zxcs.me/sort/*
// @match        *://zxcs.me/post/*
// @match        *://zxcs.me/index.php?keyword=*
// @match        *://www.zxcs.me/sort/*
// @match        *://www.zxcs.me/post/*
// @match        *://www.zxcs.me/author/*
// @match        *://www.zxcs.me/tag/*
// @match        *://www.zxcs.me/index.php?keyword=*
// @match        *://www.yousuu.com/book/*
// @match        *://www.yousuu.com/booklist/*
// @match        *://www.yousuu.com/explore*
// @match        *://www.zadzs.com/txt/*
// @match        *://www.zadzs.com/plus/search.php?*
// @match        *://www.nordfxs.com/*
// @match        *://www.15huang.com/style/*.html
// @match        *://www.15huang.com/style/*
// @match        *://www.15huang.com/e/search/result/*
// @match        *://www.3uww.cc/down/*
// @match        *://www.3uww.cc/author/*
// @match        *://www.3uww.cc/soft*
// @match        *://www.3uww.cc/search.html
// @match        *://www.3uww.cc/top/*
// @match        *://www.xuanquge.com/down/*
// @match        *://www.xuanquge.com/author/*
// @match        *://www.xuanquge.com/soft*
// @match        *://www.xuanquge.com/search.html
// @match        *://www.xuanquge.com/top/*
// @match        *://www.ixuanquge.com/down/*
// @match        *://www.ixuanquge.com/author/*
// @match        *://www.ixuanquge.com/soft*
// @match        *://www.ixuanquge.com/search.html
// @match        *://www.ixuanquge.com/top/*
// @match        *://www.wanbentxt.com/*
// @match        *://www.yuzuhon.com/*
// @match        *://www.zxcs.info/sort/*
// @match        *://www.zxcs.info/post/*
// @match        *://www.zxcs.info/author/*
// @match        *://www.zxcs.info/tag/*
// @match        *://www.zxcs.info/index.php?keyword=*
// @match        *://zxcs.info/sort/*
// @match        *://zxcs.info/post/*
// @match        *://zxcs.info/author/*
// @match        *://zxcs.info/tag/*
// @match        *://zxcs.info/index.php?keyword=*
// @match        *://zxcstxt.com/*
// @match        *://zxcsol.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      www.yousuu.com
// @connect      api.yousuu.com
// @connect      www.zxcs.me
// @connect      zxcs.me
// @connect      www.zadzs.com
// @connect      www.nordfxs.com
// @connect      www.zvzee.com
// @connect      www.15huang.com
// @connect      www.3uww.cc
// @connect      www.ibiquta.com
// @connect      www.mianhuatang.la
// @connect      zhannei.baidu.com
// @connect      www.ixdzs.com
// @connect      www.aixdzs.com
// @connect      www.xuanquge.com
// @connect      www.ixuanquge.com
// @connect      www.wanbentxt.com
// @connect      www.afs360.com
// @connect      www.auzw.com
// @connect      www.mianhuatang.cc
// @connect      www.mhtwx.la
// @connect      www.balingtxt.com
// @connect      www.dushuxiaozi.com
// @connect      jingjiaocangshu.cn
// @connect      www.kenshula.com
// @connect      www.wucuo8.com
// @connect      www.zxcs.info
// @connect      zxcs.info
// @connect      www.ibiquta.com
// @connect      www.mhtxs.la
// @connect      zxcstxt.com
// @connect      zxcsol.com
// @version      0.15
// @run-at       document-end
// ==/UserScript==

/*================================================= 常量 ================================================*/
// 下载链接缓存时间,默认1天
const DOWNLOAD_EXPIRED_TIME = 86400 * 1000;
// 优书网评分缓存时间,默认1天
const SEARCH_EXPIRED_TIME = 86400 * 1000;
// 优书网最大搜索数目,默认5个
const MAX_SEARCH_NUM = 5;
// 下载链接类型 1:直接获取 2:解析请求bookLink的响应 3:解析原bookList的响应
const DOWNLOAD_TYPE_DIRECT = 1;
const DOWNLOAD_TYPE_FETCH = 2;
const DOWNLOAD_TYPE_PROCESS = 3;

const IS_DEBUG = false;


//扩展名
const SCRIPT_HANDLER_TAMPERMONKEY = 'tampermonkey';

//要排除的下载源key
const EXCEPTED_DOWNLOAD_SITES_KEY = "excepted_sites";

// 无法获取 ready 事件的网站
const SITES_WAIT_KEY_ELEMENT = {
    "www.yuzuhon.com": "#__layout > div > div.app-main > div > div.container > div.book-info-section",
}

/*======================================================================================================*/

/*================================================  类  ================================================*/
/**
 * local storage 存储,支持过期日期
 * 没必要用class,但就是想试试
 */
class Storage {

    /**
     * 构造函数
     */
    constructor() {
        if (this._checkStorageStatus) {
            this._localStorageStatus = true;
        }
    }
    /**
     * 检查 local storage 状态
     */
    _checkStorageStatus() {
        if (!window.localStorage) {
            return false;
        }
        try {
            window.localStorage.setItem('checkLocalStorage', '1203');
        } catch (error) {
            return false;
        }
        if (window.localStorage.getItem('checkLocalStorage') !== '1203') {
            return false;
        }
        window.localStorage.removeItem('checkLocalStorage');
        return true;
    }
    /**
     * 写入
     * @param key 键名
     * @param value 值
     */
    setValue(key, value) {
        if (this._localStorageStatus) {
            let data = JSON.stringify({ value: value, time: new Date().getTime() });
            try {
                window.localStorage.setItem(key, data);
            } catch (error) {
                if (error.name === 'QUOTA_EXCEEDED_ERR') { //存储已满,清空所有
                    window.localStorage.clear();
                }
                console.log(error);
            }
        }
    }

    /**
     * 读取
     * @param key 键名
     * @param expired 到期日
     */
    getValue(key, expired) {
        if (this._localStorageStatus) {
            let value = window.localStorage.getItem(key);
            if (value !== null) {
                let dataObj = JSON.parse(value);
                if (new Date().getTime() - dataObj.time > expired) {
                    window.localStorage.removeItem(key);
                    return null;
                } else {
                    return dataObj.value;
                }
            } else {
                return null;
            }
        }
        return null;
    }

    /**
     * 删除
     * @param key 键名
     */
    deleteValue(key) {
        if (this._localStorageStatus) {
            window.localStorage.removeItem(key);
        }
    }

    /**
     * 清除所有
     */
    clear() {
        if (this._localStorageStatus) {
            window.localStorage.clear();
        }
    }
}

//初始化存储
const storage = new Storage();
/*=====================================================================================================*/

/*===============================================  配置  ===============================================*/
/**
 * 评分来源网站配置
 */
const rateSiteSourceConfig = {
    'yousuu': {
        name: 'yousuu',
        //url前缀
        prefix: 'http://www.yousuu.com/book/',
        //请求参数
        request(bookInfo) {
            return {
                method: "GET",
                url: 'https://api.yousuu.com/api/search/?type=title&value=' + bookInfo.bookName,
            }
        },
        //解析
        parse(bookInfo, response) {
            let rateInfo = { score: 0, num: 0, url: '', match: false };
            let i = 0;
            for (let item of JSON.parse(response.responseText).data.books) {
                i++;
                //超过最大计数,退出
                if (i >= bookInfo.maxNum) {
                    break;
                }
                if (item.bookId === bookInfo.bookId || (item.author == bookInfo.bookAuthor && item.title == bookInfo.bookName)) {
                    rateInfo.score = Number.parseFloat(item.score / 10).toFixed(1);
                    rateInfo.num = Math.round(Number.parseFloat(item.scorerCount));
                    rateInfo.url = this.prefix + item.bookId;
                    rateInfo.match = true;
                    break;
                }
            }
            return rateInfo;
        }
    },
}

/**
 * 需添加评分网站的路由配置
 * 根据页面转换为 rateSiteTargetConfig 的键名
 */
const rateSiteTargetRoute = {
    'www.zxcs.me': () => {
        let tag = location.pathname.split('/')[1];
        let prefix = 'zxcs8.';
        if (tag === 'post') {
            return prefix + 'post';
        }
        if (['sort', 'tag', 'author'].includes(tag)) {
            return prefix + 'sort';
        }
        // 搜索页面
        if (location.pathname.includes('index.php')) {
            return prefix + 'sort';
        }
    },
    'zxcs.me': () => {
        let tag = location.pathname.split('/')[1];
        let prefix = 'zxcs8.';
        if (tag === 'post') {
            return prefix + 'post';
        }
        if (['sort', 'tag', 'author'].includes(tag)) {
            return prefix + 'sort';
        }
        // 搜索页面
        if (location.pathname.includes('index.php')) {
            return prefix + 'sort';
        }
    },
    'zxcstxt.com': () => {
        let tag = location.pathname.split('/')[1];
        let prefix = 'zxcstxt.';
        if (/^(\d)+$/.test(tag)) {
            return prefix + 'post';
        }
        return prefix + 'sort';
    },
    'www.zadzs.com': () => {
        let pathname = location.pathname;
        let prefix = 'zadzs.';
        if (pathname.includes('txt')) {
            return prefix + 'detail';
        }
        if (pathname.includes('search')) {
            return prefix + 'search';
        }
    },
    'www.15huang.com': () => {
        let pathname = location.pathname;
        let prefix = '15huang.';
        // 搜索结果 || 作者
        if (pathname == '/e/search/result/') {
            return prefix + 'category';
        }

        // 详情页
        if (pathname.includes('.html')) {
            return prefix + 'detail';
        }
        return prefix + 'category';
    },
    'www.ibiquta.com': () => {
        let pathname = location.pathname;
        let prefix = '3uww.';
        // 排行
        if (pathname.includes('top')) {
            return prefix + 'category';
        }
        // 详情
        if (pathname.includes('down')) {
            return prefix + 'detail';
        }
        // 作者
        if (pathname.includes('author')) {
            return prefix + 'author';
        }
        // 分类
        if (pathname.search(/soft(\d+)/ig) !== -1) {
            return prefix + 'category';
        }
        // 搜索
        if (pathname.includes('search')) {
            return prefix + 'search';
        }
    },
    'www.wanbentxt.com': () => {
        let pathname = location.pathname;
        let prefix = 'wanbentxt.';
        //详情页(/数字/)
        if (/\/(\d)+\//.test(pathname)) {
            return prefix + 'detail';
        }
    },
    'www.yuzuhon.com': () => {
        let pathname = location.pathname;
        let prefix = 'yuzuhon.';
        if (/^\/book\/\d+$/.test(pathname)) {
            return prefix + 'detail';
        }
    },
    'www.zxcs.info': () => {
        let tag = location.pathname.split('/')[1];
        if (tag === 'post') {
            return 'zxcsinfo.post';
        }
        let prefix = 'zxcs8.';
        if (['sort', 'tag', 'author'].includes(tag)) {
            return prefix + 'sort';
        }
        // 搜索页面
        if (location.pathname.includes('index.php')) {
            return prefix + 'sort';
        }
    },
    'zxcs.info': () => {
        let tag = location.pathname.split('/')[1];
        if (tag === 'post') {
            return 'zxcsinfo.post';
        }
        let prefix = 'zxcs8.';
        if (['sort', 'tag', 'author'].includes(tag)) {
            return prefix + 'sort';
        }
        // 搜索页面
        if (location.pathname.includes('index.php')) {
            return prefix + 'sort';
        }
    },
    'zxcsol.com': () => {
        let tag = location.pathname.split('/')[2];
        let prefix = 'zxcstxt.';
        if (/^(\d)+(\.)html/.test(tag)) {
            return prefix + 'post';
        }
        return prefix + 'sort';
    },
    'www.yousuu.com': () => {
        let tag = location.pathname.split('/')[1];
        let prefix = 'yousuu.';
        switch (tag) {
            case 'booklist':
            case 'explore':
                return prefix + tag;
            default:
                break;
        }
    },
};

/**
 * 需添加评分网站的配置
 */
const rateSiteTargetConfig = {
    'zxcs8.post': {
        name: 'zxcs8.post',
        bookName(item) {
            return item.querySelector('h1').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.querySelector('h1').innerText);
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<p class="yinyong"><span style="font-size:14px;color:#FF0000;font-weight:bold">优书网评分: <a href = "'
                + bookLink + '" target="_blank">' + rate +
                '</a></span><p><p class="yinyong"><span style="font-size:14px;color:#FF0000;font-weight:bold">评分人数: ' + rateNum + '</span><p>';
        },
        anchorObj(item) {
            let obj = item.querySelector('.yinyong');
            return !obj ? item.querySelector('.pagefujian') : obj;
        },
        anchorPos: 'beforebegin',
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    'zxcs8.sort': {
        name: 'zxcs8.sort',
        bookName(item) {
            return item.firstElementChild.innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.firstElementChild.innerText);
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<a href= "' + bookLink + '" target = "_blank">&nbsp;&nbsp;&nbsp;评分:' + rate + '&nbsp;&nbsp;&nbsp;人数:' + rateNum + '</a>'
        },
        anchorObj(item) {
            return item.lastElementChild.querySelector('div');
        },
        anchorPos: 'beforebegin',
        handler(options, callback) {
            let bookList = Array.from(document.querySelectorAll('#plist'));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    'zxcsinfo.post': {
        name: 'zxcsinfo.post',
        bookName(item) {
            return item.querySelector('h1').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return item.querySelector("div.book-info > p.intro").innerText.split("著")[0].trim();
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<p class="intro" style="font-size:14px">评分:<a target = "_blank" href="${bookLink}">${rate}</a> 人数:${rateNum}</p>`;
        },
        anchorObj(item) {
            return item.querySelector('div.book-info > p.intro');
        },
        anchorPos: 'afterend',
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    'zadzs.detail': {
        name: 'zadzs.detail',
        bookName(item) {
            return item.querySelector('h3[title]').title;
        },
        bookAuthor(item) {
            return item.querySelector('h3[title]>span>a').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<tr><td width="42px">评分:</td><td><a href="' + bookLink + '" target = "blank">' +
                rate + '</a></td></tr><tr><td width="42px">人数:</td><td>' + rateNum + '</td></tr>';
        },
        anchorObj(item) {
            return item.querySelector('.m-bookstatus>table>tbody');
        },
        anchorPos: "afterbegin",
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    'zadzs.search': {
        name: 'zadzs.search',
        bookName(item) {
            return item.querySelector('.book>h5>a').innerText;
        },
        bookAuthor(item) {
            return getAuthorName(item.querySelector('.book>.price').innerText);
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<p class="price">评分:<a href="' + bookLink + '" target="_blank">' + rate + '</a>&nbsp;&nbsp;人数:' + rateNum + '</p>';
        },
        anchorObj(item) {
            return item.querySelector('.book>.disc');
        },
        anchorPos: "beforebegin",
        handler(options, callback) {
            let bookList = Array.from(document.querySelectorAll('.searchItem'));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    '15huang.detail': {
        name: '15huang.detail',
        bookName(item) {
            return item.querySelector('.row>h1').innerText;
        },
        bookAuthor(item) {
            return item.querySelector('p.book-writer>a').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<p class="book-writer">优书评分:<a href="' + bookLink + '" target="_blank">' +
                rate + '</a></p><p class="book-writer">评分人数:' + rateNum + '</p>';
        },
        anchorObj(item) {
            return item.querySelector('p.book-writer');
        },
        anchorPos: "afterend",
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    '15huang.category': {
        name: '15huang.category',
        bookName(item) {
            return item.querySelector('h4.ellipsis').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return item.querySelector('span.writer').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<em>|</em><span><a href="' + bookLink + '" target="_blank">' +
                rate + '分</a></span><em>|</em><span>' + rateNum + '人</span>';
        },
        anchorObj(item) {
            return item.querySelector('p.info.hei9.ellipsis');
        },
        anchorPos: "beforeend",
        handler(options, callback) {
            let bookList = Array.from(document.querySelectorAll("li.cate-infobox.col.xs-24.md-12"));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    '3uww.detail': {
        name: '3uww.detail',
        bookName(item) {
            return item.querySelector('#downInfoTitle').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return item.querySelector('.downInfoRowL>a').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<b>书籍评分:</b><a href="' + bookLink + '" class="strong blue" target="_blank">' +
                rate + '</a><br><b>评价人数:</b>' + rateNum + '<br>';
        },
        anchorObj(item) {
            return item.querySelector('.downInfoRowL>a').parentNode;
        },
        anchorPos: "beforeend",
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    '3uww.author': {
        name: '3uww.author',
        bookName(item) {
            return item.querySelector('.txt99>h2>a').innerText;
        },
        bookAuthor(item) {
            return document.querySelector('#Li1').innerText.replace(/的小说/ig, "");
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<div>书籍评分:<i><a href="' + bookLink + '" class="strong blue" target="_blank">' +
                rate + '</a></i></div><div>评分人数:<i>' + rateNum + '</i></div>';
        },
        anchorObj(item) {
            return item.querySelector('.txt99>ul').children[2];
        },
        anchorPos: "afterbegin",
        handler(options, callback) {
            let bookList = Array.from(document.querySelectorAll('.pinglw'));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    '3uww.category': {
        name: '3uww.category',
        bookName(item) {
            return item.info.querySelector('span.mainSoftName>a').innerText;
        },
        bookAuthor(item) {
            return item.bottom.querySelectorAll('.mainRunSystem')[1].innerText.replace(/书籍作者:/ig, "").trim();
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '<div class="mainAccredit"><span class="mainGreen">书籍评分:<a href = "' + bookLink + '" target = "_blank" ><u>'
                + rate + '</u></a></span></div><div class="mainstar"><span class="mainGreen">评分人数:</span>' + rateNum + '</div>'
        },
        anchorObj(item) {
            return item.bottom.querySelector('.mainRunSystem');
        },
        anchorPos: "afterend",
        handler(options, callback) {
            let bookInfo = Array.from(document.querySelectorAll('.mainListInfo'));
            let bookBottom = Array.from(document.querySelectorAll('.mainListBottom'));
            bookInfo.forEach((value, index) => {
                callback({ site: this.name, item: { info: value, bottom: bookBottom[index] }, ...options });
            });
        },
    },
    '3uww.search': {
        name: '3uww.search',
        bookName(item) {
            return item.info.querySelector('a').innerText.split('/')[0];
        },
        bookAuthor(item) {
            return item.info.querySelector('a').innerText.split('/')[1].replace(/作者:/ig, "");
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return '&nbsp;&nbsp;评分:<a href="' + bookLink + '" target="_blank"><span style="color:#0066FF">' + rate + '</span></a>&nbsp;&nbsp;人数:' + rateNum;
        },
        anchorObj(item) {
            return item.bottom.querySelector('.oldDate');
        },
        anchorPos: "afterend",
        handler(options, callback) {
            let bookInfo = Array.from(document.querySelectorAll('.searchTopic'));
            let bookBottom = Array.from(document.querySelectorAll('.searchInfo'));
            bookInfo.forEach((value, index) => {
                callback({ site: this.name, item: { info: value, bottom: bookBottom[index] }, ...options });
            });
        }
    },
    'wanbentxt.detail': {
        name: 'wanbentxt.detail',
        bookName(item) {
            return item.querySelector('div.contentDiv > div > div.detail > div.detailTop > div.detailTopMid > div.detailTitle > h1').innerText;
        },
        bookAuthor(item) {
            return item.querySelector('div.contentDiv > div > div.detail > div.detailTop > div.detailTopMid > div.writer > a').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<tr>
                    <td width="10%"><span>书籍评分:</span></td>
                    <td width="10%"><a href="${bookLink}" target="_blank">${rate}</a></td>
                    <td width="10%"><span>评分人数:</span></td>
                    <td width="10%">${rateNum}</td>
                </tr>`;

        },
        anchorObj(item) {
            return item.querySelector('div.detail > div.detailTop > div.detailTopMid > table > tbody').firstElementChild;
        },
        anchorPos: "afterend",
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    'wanbentxt.category': {
        name: 'wanbentxt.category',
        bookName(item) {
            return item.querySelector('div.sortPhr > a').innerText;
        },
        bookAuthor(item) {
            return item.querySelector('div.sortPhr > p.author > a').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<p class="actor" style="margin-top: 7px"><em>评分:</em><a href="${bookLink}" target="_blank">${rate}</a>&nbsp;&nbsp;&nbsp;&nbsp;人数:${rateNum}</p>`;
        },
        anchorObj(item) {
            return item.querySelector('div.sortPhr > p.actor');
        },
        anchorPos: "afterend",
        handler(options, callback) {
            let bookList = Array.from(document.querySelector('div.contentDiv > div > div.sortBottom > div.sortList > ul').children);
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    'yuzuhon.detail': {
        name: 'yuzuhon.detail',
        bookName(item) {
            return item.querySelector("#__layout > div > div.app-main > div > div.container > div.book-info-section > h2").innerText;
        },
        bookAuthor(item) {
            return item.querySelector("#__layout > div > div.app-main > div > div.container > div.book-info-section > div > div.book-info__metadata > div > span > a").innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            let rateStars = this._packageStar(Math.round(rate / 2 * 10) / 10, 5);
            return `<div style="padding: 15px 0px; border-top: 1px solid rgb(234, 234, 234);">
                        <span class="text-secondary"><a href="${bookLink}" target="_blank">优书网</a></span>
                        <div class="d-flex align-items-center mb-2">
                            <div class="book-info__rate"><strong>${rate}</strong></div>
                            <div class="book-info__star">
                                <div class="rate">
                                    ${rateStars}
                                </div>
                                <span> ${rateNum} 人评分 </span>
                            </div>
                        </div>
                    </div>`;
        },
        anchorObj(item) {
            return item.querySelector("#__layout > div > div.app-main > div > div.container > div.book-info-section > div > div.book-info__rate-block.mb-3");
        },
        anchorPos: "beforeend",
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
        _fullStar() {
            return `<span class="el-rate__item" style="cursor: auto;">
                        <i class="el-rate__icon el-icon-star-on" style="color: rgb(235, 159, 43);"></i>
                    </span>`;
        },
        _halfStar() {
            return `<span class="el-rate__item" style="cursor: auto;">
                        <i class="el-rate__icon el-icon-star-on" style="color: rgb(218, 218, 218);">
                            <i class="el-rate__decimal el-icon-star-on" style="color: rgb(235, 159, 43); width: 50%;"></i>
                        </i>
                    </span>`;
        },
        _emptyStar() {
            return `<span class="el-rate__item" style="cursor: auto;">
                        <i class="el-rate__icon el-icon-star-on" style="color: rgb(218, 218, 218);"></i>
                    </span>`;
        },
        _starsTemplate(rate, total, stars) {
            return `<div class="rate">
                        <div role="slider" aria-valuenow="${rate}" aria-valuetext="" aria-valuemin="0" aria-valuemax="${total}" tabindex="0" class="el-rate">
                            ${stars}
                        </div>
                    </div> `;
        },
        _packageStar(rate, total) {
            let stars = '';
            if (rate < 0) {
                for (let i = 0; i < total; i++) {
                    stars += this._emptyStar();
                }
                return this._starsTemplate(rate, total, stars);
            }
            let fullStars = Math.floor(rate);
            let emptyStars = total - Math.ceil(rate);
            let halfStars = total - fullStars - emptyStars;
            for (let i = 0; i < fullStars; i++) {
                stars += this._fullStar();
            }
            for (let j = 0; j < halfStars; j++) {
                stars += this._halfStar();
            }
            for (let k = 0; k < emptyStars; k++) {
                stars += this._emptyStar();
            }
            return this._starsTemplate(rate, total, stars);
        },
    },
    'yousuu.booklist': {
        name: 'yousuu.booklist',
        _utils: {},
        prepare() {
            //获取dataV的值
            let node = document.querySelector('p.book-info-update');
            let dataV = node.innerHTML.match(/data-v-(\w+)/g);
            this._utils.dataV = dataV;
        },
        bookName(item) {
            return item.querySelector('a.book-name').innerText;
        },
        bookAuthor(item) {
            return item.querySelector('a.author-name.ellipsis').innerText;
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<p class="book-info-update" ${this._utils.dataV}="">评分:${rate}&nbsp;&nbsp;&nbsp;&nbsp;人数:${rateNum}</p>`
        },
        anchorObj(item) {
            return item.querySelector('p.book-info-update');
        },
        anchorPos: "afterend",
        handler(options, callback) {
            this.prepare();
            let bookList = Array.from(document.querySelectorAll('div.common-card-layout.booklist-book-item'));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    'yousuu.explore': {
        name: 'yousuu.explpre',
        _utils: {},
        _checkedClassName: 'gmRateChecked',
        prepare() {
            //获取dataV的值
            let node = document.querySelector('div.comment-content.comment');
            let dataV = node.innerHTML.match(/data-v-(\w+)/g);
            this._utils.dataV = dataV.shift();
        },
        bookName(item) {
            return item.getAttribute('booktitle') || item.querySelector('div.author-info > div.book-name-and-score.space-praiseCom-BookTitleScore-margin > a').innerText;
        },
        bookAuthor(item) {
            //explore 页面没有作者信息
            return null;
        },
        bookId(item) {
            return parseInt(item.getAttribute('bookid'));
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<div ${this._utils.dataV}="" class="comment-content-inner default" style="color:grey">评分:${rate}&nbsp;&nbsp;&nbsp;&nbsp;人数:${rateNum}</div>`
        },
        anchorObj(item) {
            return item.querySelector('div.comment-content-inner.default');
        },
        anchorPos: "beforebegin",
        handler(options, callback) {
            this.prepare();
            let bookList = Array.from(document.querySelectorAll(`div.comment-card.BookCommentItem:not(.${this._checkedClassName})`));
            bookList.forEach((item) => {
                //无限滚动 Feed 流,加个标签区分一下
                item.classList.add(this._checkedClassName);
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
    'zxcstxt.post': {
        name: 'zxcstxt.post',
        bookName(item) {
            return item.querySelector('h1').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.querySelector('h1').innerText);
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<p style="font-weight:bold">【评分】:<br><a href="${bookLink}" target="_blank">优书网: ${rate}</a>&nbsp;&nbsp;人数: ${rateNum}</p>`;
        },
        anchorObj(item) {
            return item.querySelector('div.theme-box.wp-posts-content > p');
        },
        anchorPos: 'afterend',
        handler(options, callback) {
            callback({ site: this.name, item: document, ...options });
        },
    },
    'zxcstxt.sort': {
        name: 'zxcstxt.sort',
        bookName(item) {
            return item.firstElementChild.innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.firstElementChild.innerText);
        },
        maxNum: MAX_SEARCH_NUM,
        rateItem(rate, rateNum, bookLink) {
            return `<div class="item-tags scroll-x no-scrollbar mb6">
            <a class="but c-blue" href="${bookLink}" target="_blank"><i class="fa fa-book" aria-hidden="true"></i>评分: ${rate}&nbsp;&nbsp;人数: ${rateNum}</a></div>`;
        },
        anchorObj(item) {
            return item.querySelector('.item-meta');
        },
        anchorPos: 'beforebegin',
        handler(options, callback) {
            let bookList = Array.from(document.querySelectorAll('.posts-item'));
            bookList.forEach((item) => {
                callback({ site: this.name, item: item, ...options });
            });
        },
    },
};

/**
 * 下载链接来源网站配置
 */
const downloadSiteSourceConfig = {
    'zxcs8': {
        name: 'zxcs8',
        siteName: '知轩藏书',
        host: 'http://zxcs.me',
        searchConfig(args) {
            return { url: this.host + '/index.php?keyword=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.getElementsByTagName('dl'));
        },
        bookName(item) {
            return item.children["0"].innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.children["0"].innerText);
        },
        bookLink(item) {
            let url = item.children["0"].getElementsByTagName('a')[0].href;
            return handleUrl(url,location.origin,this.host);     
        },
        downloadLink(item) {
            let url = item.querySelector('.down_2>a').href;
            return handleUrl(url,location.origin,this.host);  
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_FETCH })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'nordfxs': {
        name: 'nordfxs',
        siteName: '龙凤互联',
        host: 'https://www.zvzee.com',
        searchConfig(args) {
            let data = 'formhash=191940c0&srchtxt=' + args.bookName + '&searchsubmit=yes';
            let headers = { "Content-Type": "application/x-www-form-urlencoded" };
            return { url: this.host + '/search.php?mod=forum', data: data, method: "POST", headers: headers };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll('.pbw'));
        },
        bookName(item) {
            return item.querySelector('.xs3>a').innerText.split('(').shift().replace(/[《,》]/g, '');
        },
        bookAuthor(item) {
            return getAuthorName(item.querySelector('.xs3>a').innerText);
        },
        bookLink(item) {
            return item.querySelector('.xs3>a').href;
        },
        downloadLink(item) {
            return item.querySelector('.xs3>a').href;
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    '15huang': {
        name: '15huang',
        siteName: '书荒网',
        host: 'http://www.15huang.com',
        searchConfig(args) {
            let data = 'show=title%2Cwriter%2Ckeyboard&tbname=news&tempid=1&keyboard=' + encodeURIComponent(args.bookName);
            let headers = { "Content-Type": "application/x-www-form-urlencoded", "Cookie": "alllclastsearchtime=" + (Date.parse(new Date) / 1000 - 480) };
            return { url: this.host + '/e/search/index.php', data: data, method: "POST", headers: headers, anonymous: true };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("li.cate-infobox.col.xs-24.md-12"));
        },
        bookName(item) {
            return item.querySelector('h4.ellipsis').innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return item.querySelector('span.writer').innerText;
        },
        bookLink(item) {
            return item.querySelector('a.open.bg-hui-hover').href;
        },
        downloadLink(item) {
            return this.host + item.querySelector('.downurl.col.xs-24.md-5>a').href.replace(location.origin, '');
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_FETCH })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    '3uww': {
        name: '3uww',
        siteName: '炫书网',
        host: 'https://www.ibiquta.com',
        searchConfig(args) {
            let data = 'searchkey=' + args.bookName;
            let headers = { "Content-Type": "application/x-www-form-urlencoded" };
            return { url: this.host + '/search.html', data: data, method: "POST", headers: headers };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll('.searchTopic'));
        },
        bookName(item) {
            return item.querySelector('a').innerText.split('/')[0];
        },
        bookAuthor(item) {
            return item.querySelector('a').innerText.split('/')[1].replace(/作者:/ig, "");
        },
        bookLink(item) {
            return item.querySelector('a').href;
        },
        downloadLink(item) {
            return item.querySelector('.downAddress_li>a').href;
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_FETCH })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'ixdzs': {
        name: 'ixdzs',
        siteName: '爱下电子书',
        host: 'https://www.aixdzs.com',
        searchConfig(args) {
            return { url: this.host + '/bsearch?q=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll('div.box_k>ul>li'));
        },
        bookName(item) {
            return item.querySelector('h2.b_name>a').innerText;
        },
        bookAuthor(item) {
            return item.querySelector('p.b_info>span>a').innerText;
        },
        bookLink(item) {
            return this.host + item.querySelector('h2.b_name>a').href.replace(location.origin, '');
        },
        downloadLink(item) {
            return this.host + item.querySelector('h2.b_name>a').href.replace(location.origin, '');
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'wanbentxt': {
        name: 'wanbentxt',
        siteName: '完本神站',
        host: 'https://www.wanbentxt.com',
        _isBookList: false,
        _isBookListChecked: false,
        _isBlockedBySearchTimeGap: false,
        _isBlockedBySearchTimeGapChecked: false,
        _isCurrentBookList(item) {
            //如果匹配到的搜索结果只有一条, 会直接进入对应的书籍详情页,因此需要判断一下
            if (!this._isBookListChecked) {
                this._isBookList = !item.querySelector('div.contentDiv > div > div.detail');
                this._isBookListChecked = true;
            }
            return this._isBookList;
        },
        _isCurrentBlockedBySearchTimeGap(item) {
            //由于搜索时间间隔过短被限制
            if (!this._isBlockedBySearchTimeGapChecked) {
                this._isBlockedBySearchTimeGap = (item.querySelector("div.blockcontent") || { innerText: '' }).innerText.includes('间隔时间不得少于');
                this._isBlockedBySearchTimeGapChecked = true;
            }
            return this._isBlockedBySearchTimeGap;
        },
        searchConfig(args) {
            let data = 'searchtype=articlename&searchkey=' + GBK.URI.encodeURI(args.bookName);
            let headers = { "Content-Type": "application/x-www-form-urlencoded;charset=gbk", "Cookie": "jieqiVisitTime=jieqiArticlesearchTime%3d" + (Date.parse(new Date) / 1000 - 240) };
            let details = { url: this.host + '/modules/article/search.php', data: data, method: 'POST', headers: headers, overrideMimeType: 'text/html;charset=gbk' };
            //Tampermonkey 有个BUG, anonymous = true 时 overrideMimeType 无效
            if (!isTampermonkey()) {
                Object.assign(details, { anonymous: true });
            }
            return details;

        },
        bookList(item) {
            //检查是否由于搜索时间间隔问题而被限制
            this._isCurrentBlockedBySearchTimeGap(item);
            //书籍详情页, 包装下 html 直接返回即可
            if (this._isCurrentBookList(item)) {
                return Array.from(item.querySelectorAll('body > div.contentDiv > div > div.result > div.resultLeft > ul > li'));
            }
            return Array.from(item.querySelectorAll('body'));

        },
        bookName(item) {
            if (this._isCurrentBookList(item)) {
                return item.querySelector('div.sortPhr > a').innerText;
            }
            return item.querySelector('div.contentDiv > div > div.detail > div.detailTop > div.detailTopMid > div.detailTitle > h1').innerText;
        },
        bookAuthor(item) {
            if (this._isCurrentBookList(item)) {
                return item.querySelector('div.sortPhr > p.author > a').innerText;
            }
            return item.querySelector('div.contentDiv > div > div.detail > div.detailTop > div.detailTopMid > div.writer > a').innerText;
        },
        bookLink(item) {
            let href;
            if (this._isCurrentBookList(item)) {
                href = item.querySelector('div.sortPhr > a').href;
            } else {
                href = item.querySelector('div.contentDiv > div > div.route').lastElementChild.href;
            }
            return handleUrl(url,location.origin,this.host);
        },
        downloadLink(item) {
            return this.host + '/down' + this.bookLink(item).replace(location.origin, '').replace(this.host, '');
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_PROCESS })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
        //bookLink是否应该添加缓存
        shouldCacheBookLink(options) {
            return !this._isCurrentBlockedBySearchTimeGap(options.item);
        },
    },
    'afs360': {
        name: 'afs360',
        siteName: '万书网',
        host: 'https://www.afs360.com',
        searchConfig(args) {
            let data = 'show=title&keyboard=' + args.bookName;
            let headers = { "Content-Type": "application/x-www-form-urlencoded", "Cookie": "txt2017lastsearchtime=" + (Date.parse(new Date) / 1000 - 480) };
            return { url: this.host + '/e/search/index.php', data: data, method: "POST", headers: headers, anonymous: true };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("tr > td > table.box > tbody > tr > td > h2"));
        },
        bookName(item) {
            return item.querySelector('a').innerText;
        },
        bookAuthor(item) {
            return item.lastChild.textContent.slice(1, -1);
        },
        bookLink(item) {
            return this.host + item.querySelector('a').href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'auzw': {
        name: 'auzw',
        siteName: '傲世中文网',
        host: 'https://www.auzw.com',
        searchConfig(args) {
            return { url: this.host + '/search.php?q=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("div > div> div.book_info"));
        },
        bookName(item) {
            return item.querySelector("a").innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return item.querySelector("p.nowrap > a").innerText;
        },
        bookLink(item) {
            return this.host + item.querySelector('a').href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            return this.host + item.querySelector('small > a').href.replace(location.origin, '').replace(this.host, '');
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_PROCESS })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'mianhuatang': {
        name: 'mianhuatang',
        siteName: '棉花糖小说网',
        host: 'http://www.mhtwx.la',
        searchConfig(args) {
            let data = 'searchkey=' + args.bookName;
            let headers = { "Content-Type": "application/x-www-form-urlencoded" };
            return { url: this.host + '/search.php', data: data, method: "POST", headers: headers };
        },
        bookList(item) {
            //从第一行开始, 去除表头
            return Array.from(item.querySelectorAll("div.main > div > table > tbody > tr")).slice(1);
        },
        bookName(item) {
            return item.querySelector("a").innerText;
        },
        bookAuthor(item) {
            return item.children[2].innerText;
        },
        bookLink(item) {
            return this.host + item.querySelector('a').href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            let bookId = this.bookLink(item).match(/\/(\d+)\/$/)[1];
            return this.host + `/down/txt${bookId}.html`;
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_PROCESS })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'balingtxt': {
        name: 'balingtxt',
        siteName: '八零电子书',
        host: 'http://www.balingtxt.com',
        searchConfig(args) {
            let data = 'searchkey=' + args.bookName + '&s=18140131260432570322';
            let headers = { "Content-Type": "application/x-www-form-urlencoded" };
            return { url: this.host + '/modules/article/search.php', data: data, method: "POST", headers: headers };
        },
        bookList(item) {
            //从第一行开始, 去除表头
            return Array.from(item.querySelectorAll("#content > div > ul > li.storelistbt5a"));
        },
        bookName(item) {
            return item.querySelector("a.bookname").innerText.match(/《(.*?)》/)[1];
        },
        bookAuthor(item) {
            return item.querySelector("p > a").innerText;
        },
        bookLink(item) {
            return this.host + item.querySelector('a.bookname').href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            return this.bookLink(item).replace('.html', '/down.html');
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_PROCESS })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'dushuxiaozi': {
        name: 'dushuxiaozi',
        siteName: '读书小子',
        host: 'https://www.dushuxiaozi.com',
        searchConfig(args) {
            return { url: this.host + '/?s=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("#main > ul > li.entry-title > a"));
        },
        bookName(item) {
            return item.innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.innerText);
        },
        bookLink(item) {
            return item.href.replace(location.origin, '');
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'jingjiaocangshu': {
        name: 'jingjiaocangshu',
        siteName: '精校藏书',
        host: 'https://jingjiaocangshu.cn',
        searchConfig(args) {
            return { url: this.host + '/?s=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("#primary-home > article"));
        },
        bookName(item) {
            return item.querySelector("header > h1").innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.querySelector("header > h1").innerText);
        },
        bookLink(item) {
            //实际是下载链接
            return this.host + item.querySelector("div.entry-content > div > div > a").href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'kenshula': {
        name: 'kenshula',
        siteName: '啃书啦',
        host: 'https://www.kenshula.com',
        _isBookList: false,
        _isBookListChecked: false,
        _isCurrentBookList(item) {
            //如果匹配到的搜索结果只有一条, 会直接进入对应的书籍详情页,因此需要判断一下
            if (!this._isBookListChecked) {
                this._isBookList = item.querySelector('#conn > div > div.fleft.column-l > div > div.search-title');
                this._isBookListChecked = true;
            }
            return this._isBookList;
        },
        searchConfig(args) {
            let data = 'area=2&searchkey=' + GBK.URI.encodeURI(args.bookName);
            let headers = { "Content-Type": "application/x-www-form-urlencoded" };
            return { url: this.host + '/modules/article/search.php', data: data, method: 'POST', headers: headers, overrideMimeType: 'text/html;charset=gbk', anonymous: true };

        },
        bookList(item) {
            if (this._isCurrentBookList(item)) {
                return Array.from(item.querySelectorAll('#conn > div > div.fleft.column-l > div > ul > li'));
            }
            return Array.from(item.querySelectorAll('head'));

        },
        bookName(item) {
            if (this._isCurrentBookList(item)) {
                return item.querySelector("div > h3 > a").innerText;
            }
            return item.querySelector('meta[property="og:novel:book_name"]').getAttribute("content");
        },
        bookAuthor(item) {
            if (this._isCurrentBookList(item)) {
                return item.querySelector("p.author").lastChild.wholeText;
            }
            return item.querySelector('meta[property="og:novel:author"]').getAttribute("content");
        },
        bookLink(item) {
            if (this._isCurrentBookList(item)) {
                return this.host + item.querySelector("div > h3 > a").href.replace(location.origin, '').replace(this.host, '');
            }
            return item.querySelector('meta[property="og:novel:read_url"]').getAttribute("content");
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'wucuo8': {
        name: 'wucuo8',
        siteName: '无错吧',
        host: 'https://www.wucuo8.com',
        searchConfig(args) {
            let data = 'tempid=1&tbname=xs&show=writer,title&keyboard=' + args.bookName;
            let headers = { "Content-Type": "application/x-www-form-urlencoded", "Cookie": "swmmjlastsearchtime=" + (Date.parse(new Date) / 1000 - 480) };
            return { url: this.host + '/e/search/index.php', data: data, method: "POST", headers: headers, anonymous: true };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll("div.row.md.bread.w15-1 > div > div"));
        },
        bookName(item) {
            return item.querySelector("div.xq > h2 > a").innerText.split("t")[0];
        },
        bookAuthor(item) {
            return item.querySelector("div.xq > p.writer.ellipsis").innerText.split("|")[2].replace(/\s/g, '');
        },
        bookLink(item) {
            return this.host + item.querySelector("div.xq > h2 > a").href.replace(location.origin, '').replace(this.host, '');
        },
        downloadLink(item) {
            let url = item.querySelector("div.col.xs-24.md-9.loadbutton > a").href;
            return handleUrl(url,location.origin,this.host);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'zxcsinfo': {
        name: 'zxcsinfo',
        siteName: '知轩藏书(info)',
        host: 'https://www.zxcs.info',
        searchConfig(args) {
            return { url: this.host + '/index.php?keyword=' + args.bookName, method: "GET" };
        },
        bookList(item) {
            return Array.from(item.getElementsByTagName('dl'));
        },
        bookName(item) {
            return item.children["0"].innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.children["0"].innerText);
        },
        bookLink(item) {
            let url = item.children["0"].getElementsByTagName('a')[0].href;
            return handleUrl(url,location.origin,this.host);  
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
        // type: DOWNLOAD_TYPE_FETCH 需设置
        fetchConfig(options) {
            return { url: options.url, method: 'GET' };
        },
    },
    'zxcstxt': {
        name: 'zxcstxt',
        siteName: '知轩藏书(txt)',
        host: 'https://zxcstxt.com',
        searchConfig(args) {
            return { url: this.host + '/?s=' + args.bookName + '&type=post', method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll('.posts-item'));
        },
        bookName(item) {
            return item.children["0"].innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.children["0"].innerText);
        },
        bookLink(item) {
            let url = item.children["0"].getElementsByTagName('a')[0].href;
            let bookId = parseInt(url.split("/").pop());
            return `${this.host}/download?post=${bookId}`
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
    },
    'zxcsol': {
        name: 'zxcsol',
        siteName: '知轩藏书(ol)',
        host: 'https://zxcsol.com',
        searchConfig(args) {
            return { url: this.host + '/?s=' + args.bookName + '&type=post', method: "GET" };
        },
        bookList(item) {
            return Array.from(item.querySelectorAll('.posts-item'));
        },
        bookName(item) {
            return item.children["0"].innerText.match('《(.*?)》')[1];
        },
        bookAuthor(item) {
            return getAuthorName(item.children["0"].innerText);
        },
        bookLink(item) {
            let url = item.children["0"].getElementsByTagName('a')[0].href;
            let bookId = parseInt(url.split("/").pop());
            return `${this.host}/download?post=${bookId}`
        },
        downloadLink(item) {
            return this.bookLink(item);
        },
        handler(options) {
            return getDownLoadLink((Object.assign(options, { type: DOWNLOAD_TYPE_DIRECT })));
        },
        parse(bookInfo, handler, response) {
            return parseRawDownloadResponse(bookInfo, handler, response);
        },
    },
};

/**
 * 需要添加下载链接的网站路由
 * 转换为 downloadSiteTargetConfig 的键名
 */
const downloadSiteTargetRoute = {
    'www.yousuu.com': {
        isValid: () => location.pathname.split('/')[1] === 'book',
        targetConfig: () => 'yousuu'

    },
    'www.yuzuhon.com': {
        isValid: () => true,
        targetConfig: () => 'yuzuhon'
    },
};

/**
 * 需要添加下载链接的网站配置
 */
const downloadSiteTargetConfig = {
    'yousuu': {
        name: 'yousuu',
        siteName: '优书网',
        _utils: {},
        //预处理
        prepare() {
            //获取dataV的值
            let node = document.querySelector('div.common-card-layout.main-left-header');
            let dataV = node.outerHTML.match(/data-v-(\w+)/g);
            this._utils.dataV = dataV;
            //插入下载容器
            let content = '<div ' + dataV[0] + '="" class="common-card-layout main-left-header" id="gm-insert-download-box" style="display: none">'
                + '<div ' + dataV[1] + '="" ' + dataV[2] + '="" class="tabs" id="gm-insert-download-content"></div></div>';
            document.querySelector('div.common-card-layout.main-left-header').insertAdjacentHTML('beforebegin', content);
        },
        bookName() {
            return document.querySelector('div.book-info-wrap>div.book-info-detail>h1.book-name').innerText;
        },
        bookAuthor() {
            let author = document.querySelector('div.book-info-wrap>div.book-info-detail>p.book-author>a').innerText;
            if (!author) {
                return '';
            }
            return getAuthorName(author) || author;
        },
        //获取下载链接后的处理
        task(info) {
            let obj = document.querySelector('#gm-insert-download-content');
            let item = '';
            //如果第一次插入,则显示父容器,同时插入标识
            if (obj.parentElement.style.display === 'none') {
                obj.parentElement.setAttribute('style', 'display:run-in');
                item = '<label ' + this._utils.dataV[3] + '="" class="tab current">下载地址</label>';
            }
            item += '<label ' + this._utils.dataV[3] + '="" class="tab"><a href="' + info.downloadLink + '" target="_blank">' + info.siteName + '</a></label>';
            obj.insertAdjacentHTML('beforeend', item);
        },
    },
    'yuzuhon': {
        name: 'yuzuhon',
        siteName: '柚子书',
        //预处理
        prepare() {
            let content =
                `<section class="book-page__section mt-5" id="gm-insert-download-box" style="display: none">
                    <div class="section-head pl-0 border-0">
                        <h2 class="section-head__title">下载地址</h2>
                    </div>
                    <div class="rich-content is-collapsed">
                        <div class="font" id="gm-insert-download-content">
                        </div>
                    </div>
                </section>`;
            //插入下载容器
            document.querySelector('#__layout > div > div.app-main > div > div.container > div > section').insertAdjacentHTML('afterend', content);
        },
        bookName() {
            return document.querySelector("#__layout > div > div.app-main > div > div.container > div.book-info-section > h2").innerText;
        },
        bookAuthor() {
            return document.querySelector("#__layout > div > div.app-main > div > div.container > div.book-info-section > div > div.book-info__metadata > div > span > a").innerText;
        },
        //获取下载链接后的处理
        task(info) {
            let obj = document.querySelector('#gm-insert-download-content');
            let box = document.querySelector('#gm-insert-download-box');
            let item = '';
            //如果第一次插入,则显示父容器,同时插入标识
            if (box.style.display === 'none') {
                box.setAttribute('style', 'display:run-in');
                item = `<a href="${info.downloadLink}" target="_blank" class="">${info.siteName}</a>`;
            } else {
                item =
                    `<span>
                        <span class="dot"></span>
                        <a href="${info.downloadLink}" target="_blank" class="">${info.siteName}</a>
                    </span> `;
            }
            obj.insertAdjacentHTML('beforeend', item);
        },
    }
}

/**
 * 其他链接来源的相关配置
 */
const linkSiteSourceConfig = {
    'baike': {
        name: 'baike',
        siteName: '百度百科',
        link(info) {
            return 'https://baike.baidu.com/search?word=' + info.bookName;
        },
    }
}

/**
 * 需要添加其他链接的网站路由
 * 转换为 downloadSiteTargetConfig 的键名
 */
const linkSiteTargetRoute = {
    'www.yousuu.com': {
        isValid: () => location.pathname.split('/')[1] === 'book',
        targetConfig: () => 'yousuu'
    },
};

/**
 * 需要添加其他链接的网站配置
 */
const linkSiteTargetConfig = {
    'yousuu': {
        name: 'yousuu',
        siteName: '优书网',
        _utils: {},
        //预处理
        prepare() {
            //获取dataV的值
            let node = Array.from(document.querySelectorAll('div.common-card-layout.main-left-header')).pop();
            let dataV = node.outerHTML.match(/data-v-(\w+)/g);
            this._utils.dataV = dataV;
            //插入下载容器
            let content = '<div ' + dataV[0] + '="" class="common-card-layout main-left-header" id="gm-insert-link-box" style="display: none">'
                + '<div ' + dataV[1] + '="" ' + dataV[2] + '="" class="tabs" id="gm-insert-link-content"></div></div>';
            document.querySelector('div.common-card-layout.main-left-header').insertAdjacentHTML('beforebegin', content);
        },
        bookName() {
            return document.querySelector('div.book-info-wrap>div.book-info-detail>h1.book-name').innerText;
        },
        bookAuthor() {
            return document.querySelector('div.book-info-wrap>div.book-info-detail>p.book-author>a').innerText;
        },
        //获取下载链接后的处理
        task(info) {
            let obj = document.querySelector('#gm-insert-link-content');
            let item = '';
            //如果第一次插入,则显示父容器,同时插入标识
            if (obj.parentElement.style.display === 'none') {
                obj.parentElement.setAttribute('style', 'display:run-in');
                item = '<label ' + this._utils.dataV[3] + '="" class="tab current">链接</label>';
            }
            item += '<label ' + this._utils.dataV[3] + '="" class="tab"><a href="' + info.link + '" target="_blank">' + info.siteName + '</a></label>';
            obj.insertAdjacentHTML('beforeend', item);
        },
    },
}

/**
 * 使用 ajax 翻页网站配置
 */
const ajaxSiteTargetRoute = {
    'www.yousuu.com': () => {
        let prefix = 'yousuu.';
        let pathnames = location.pathname.split('/');
        switch (pathnames[1]) {
            case 'booklist':
            case 'explore':
                return `${prefix}${pathnames[1]}`
            default:
                return null;
        }
    },
}
const ajaxSiteTargetConfig = {
    'yousuu.booklist': {
        name: 'yousuu.booklist',
        handler: () => { return callbackWhenUrlChange },
    },
    'yousuu.explore': {
        name: 'yousuu.explore',
        handler: () => { return callbackWhenYousuuExploreFeedLoad },
    }
}
/*======================================================================================================*/

/*===============================================  方法  ================================================*/

/*===============================================  util  ===============================================*/

/**
 * 数组的差集
 * @param {array} firstArray
 * @param {array} secondArray
 * @returns {array}
 */
let arrayDiff = (firstArray, secondArray) => {
    let set = new Set(secondArray);
    return [...firstArray].filter(item => !set.has(item));
}

let log = (...data) => IS_DEBUG && console.log(data);
/*=====================================================================================================*/


/*===============================================  base  ===============================================*/
/**
 * xhr
 * @param options 须满足 GM_xmlhttpRequest 参数
 */
let fetch = async (options) => {
    return new Promise((resolve, reject = (response, url = options.url) => {
        console.log(
            'Error getting ' + url + ' (' + response.status + ' ' + response.statusText + ')'
        );
    }) => {
        GM_xmlhttpRequest({
            onload(response) {
                if (response.status >= 200 && response.status < 400) {
                    resolve(response);
                } else {
                    reject(response);
                }
            },
            onerror(response) {
                reject(response);
            },
            ...options
        });
    });
};

/**
 * 处理提取到的 url, 去除 location.ogrigin,拼接 site host
 * @param {string} url 
 * @param {string} locationOrigin string 
 * @param {string} siteHost string
 * @returns {string}
 */
let handleUrl = (url, locationOrigin, siteHost) => {
    if(!url){
        return '';
    }
    url = url.replace(locationOrigin,'');
    if(url.startsWith('http') || url.startsWith('www') || url.startsWith(siteHost)){
        return url;
    }
    return siteHost + url.replace(siteHost,'');
}

/**
 * 提取冒号后的 author name
 * @param {string} str 
 * @returns {string}
 */
let getAuthorName = (str) => {
    if(!str){
        return '';
    }
    let sep = ':';
    if(!str.includes(sep)){
        sep = ':';
    }
    return (str.split(sep)?.pop() || '').trim();
}
/*=====================================================================================================*/

/*===============================================  评分  ===============================================*/
/**
 * 获取评分信息
 * @param handler
 * @param bookInfo {bookName:'',bookAuthor:''}
 */
let getRateInfo = async (handler, bookInfo) => {
    let cacheKeyPrefix = `GET:RATE:${handler.name.toUpperCase()}`;
    let cacheKey = null;
    if (bookInfo.bookId) {
        cacheKey = `${cacheKeyPrefix}:ID:${bookInfo.bookId}`;
    } else {
        cacheKey = `${cacheKeyPrefix}:NAME:${bookInfo.bookName}:AUTHOR:${bookInfo.bookAuthor}`;
    }
    //查询缓存
    let cacheValue = storage.getValue(cacheKey, SEARCH_EXPIRED_TIME);
    if (cacheValue !== null) {
        return cacheValue;
    }
    let response = await fetch(handler.request(bookInfo));
    log(response,response?.responseText);
    let data = handler.parse(bookInfo, response);
    storage.setValue(cacheKey, data);
    return data;
};

/**
 * 将评分插入目标网站
 * @param options
 */
let insertRate = async (options) => {
    let siteConfig = rateSiteTargetConfig[options.site];
    let args = { bookName: siteConfig.bookName(options.item), bookAuthor: siteConfig.bookAuthor(options.item), maxNum: siteConfig.maxNum };
    //判断是否可以获取到 bookId
    if (siteConfig.hasOwnProperty('bookId')) {
        let bookId = siteConfig.bookId(options.item);
        if (bookId) {
            args.bookId = bookId;
        }
    }
    let data = await getRateInfo(rateSiteSourceConfig[options.rateSourceSite], args);
    if (data.match) {
        siteConfig.anchorObj(options.item).insertAdjacentHTML(siteConfig.anchorPos, siteConfig.rateItem(data.score, data.num, data.url));
    }
};

/**
 * 插入评分(入口)
 * @param hostname
 */
let insertBookRate = (hostname) => {
    if (Object.keys(rateSiteTargetRoute).includes(hostname)) {
        let site = rateSiteTargetRoute[hostname]();
        if (!site) {
            return;
        }
        let siteConfig = rateSiteTargetConfig[site];
        if (!siteConfig) {
            return;
        }
        let options = { site: site, rateSourceSite: 'yousuu' };
        siteConfig.handler(options, insertRate);
    }
}

/*=====================================================================================================*/

/*===============================================  下载  ===============================================*/


/**
 * 获取需要排除的下载源
 * @returns Array
 */
let getExpectedSites = () => {
    let sites = GM_getValue(EXCEPTED_DOWNLOAD_SITES_KEY, []);
    return Array.isArray(sites) ? sites : [];
}
/**
 * 排除下载源
 */
let exceptDownloadSites = () => {
    let sites = getExpectedSites().map((site) => {
        return downloadSiteSourceConfig[site]?.siteName || "";
    }).filter(site => site != null && site != undefined);

    let input = prompt("请输入要排除的下载站(下载地址中的网站名,逗号分隔):", sites.join(",") || "");
    if (input == null || input == undefined) {
        return;
    }

    let siteNameMap = new Map();
    for (const [key, value] of Object.entries(downloadSiteSourceConfig)) {
        siteNameMap.set(value.siteName, key);
    }

    let savedSites = input.split(/[,,]/).map((item) => {
        return siteNameMap.get(item.trim());
    }).filter((item) => item != null && item != undefined);

    GM_setValue(EXCEPTED_DOWNLOAD_SITES_KEY, savedSites);

    if (confirm("刷新当前页面?")) {
        window.location.reload();
    }
};
/**
 * 解析下载链接
 * @param options
 */
let getDownLoadLink = async (options) => {
    let type = options.type;
    let siteConfig = downloadSiteSourceConfig[options.site];
    if (type === DOWNLOAD_TYPE_DIRECT) { //直接获取
        return { downloadLink: options.bookLink, siteName: siteConfig.siteName };
    } else if (type === DOWNLOAD_TYPE_FETCH) { //从链接中再次解析
        //缓存
        let cacheKey = 'GET:DOWNLOADLINK:' + siteConfig.name.toUpperCase() + ':' + hex_md5(options.bookLink);
        let cacheValue = storage.getValue(cacheKey, DOWNLOAD_EXPIRED_TIME);
        if (cacheValue !== null) {
            return { downloadLink: cacheValue, siteName: siteConfig.siteName };
        }
        let response = await fetch(siteConfig.fetchConfig({ url: options.bookLink }));
        log(response,response?.responseText);
        let html = new DOMParser().parseFromString(response.responseText, "text/html");
        let downloadLink = siteConfig.downloadLink(html);
        storage.setValue(cacheKey, downloadLink);
        return { downloadLink: downloadLink, siteName: siteConfig.siteName };
    } else if (type === DOWNLOAD_TYPE_PROCESS) {
        return { downloadLink: siteConfig.downloadLink(options.bookItem), siteName: siteConfig.siteName };
    }
};

/**
 * 从原始响应返回解析下载信息
 * @param bookInfo
 * @param response
 */
let parseRawDownloadResponse = (bookInfo, handler, response) => {
    let html = new DOMParser().parseFromString(response.responseText, "text/html");
    log(html);
    let bookList = handler.bookList(html);
    let bookLink = '';
    let bookItem = '';
    let match = bookList.some((item) => {
        IS_DEBUG && log(handler.bookName(item).trim(),bookInfo.bookName.trim(),handler.bookAuthor(item).trim(),bookInfo.bookAuthor.trim());
        if (handler.bookName(item).trim() === bookInfo.bookName.trim() && handler.bookAuthor(item).trim() === bookInfo.bookAuthor.trim()) {
            bookItem = item;
            bookLink = handler.bookLink(item);
            return true;
        }
    });
    let data = { bookLink: bookLink, bookItem: bookItem, match: match };
    let cache = { bookLink: bookLink, bookItem: (match ? bookItem.innerHTML : ''), match: match };
    return [data, cache];
};

/**
 * 获取下载信息
 * @param handler
 * @param bookInfo
 */
let getDownloadInfo = async (handler, bookInfo) => {
    //查缓存
    let cacheKey = 'GET:BOOKLINK:' + handler.name.toUpperCase() + ':NAME:' + bookInfo.bookName + ':AUTHOR:' + bookInfo.bookAuthor;
    let cacheValue = storage.getValue(cacheKey, DOWNLOAD_EXPIRED_TIME);
    if (cacheValue !== null) {
        return { bookLink: cacheValue.bookLink, bookItem: new DOMParser().parseFromString(cacheValue.bookItem, "text/html"), match: cacheValue.match };
    }

    let response = await fetch(handler.searchConfig({ bookName: bookInfo.bookName }));
    log(response?.responseText);
    let [data, cache] = handler.parse(bookInfo, handler, response);
    //判断是否应该添加缓存
    if (!handler.hasOwnProperty('shouldCacheBookLink') || handler.shouldCacheBookLink({ item: data.bookItem })) {
        storage.setValue(cacheKey, cache);
    }
    return data;
};

/**
 * 将下载链接插入目标网站
 * @param options
 */
let insertDownload = async (options) => {
    let target = downloadSiteTargetConfig[options.downloadTargetSite];
    let bookInfo = { bookName: target.bookName(), bookAuthor: target.bookAuthor() };
    let source = downloadSiteSourceConfig[options.site];
    //获取下载信息
    let downloadInfo = await getDownloadInfo(source, bookInfo);
    if (downloadInfo.match) {
        //解析下载链接
        let data = await source.handler({ site: options.site, ...downloadInfo });
        //处理下载链接
        return target.task(data);
    }
};

/**
 * 插入下载链接(入口)
 * @param hostname
 */
let insertBookDownloadLink = async (hostname) => {
    if (!Object.keys(downloadSiteTargetRoute).includes(hostname)) {
        return;
    }
    let sites = arrayDiff(Object.keys(downloadSiteSourceConfig), getExpectedSites());
    let targetRoute = downloadSiteTargetRoute[hostname];
    if (!targetRoute.isValid()) {
        return;
    }
    let downloadTargetSite = targetRoute.targetConfig();
    let targetConfig = downloadSiteTargetConfig[downloadTargetSite];
    targetConfig.prepare();
    let promises = sites.map((site) => insertDownload({ site: site, downloadTargetSite: downloadTargetSite }).catch(e => console.log(e)));
    await Promise.all(promises);
}

/**
 * 从来源处解析并插入链接
 * @param {String} site
 * @param {Object} bookInfo
 */
let insertLinkFromSource = async (site, targetConfig) => {
    let siteConfig = linkSiteSourceConfig[site];
    let bookName = targetConfig.bookName();
    let bookAuthor = targetConfig.bookAuthor();
    if (!bookName || !bookAuthor) {
        return;
    }
    let bookInfo = { bookName: bookName, bookAuthor: bookAuthor };
    let link = siteConfig.link(bookInfo);
    targetConfig.task({ link: link, siteName: siteConfig.siteName });
}
/**
 *
 * 插入下载链接
 * @param {String} hostname
 */
let insertLinks = async (hostname) => {
    if (!Object.keys(linkSiteTargetRoute).includes(hostname)) {
        return;
    }
    let targetRoute = linkSiteTargetRoute[hostname];
    if (!targetRoute.isValid()) {
        return;
    }
    let targetConfig = linkSiteTargetConfig[targetRoute.targetConfig()];
    targetConfig.prepare();
    let promises = Object.keys(linkSiteSourceConfig).map((site) => insertLinkFromSource(site, targetConfig).catch(e => console.log(e)));
    await Promise.all(promises);
}

/*=====================================================================================================*/

/*===============================================  UI  ===============================================*/

/**
 * 注册菜单
 */
let registerMenu = () => {
    GM_registerMenuCommand('排除不需要的下载源', exceptDownloadSites, 'E');
};
/**
 * 检查插件兼容性
 */
let checkCanUse = () => {
    let message = '脚本 [优书网 <=> 知轩藏书] ';
    let canUse = true;
    if (typeof GM_xmlhttpRequest === "undefined") {
        message += "暂不支持 Greasemonkey 4.x, 请使用 Tampermonkey 或 Violetmonkey 。";
        canUse = false;
    }
    return { canUse: canUse, message: message };
};

let isTampermonkey = () => {
    return (GM_info || { scriptHandler: '' }).scriptHandler.toLowerCase() === SCRIPT_HANDLER_TAMPERMONKEY;
}

/**
 * 开始执行脚本
 */
let start = () => {
    'use strict';
    //检查兼容性
    let checkResult = checkCanUse();
    if (!checkResult.canUse) {
        alert(checkResult.message);
        return;
    }
    //插入评分
    insertBookRate(location.hostname);
    //插入下载链接
    insertBookDownloadLink(location.hostname);
    //插入其他链接
    insertLinks(location.hostname);
}

/**
 * 延时执行脚本
 * @param {integer} interval
 */
let startWithInterval = (interval) => {
    window.setTimeout(start, interval);
}

/**
 * 当前站点是否触发 ready 事件
 * @param {string} hostname
 */
let isSiteTriggerReadyEvent = (hostname) => {
    return !Object.keys(SITES_WAIT_KEY_ELEMENT).includes(hostname);
}

/**
 * 页面 url 参数变化时回调
 * @param {CallableFunction} callback
 */
let callbackWhenUrlChange = (callback) => {
    if (this.lastPathStr !== location.pathname
        || this.lastQueryStr !== location.search
        || this.lastHashStr !== location.hash
    ) {
        this.lastPathStr = location.pathname;
        this.lastQueryStr = location.search;
        this.lastHashStr = location.hash;
        callback();
    }
}

/**
 * 优书网发现页面最后一个 bookId 变化时回调
 * @param {CallableFunction} callback
 */
let callbackWhenYousuuExploreFeedLoad = (callback) => {
    let bookId = document.querySelector('#app > div.app-main > section > div > div.left > div:not([style*="display:none"]):not([style*="display: none"]) >div:nth-last-child(2)').getAttribute("bookid");
    if (this.lastYousuuExploreFeedBookId !== bookId) {
        this.lastYousuuExploreFeedBookId = bookId;
        callback();
    }
}

let ajaxSiteConfig = (hostname) => {
    if (!Object.keys(ajaxSiteTargetRoute).includes(hostname)) {
        return null;
    }
    let site = ajaxSiteTargetRoute[hostname]();
    if (!site) {
        return null;
    }
    return ajaxSiteTargetConfig[site];
}

let startWhenAjax = () => {
    let config = ajaxSiteConfig(location.hostname);
    if (!config) {
        return false;
    }
    window.setInterval(config.handler(), 1000, () => startWithInterval(1000));
    return true;
}

/*======================================================================================================*/
/**
 * 入口
 */

registerMenu();

//使用 ajax 翻页的网站
if (startWhenAjax()) {
    return;
}

if (isSiteTriggerReadyEvent(location.hostname)) {
    startWithInterval(1000);
    return;
}
waitForKeyElements(SITES_WAIT_KEY_ELEMENT[location.hostname], () => startWithInterval(1000));