// ==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"> 评分:' + rate + ' 人数:' + 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> 人数:' + 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 ' 评分:<a href="' + bookLink + '" target="_blank"><span style="color:#0066FF">' + rate + '</span></a> 人数:' + 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> 人数:${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} 人数:${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} 人数:${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> 人数: ${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} 人数: ${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));