// ==UserScript==
// @name 轻小说文库+
// @namespace Wenku8+
// @version 0.9.2
// @description 章节批量下载,版权限制小说TXT简繁全本下载,书名/作者名双击复制,Ctrl+Enter快捷键发表书评,单章节下载,小说JPEG插图下载,下载线路点击切换,书评帖子全贴下载保存
// @author PY-DNG
// @match http*://www.wenku8.net/*
// @connect wenku8.com
// @connect wenku8.net
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_info
// @noframes
// ==/UserScript==
// 记录
// 阅读API:http://dl.wenku8.com/pack.php?aid=2478&vid=92914
// 回帖API:https://www.wenku8.net/modules/article/reviewshow.php?rid=209631&aid=2751
(function() {
'use strict';
// CONSTS
const HTML_DOWNLOAD_CONTENER = '<div id="dctn" style=\"margin:0px auto;overflow:hidden;\">\n<fieldset style=\"width:820px;height:35px;margin:0px auto;padding:0px;\">\n<legend><b>《{BOOKNAME}》小说TXT简繁全本下载</b></legend>\n</fieldset>\n</div>';
const HTML_DOWNLOAD_LINKS = '<div class="even">\n<span>简体(G)(<a class="dlink" href="http://dl.wenku8.com/down.php?type=txt&id={BOOKID}" target="_black">载点一</a> \n<a class="dlink" href="http://dl.wenku8.com/down.php?type=txt&id={BOOKID}&fname=%B0%B2%B4%EF%D3%EB%B5%BA%B4%E5" target="_black">载点二</a>)</span>\n\n<span>简体(U)(<a class="dlink" href="http://dl.wenku8.com/down.php?type=utf8&id={BOOKID}" target="_black">载点一</a> \n<a class="dlink" href="http://dl.wenku8.com/down.php?type=utf8&id={BOOKID}&fname=%B0%B2%B4%EF%D3%EB%B5%BA%B4%E5" target="_black">载点二</a>)</span>\n\n<span>繁体(U)(<a class="dlink" href="http://dl.wenku8.com/down.php?type=big5&id={BOOKID}" target="_black">载点一</a> \n<a class="dlink" href="http://dl.wenku8.com/down.php?type=big5&id={BOOKID}&fname=%B0%B2%B4%EF%D3%EB%B5%BA%B4%E5" target="_black">载点二</a>)</span>\n </div>';
const HTML_DOWNLOAD_BOARD = '[轻小说文库+] 为您提供《{BOOKNAME}》的TXT简繁全本下载!</br>由此产生的一切法律及其他问题均由脚本用户承担</br>—— PY-DNG';
const CSS_DOWNLOAD = '.even {display: grid; grid-template-columns: repeat(3, 1fr); text-align: center;} .dlink {text-align: center;}';
const CSS_COMMON = '.plusbtn {color: rgb(0, 160, 0);} .plusbtn:hover {color: rgb(0, 100, 0);} .plusbtn:focus {color: rgb(0, 100, 0);}';
const CSS_COLOR_BTN_NORMAL = 'rgb(0, 160, 0)', CSS_COLOR_BTN_HOVER = 'rgb(0, 100, 0)';
const CLASSNAME_BUTTON = 'plusbtn';
const TEXT_TIP_COPY = '双击复制';
const TEXT_TIP_SERVERCHANGE = '点击切换线路';
const TEXT_GUI_DOWNLOAD_IMAGE = '下载图片';
const TEXT_GUI_DOWNLOAD_TEXT = '下载本章';
const TEXT_GUI_DOWNLOAD_REVIEW = '[下载本帖(共A页)]';
const TEXT_GUI_DOWNLOADING_REVIEW = '[下载中...(C/A)]';
const TEXT_GUI_DOWNLOADFINISH_REVIEW = '[下载完毕]';
const TEXT_GUI_DOWNLOADING = ' 下载中...'; const REG_GUI_DOWNLOADING = new RegExp(TEXT_GUI_DOWNLOADING + '$');
const TEXT_GUI_DOWNLOADED = ' (下载完毕)'; const REG_GUI_DOWNLOADED = new RegExp(TEXT_GUI_DOWNLOADED.replaceAll(/([\(\)])+/g, '\\$1') + '$');
const TEXT_GUI_DOWNLOADING_ALL = '下载中...(C/A)';
const TEXT_GUI_DOWNLOADED_ALL = '下载图片(已完成)';
/* \t
┌┬┐┌─┐┏┳┓┏━┓╭─╮
├┼┤│┼│┣╋┫┃╋┃│╳│
└┴┘└─┘┗┻┛┗━┛╰─╯
╲╱╭╮
╱╲╰╯
*/
/* **output format: Review Name.txt**
** 轻小说文库-帖子 [ID: reviewid]
** title
** 保存自: reviewlink
** 保存时间: savetime
** By scriptname Ver. version, author authorname
**
** ──────────────────────────────
** [用户: username userid]
** 用户名: username
** 用户ID: userid
** 加入日期: 1970-01-01
** 用户链接: userlink
** 最早出现: 1楼
** ──────────────────────────────
** ...
** ──────────────────────────────
** [#1 2021-04-26 17:53:49] [username userid]
** ──────────────────────────────
** content - line 1
** content - line 2
** content - line 3
** ──────────────────────────────
**
** ──────────────────────────────
** [#2 2021-04-26 19:28:08] [username userid]
** ──────────────────────────────
** content - line 1
** content - line 2
** content - line 3
** ──────────────────────────────
**
** ...
**
**
** [THE END]
*/
const TEXT_SPLIT_LINE_CHAR = '━'; const TEXT_SPLIT_LINE = TEXT_SPLIT_LINE_CHAR.repeat(20)
const TEXT_OUTPUT_REVIEW_HEAD =
'轻小说文库-帖子 [ID: {RWID}]\n{RWTT}\n保存自: {RWLK}\n保存时间: {SVTM}\nBy {SCNM} Ver. {VRSN}, author {ATNM}'
const TEXT_OUTPUT_REVIEW_USER =
'{LNSPLT}\n[用户: {USERNM} {USERID}]\n用户名: {USERNM}\n用户ID: {USERID}\n加入日期: {USERJT}\n用户链接: {USERLK}\n最早出现: {USERFL}楼\n{LNSPLT}'
const TEXT_OUTPUT_REVIEW_FLOOR =
'{LNSPLT}\n[#{RPNUMB} {RPTIME}] [{USERNM} {USERID}]\n{LNSPLT}\n{RPTEXT}\n{LNSPLT}';
const TEXT_OUTPUT_REVIEW_END = '\n[THE END]';
/** DoLog相关函数改自 Ocrosoft 的 Pixiv Previewer
* [GitHub] Ocrosoft: https://github.com/Ocrosoft/
* [GreasyFork] Ocrosoft: https://greasyfork.org/zh-CN/users/63073
* [GreasyFork] Pixiv Previewer: https://greasyfork.org/zh-CN/scripts/30766
* [GitHub] Pixiv Previewer: https://github.com/Ocrosoft/PixivPreviewer
**/
let LogLevel = {
None: 0,
Error: 1,
Success: 2,
Warning: 3,
Info: 4,
Elements: 5,
};
let g_logCount = 0;
let g_logLevel = LogLevel.Success;
function DoLog(level, msgOrElement, isElement=false) {
if (level <= g_logLevel) {
let prefix = '%c';
let param = '';
if (level == LogLevel.Error) {
prefix += '[Error]';
param = 'color:#ff0000';
} else if (level == LogLevel.Success) {
prefix += '[Success]';
param = 'color:#00aa00';
} else if (level == LogLevel.Warning) {
prefix += '[Warning]';
param = 'color:#ffa500';
} else if (level == LogLevel.Info) {
prefix += '[Info]';
param = 'color:#888888';
} else if (level == LogLevel.Elements) {
prefix += 'Elements';
param = 'color:#000000';
}
if (level != LogLevel.Elements && !isElement) {
console.log(prefix + msgOrElement, param);
} else {
console.log(msgOrElement);
}
if (++g_logCount > 512) {
console.clear();
g_logCount = 0;
}
}
}
// Common actions
addStyle(CSS_COMMON);
// Get tab url api part
const API = window.location.href.replace(/https?:\/\/www\.wenku8\.net\//, '').replace(/\?.*/, '')
.replace(/^book\/\d+\.html?/, 'book').replace(/novel\/(\d+\/?)+\.html?$/, 'novel');
switch (API) {
// Dwonload page
case 'modules/article/packshow.php':
pageDownload();
break;
case 'modules/article/reviews.php':
case 'modules/article/reviewshow.php':
pageReview();
break;
// Index page
case 'index.php':
pageIndex();
break;
// Book page
case 'book':
pageBook();
break;
// Novel page
case 'novel':
pageNovel();
break;
// Other pages
default:
DoLog(LogLevel.Info, API);
}
// Book page add-on
function pageBook() {
const bookIdText = location.href.match(/\/(\d+)\.htm/)[1];
const bookNameElement = document.querySelector('#content > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > span:nth-child(1) > b:nth-child(1)');
const bookName = bookNameElement.innerText;
const authorNameElement = document.querySelector('#content > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2)');
const authorName = authorNameElement.innerText.substr(authorNameElement.innerText.indexOf(':') + 1);
const downloadEnabled = document.querySelector('#content > div:nth-child(1) > div > fieldset:nth-child(1) > legend:nth-child(1) > b:nth-child(1)') !== null;
const commentArea = document.querySelector('#pcontent');
const commentForm = document.querySelector('form[action^="https://www.wenku8.net/modules/article/reviews.php"]');
const commentSbmt = document.querySelector('td > input[name="Submit"]');
// Ctrl+Enter comment submit
commentSbmt.value = '发表书评(Ctrl+Enter)';
commentSbmt.style.padding = '0.3em 0.4em 0.3em 0.4em';
commentSbmt.style.height= 'auto';
commentArea.addEventListener('keydown', function() {
let keycode = event.keyCode;
if (keycode === 13 && event.ctrlKey && !event.altKey) {
commentForm.submit();
}
})
// Provide book & author name doubleclick copy
if (tipshow && tiphide) {
// tipshow and tiphide is coded inside wenku8 itself, its function is to show a text tip besides the mouse
bookNameElement.addEventListener('mouseover', function() {tipshow(TEXT_TIP_COPY);});
bookNameElement.addEventListener('mouseout' , tiphide);
authorNameElement.addEventListener('mouseover', function() {tipshow(TEXT_TIP_COPY);});
authorNameElement.addEventListener('mouseout' , tiphide);
} else {
bookNameElement.title = TEXT_TIP_COPY;
authorNameElement.title = TEXT_TIP_COPY;
}
bookNameElement.addEventListener('dblclick', function() {copyText(bookName);});
authorNameElement.addEventListener('dblclick', function() {copyText(authorName);});
// Provide txtfull download for book which download is disabled
if (!downloadEnabled) {
// Append download html model
const modelContainer = document.createElement('div');
document.querySelector('#content div').appendChild(modelContainer);
modelContainer.outerHTML = HTML_DOWNLOAD_CONTENER.replaceAll('{BOOKNAME}', bookName);
//document.querySelector('#content div').innerHTML += HTML_DOWNLOAD_CONTENER.replaceAll('{BOOKNAME}', bookName);
document.querySelector('#content div').lastChild.querySelector('fieldset').innerHTML += HTML_DOWNLOAD_LINKS.replaceAll('{BOOKID}', bookIdText);
// Append CSS
addStyle(CSS_DOWNLOAD);
// Write textboard
let textBoard = document.querySelector('#content > div:nth-child(1) > table:nth-child(4) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > span:nth-child(1) > b:nth-child(2)');
textBoard.innerHTML = HTML_DOWNLOAD_BOARD.replaceAll('{BOOKNAME}', bookName);
textBoard.style.color = 'green';
}
}
// Review page add-on
function pageReview() {
// Release title area first
if (document.querySelector('td > input[name="Submit"]')) {
const table = document.querySelector('form>table');
const titleText = table.innerHTML.match(/<!--[\s\S]+id="ptitle"[\s\S]+-->/)[0];
const titleHTML = titleText.replace(/^<!--\s*/, '').replace(/\s*-->$/, '');
table.innerHTML = table.innerHTML.replace(titleText, titleHTML);
}
const commentArea = document.querySelector('#pcontent');
const commentForm = document.querySelector('form[action^="https://www.wenku8.net/modules/article/review"]');
const commentSbmt = document.querySelector('td > input[name="Submit"]');
const commenttitl = document.querySelector('#ptitle');
// Ctrl+Enter comment submit
if (commentSbmt) {
commentSbmt.value = '发表书评(Ctrl+Enter)';
commentSbmt.style.padding = '0.3em 0.4em 0.3em 0.4em';
commentSbmt.style.height= 'auto';
commentArea.addEventListener('keydown', hotkeyReply);
commenttitl.addEventListener('keydown', hotkeyReply);
}
function hotkeyReply() {
let keycode = event.keyCode;
if (keycode === 13 && event.ctrlKey && !event.altKey) {
commentForm.submit();
}
}
// ## Save whole post ##
// GUI
const pageCountText = document.querySelector('#pagelink>.last').href.match(/page=(\d+)/)[1];
const main = document.querySelector('#content');
const headBars = main.querySelectorAll('tr>td[align]');
headBars[0].width = '80%';
headBars[1].width = '20%';
const saveBtn = document.createElement('span');
saveBtn.innerText = TEXT_GUI_DOWNLOAD_REVIEW.replaceAll('A', pageCountText);
saveBtn.classList.add(CLASSNAME_BUTTON);
saveBtn.addEventListener('click', downloadWholePost);
headBars[1].appendChild(saveBtn);
/*
// Testing
getAllPages(function(data) {
const txt = joinTXT(data);
DoLog(LogLevel.Success, txt);
});
*/
// ## Function: Get data from page document or join it into the given data variable ##
function getDataFromPage(document, data) {
let i;
// Get Floors; avatars uses for element locating
const main = document.querySelector('#content');
const avatars = main.querySelectorAll('table div img.avatar');
// init data, floors and users if need
let floors = {}, users = {};
if (data) {
floors = data.floors;
users = data.users;
} else {
data = {};
initData(data, floors, users);
}
for (i = 0; i < avatars.length; i++) {
const floor = newFloor(floors, avatars, i);
const elements = getFloorElements(floor);
const reply = getFloorReply(floor);
const user = getFloorUser(floor);
appendFloor(floors, floor);
}
return data;
function initData(data, floors, users) {
// data vars
data.floors = floors; floors.data = data;
data.users = users; users.data = data;
// review info
data.link = location.href;
data.id = location.href.match(/rid=(\d+)/) ? Number(location.href.match(/rid=(\d+)/)[1]) : 0;
data.page = location.href.match(/page=(\d+)/) ? Number(location.href.match(/page=(\d+)/)[1]) : 1;
data.title = main.querySelector('th strong').innerText;
return data;
}
function newFloor(floors, avatars, i) {
const floor = {};
floor.avatar = avatars[i];
floor.floors = floors;
return floor;
}
function getFloorElements(floor) {
const elements = {}; floor.elements = elements;
elements.avatar = floor.avatar;
elements.table = elements.avatar.parentElement.parentElement.parentElement.parentElement.parentElement;
elements.tr = elements.table.querySelector('tr');
elements.tdUser = elements.table.querySelector('td.odd');
elements.tdReply = elements.table.querySelector('td.even');
elements.divUser = elements.tdUser.querySelector('div');
elements.aUser = elements.divUser.querySelector('a');
elements.attr = elements.tdReply.querySelector('div a').parentElement;
elements.time = elements.attr.childNodes[0];
elements.number = elements.attr.childNodes[1];
elements.title = elements.tdReply.querySelector('div>strong');
elements.content = elements.tdReply.querySelector('hr+div');
return elements;
}
function getFloorReply(floor) {
const elements = floor.elements;
const reply = {}; floor.reply = reply;
reply.time = elements.time.nodeValue.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0];
reply.number = Number(elements.number.innerText.match(/\d+/)[0]);
reply.value = elements.content.innerText;
reply.title = elements.title.innerText;
return reply;
}
function getFloorUser(floor) {
const elements = floor.elements;
const user = {}; floor.user = user;
user.id = elements.aUser.href.match(/uid=(\d+)/)[1];
user.name = elements.aUser.innerText;
user.avatar = elements.avatar.src;
user.link = elements.aUser.href;
user.jointime = elements.divUser.innerText.match(/\d{4}-\d{2}-\d{2}/)[0];
const data = floor.floors.data; const users = data.users;
if (!users.hasOwnProperty(user.id)) {
users[user.id] = user;
user.floors = [floor];
} else {
const uFloors = users[user.id].floors;
uFloors.push(floor);
sortUserFloors(uFloors);
}
return user;
}
function sortUserFloors(uFloors) {
uFloors.sort(function(F1, F2) {
return F1.reply.number > F2.reply.number;
})
}
function appendFloor(floors, floor) {
floors[floor.reply.number-1] = floor;
}
}
// ## Function: Get pages and parse each pages to a data, returns data ##
// callback(data, gotcount, finished) is called when xhr and parsing completed
function getAllPages(callback) {
let i, data, gotcount = 0;
const ridMatcher = /rid=(\d+)/, pageMatcher = /page=(\d+)/;
const lastpageUrl = document.querySelector('#pagelink>.last').href;
const rid = Number(lastpageUrl.match(ridMatcher)[1]);
const pageCount = Number(lastpageUrl.match(pageMatcher)[1]);
const curPageNum = location.href.match(pageMatcher) ? Number(location.href.match(pageMatcher)[1]) : 1;
for (i = 1; i <= pageCount; i++) {
const url = lastpageUrl.replace(pageMatcher, 'page='+String(i));
DoLog(LogLevel.Info, 'getting page ' + String(i) + ', url=\'' + url + '\'');
getDocument(url, joinPageData, callback);
}
function joinPageData(pageDocument, callback) {
data = getDataFromPage(pageDocument, data);
gotcount++;
// log
DoLog(LogLevel.Info, 'got ' + String(gotcount) + ' pages.');
if (gotcount === pageCount) {
DoLog(LogLevel.Success, 'All pages xhr and parsing completed.');
DoLog(LogLevel.Success, data, true);
}
// callback
if (callback) {callback(data, gotcount, gotcount === pageCount);};
}
}
// Function output
function joinTXT(data, noSpliter=true) {
const floors = data.floors; const users = data.users;
// HEAD META DATA
const saveTime = getTime();
const head = TEXT_OUTPUT_REVIEW_HEAD
.replaceAll('{RWID}', data.id).replaceAll('{RWTT}', data.title).replaceAll('{RWLK}', data.link)
.replaceAll('{SVTM}', saveTime).replaceAll('{SCNM}', GM_info.script.name)
.replaceAll('{VRSN}', GM_info.script.version).replaceAll('{ATNM}', GM_info.script.author);
// join userinfos
let userText = '';
for (const [pname, user] of Object.entries(users)) {
if (!isNumeric(pname)) {continue;};
userText += TEXT_OUTPUT_REVIEW_USER
.replaceAll('{LNSPLT}', noSpliter ? '' : TEXT_SPLIT_LINE).replaceAll('{USERNM}', user.name)
.replaceAll('{USERID}', user.id).replaceAll('{USERJT}', user.jointime)
.replaceAll('{USERLK}', user.link).replaceAll('{USERFL}', user.floors[0].reply.number);
userText += '\n'.repeat(2);
}
// join floors
let floorText = '';
for (const [pname, floor] of Object.entries(floors)) {
if (!isNumeric(pname)) {continue;};
const avatar = floor.avatar; const elements = floor.elements; const user = floor.user; const reply = floor.reply;
floorText += TEXT_OUTPUT_REVIEW_FLOOR
.replaceAll('{LNSPLT}', noSpliter ? '' : TEXT_SPLIT_LINE).replaceAll('{RPNUMB}', String(reply.number))
.replaceAll('{RPTIME}', reply.time).replaceAll('{USERNM}', user.name)
.replaceAll('{USERID}', user.id).replaceAll('{RPTEXT}', reply.value);
floorText += '\n'.repeat(2);
}
// End
const foot = TEXT_OUTPUT_REVIEW_END;
// return
const txt = head + '\n'.repeat(2) + userText + '\n'.repeat(2) + floorText + '\n'.repeat(2) + foot;
return txt;
}
// ## Function: Download the whole post ##
function downloadWholePost() {
// Continues only if not working
if (downloadWholePost.working) {return;};
downloadWholePost.working = true;
// GUI
saveBtn.innerText = TEXT_GUI_DOWNLOADING_REVIEW
.replaceAll('C', '0').replaceAll('A', pageCountText);
// go work!
getAllPages(function(data, gotCount, finished) {
// GUI
saveBtn.innerText = TEXT_GUI_DOWNLOADING_REVIEW
.replaceAll('C', String(gotCount)).replaceAll('A', pageCountText);
// Stop here if not completed
if (!finished) {return;};
// Join text
const TXT = joinTXT(data);
// Download
const blob = new Blob([TXT],{type:"text/plain;charset=utf-8"});
const url = URL.createObjectURL(blob);
const name = '文库贴 - ' + String(data.id) + '.txt';
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
// GUI
saveBtn.innerText = TEXT_GUI_DOWNLOADFINISH_REVIEW;
// Work finish
downloadWholePost.working = false;
})
}
}
// Novel page add-on
function pageNovel() {
const title = document.querySelector('#title').textContent;
const isImagePage = title.includes('插图') || title.includes('插圖');
const rightButtonDiv = document.querySelector('#linkright');
const rightButtons = rightButtonDiv.childNodes;
let dlCompleted = 0; // number of completed download tasks
let dlAllCount = 0; // number of all download tasks
let dlAllRunning = false; // whether there is downloadAllImages running
// append control buttons
let i;
let spliter, button = rightButtonDiv.querySelector('a').cloneNode();
for (i = 0; i < rightButtons.length; i++) {
if (rightButtons[i].textContent.includes('|')) {
spliter = rightButtons[i].cloneNode();
}
}
// Attributes & Display config
let allImages, buttonText;
let clickFunc;
if (isImagePage) {
buttonText = TEXT_GUI_DOWNLOAD_IMAGE;
clickFunc = function() {downloadAllImages();};
} else {
buttonText = TEXT_GUI_DOWNLOAD_TEXT;
clickFunc = function() {downloadText();};
}
button.href = 'javascript:void(0);';
button.target = '';
button.innerText = buttonText;
button.style.color = '#00BB00';
button.addEventListener('click', clickFunc);
rightButtonDiv.insertBefore(spliter, rightButtonDiv.lastChild);
rightButtonDiv.insertBefore(button, rightButtonDiv.lastChild);
rightButtonDiv.style.width = '500px';
// Prevent URL.revokeObjectURL in script 轻小说文库下载
const Ori_revokeObjectURL = URL.revokeObjectURL;
URL.revokeObjectURL = function(arg) {
if (typeof(arg) === 'string' && arg.substr(0, 5) === 'blob:') {return false;};
return Ori_revokeObjectURL(arg);
}
function downloadText() {
const contentEle = document.querySelector('#content');
let content = contentEle.innerText//.replaceAll('\n', '\r\n');
if (content.length === 0) {
return false;
}
// Clear spaces
content = content.split('\n');
for (let i = 0; i < content.length; i++) {
content[i] = content[i].trim();
}
content = content.join('\r\n');
// Download
const blob = new Blob([content],{type:"text/plain;charset=utf-8"});
const url = URL.createObjectURL(blob);
const name = title + '.txt';
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = name;
a.click();
}
function downloadAllImages() {
if (dlAllRunning) {
return false;
}
allImages = document.querySelectorAll('#content > div.divimage img');
dlAllCount = allImages.length;
dlCompleted = 0;
dlAllRunning = true;
// Display
button.innerText = TEXT_GUI_DOWNLOADING_ALL.replace('C', '0').replace('A', String(dlAllCount));
rightButtonDiv.style.width = '550px';
// Download
const numLen = String(dlAllCount).length;
for (let i = 0; i < dlAllCount; i++) {
const imageName = title + '_' + fillNumber(i+1, numLen) + '.jpg';
const url = allImages[i].src;
if (allImages[i].src.substr(0,5) === 'blob:') {
const image = new Image();
image.onload = function() {
saveBlobToFile(toImageFormatURL(image, 1), imageName);
dlIncrease(button);
}
image.src = url;
} else {
download(url, imageName, button);
}
}
}
// File download function
function download(url, name, displayElement) {
// Check
if (!url || !name) {
return false;
}
// xmlHTTPRequest
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: function(request) {
// DataURL
let objURL = URL.createObjectURL(request.response);
// toImageFormatURL
const image = new Image();
image.src = objURL;
image.onload = function() {
//image.style.display = 'none';
//document.body.appendChild(image);
const formatURL = toImageFormatURL(image, 1);
//document.body.removeChild(image);
saveBlobToFile(formatURL, name);
dlIncrease(displayElement);
};
}
})
return true;
}
// Increase dlCompleted and judge dlAllRunning
function dlIncrease(displayElement) {
// Task count decrease
dlCompleted++;
if (dlCompleted === dlAllCount) {
dlAllRunning = false;
}
// Display
if (displayElement) {
displayElement.innerText = TEXT_GUI_DOWNLOADING_ALL
.replace('C', String(dlCompleted)).replace('A', String(dlAllCount));
if (!dlAllRunning) {
displayElement.innerText = TEXT_GUI_DOWNLOADED_ALL;
rightButtonDiv.style.width = '550px';
}
}
}
// Blob url file saving function
function saveBlobToFile(blobURL, name) {
// Create <a>
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobURL;
a.download = name;
a.click();
}
// Image format changing function
function toImageFormatURL(image, format) {
if (typeof(format) === 'number') {format = ['image/jpeg', 'image/png', 'image/webp'][format-1]}
const cvs = document.createElement('canvas');
cvs.width = image.width;
cvs.height = image.height;
const ctx = cvs.getContext('2d');
ctx.drawImage(image, 0, 0);
return cvs.toDataURL(format);
}
}
// Index page add-on
function pageIndex() {
}
// Download page add-on
function pageDownload() {
let i;
let dlCount = 0; // number of active download tasks
let dlAllRunning = false; // whether there is downloadAll running
/* ******************* GUI ******************* */
// Create left operation GUI
let downloadGUI = document.querySelectorAll('#left div.block')[1].cloneNode(true);
// Rename title
downloadGUI.querySelector('.blocktitle .txt').innerHTML = '下载全部章节';
// Remove content
downloadGUI.removeChild(downloadGUI.querySelector('.blockcontent'));
// Create operation ul list
let optionButtonsForm = document.querySelector('#left div.block div.blockcontent div ul[style]').cloneNode(true);
// Reset lis
const NAMES = ['本地简体(G)', '本地简体(U)', '本地繁体(U)', '地址二简体(G)', '地址二简体(U)', '地址二繁体(U)'];
let lis = optionButtonsForm.querySelectorAll('li');
let li = lis[0].cloneNode(true);
let newli;
li.querySelector('a').href = 'javascript:void(0);';
li.querySelector('a').className = '';
li.querySelector('a').classList.add(CLASSNAME_BUTTON);
li.querySelector('a').innerHTML = '默认按钮文本';
for (i = 0; i < 6; i++) {
// If li exist, remove it
if (lis[i]) {
optionButtonsForm.removeChild(lis[i]);
};
// Create a new one
newli = li.cloneNode(true);
// Modify name
newli.querySelector('a').innerHTML = NAMES[i];
// Mark i
newli.i = i;
// Append it
optionButtonsForm.appendChild(newli);
// Add event listener
newli.addEventListener('click',
function() { // i refers to its current value in loop by marking on the li element
downloadAll(this.i);
})
}
// Create a container
let blockcontent = document.createElement('div');
blockcontent.classList.add('blockcontent');
blockcontent.style.paddingLeft = '10px';
// Append ul
blockcontent.appendChild(optionButtonsForm);
// Append container
downloadGUI.appendChild(blockcontent);
// Append GUI
document.querySelector('#left').appendChild(downloadGUI);
// Servers GUI
let servers = document.querySelectorAll('#content>b');
let serverEles = [];
for (i = 0; i < servers.length; i++) {
if (servers[i].innerText.includes('wenku8.com')) {
serverEles.push(servers[i]);
}
}
for (i = 0; i < serverEles.length; i++) {
serverEles[i].classList.add(CLASSNAME_BUTTON);
serverEles[i].addEventListener('click', function() {changeAllServers(this.innerText);});
if (tipshow && tiphide) {
// tipshow and tiphide is coded inside wenku8 itself, its function is to show a text tip besides the mouse
serverEles[i].addEventListener('mouseover', function() {tipshow(TEXT_TIP_SERVERCHANGE);});
serverEles[i].addEventListener('mouseout' , tiphide);
} else {
serverEles[i].title = TEXT_TIP_SERVERCHANGE;
}
}
/* ******************* Code ******************* */
// Change all server elements
function changeAllServers(server) {
let i;
const allA = document.querySelectorAll('.even a');
for (i = 0; i < allA.length; i++) {
changeServer(server, allA[i]);
}
}
// Change server for an element
function changeServer(server, element) {
if (!element.href) {return false;};
element.href = element.href.replace(/\/\/dl\d?\.wenku8\.com\//g, '//' + server + '/');
}
// Get novel name
const novelName = document.querySelector('html body div.main div#centerm div#content table.grid caption a').innerText;
let downloadAll = function(type) {
// Check: only download while no download active tasks currently
if (dlAllRunning) {
return false;
}
dlAllRunning = true;
// GUI display
downloadGUI.querySelector('.blocktitle .txt').innerHTML = TEXT_GUI_DOWNLOADING;
// Name customize
let NAME = novelName + ' {j}.';
let allNames = getAllNames();
if (window.location.href.indexOf('txt') != -1) {
NAME += 'txt';
} else {
NAME += document.querySelector('html body div.main div#centerm div#content table.grid tbody tr td.even a').innerText.replace(/[^\w]+/, '').toLowerCase();
}
let i,j = 0;
const allA = document.querySelectorAll('.even a');
for (i = type; i < allA.length; i = i + 6) {
/*GM_download({
url: allA[i].href,
name: NAME.replace('{j}', (window.location.href.indexOf('txtfull') === -1 ? allNames[j] : ''))
});*/
download(
allA[i].href,
NAME.replace('{j}', (window.location.href.indexOf('txtfull') === -1 ? allNames[j] : '')),
allA[i].parentElement.parentElement.querySelector('td.odd')
)
j += 1;
}
downloadGUI.querySelector('.blocktitle .txt').innerHTML = '下载全部章节';
}
function getAllNames() {
let all = document.querySelectorAll('.grid tbody tr .odd');
let names = [];
for (let i = 0; i < all.length; i++) {
names[i] = all[i].innerText.replace(REG_GUI_DOWNLOADED, '').replace(REG_GUI_DOWNLOADING,'');
}
return names;
}
// File download function
function download(url, name, displayElement) {
// Check
if (!url || !name) {
return false;
}
// dl task count increase
dlCount++;
// Display
let text = '';
if (displayElement) {
if (displayElement.innerText) {text = displayElement.innerText.replace(REG_GUI_DOWNLOADED, '').replace(REG_GUI_DOWNLOADING,'');};
displayElement.innerText = text + TEXT_GUI_DOWNLOADING;
}
// xmlHTTPRequest
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: function(request) {
// DataURL
let objURL = URL.createObjectURL(request.response);
// Create <a>
const a = document.createElement('a');
a.style.display = 'none';
a.href = objURL;
a.download = name;
a.click();
// Task count decrease
dlCount--;
if (dlCount === 0) {
dlAllRunning = false;
}
// Display
if (displayElement) {
displayElement.innerText = TEXT_GUI_DOWNLOADED.replace(/^ /, '');
if (text) {displayElement.innerText = text + TEXT_GUI_DOWNLOADED;};
}
}
})
return true;
}
}
// Download and parse a url page into a html document(dom).
// when xhr onload: callback.apply([dom, args])
function getDocument(url, callback, args) {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: function(response) {
const htmlblob = response.response;
const reader = new FileReader();
reader.onload = function(e) {
const htmlText = reader.result;
const dom = new DOMParser().parseFromString(htmlText, 'text/html');
args = [dom].concat(args);
callback.apply(null, args);
//callback(dom, htmlText);
}
reader.readAsText(htmlblob, 'GBK');
/* 注意!原来这里只是使用了DOMParser,DOMParser不像iframe加载Document一样拥有完整的上下文并执行所有element的功能,
** 只是按照HTML格式进行解析,所以在文库页面的GBK编码下仍然会按照UTF-8编码进行解析,导致中文乱码。
** 所以处理dom时不要使用ASC-II字符集以外的字符!
**
** 注:现在使用了FileReader来以GBK编码解析htmlText,故编码问题已经解决,可以正常使用任何字符
*/
}
})
}
// Get a time text like 1970-01-01 00:00:00
function getTime(dateSpliter='-', timeSpliter=':') {
const d = new Date();
const fulltime = fillNumber(d.getFullYear(), 4) + dateSpliter + fillNumber((d.getMonth() + 1), 2) + dateSpliter + fillNumber(d.getDate(), 2)
+ ' ' + fillNumber(d.getHours(), 2) + timeSpliter + fillNumber(d.getMinutes(), 2) + timeSpliter + fillNumber(d.getSeconds(), 2);
return fulltime;
}
// Fill number text to certain length with '0'
function fillNumber(number, length) {
let str = String(number);
for (let i = str.length; i < length; i++) {
str = '0' + str;
}
return str;
}
// Judge whether the str is a number
function isNumeric(str) {
const result = Number(str);
return !isNaN(result) && str !== '';
}
// Append a style text to document(<head>) with a <style> element
function addStyle(css) {
document.head.appendChild(document.createElement("style")).textContent = css;
}
// Copy text to clipboard (needs to be called in an user event)
function copyText(text) {
// Create a new textarea for copying
const newInput = document.createElement('textarea');
document.body.appendChild(newInput);
newInput.value = text;
newInput.select();
document.execCommand('copy');
document.body.removeChild(newInput);
}
})();