// ==UserScript==
// @name 轻小说文库+
// @namespace Wenku8+
// @version 0.8.4
// @description 章节批量下载,版权限制小说TXT简繁全本下载,书名/作者名双击复制,Ctrl+Enter快捷键发表书评,单章节下载,小说JPEG插图下载,下载线路点击切换
// @author PY-DNG
// @match http*://www.wenku8.net/*
// @connect dl.wenku8.com
// @connect dl2.wenku8.com
// @connect dl3.wenku8.com
// @connect picture.wenku8.com
// @grant GM_download
// @grant GM_xmlhttpRequest
// ==/UserScript==
(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_DOWNLOADPAGE = '.server {color: rgb(0, 160, 0);} .server:hover {color: rgb(0, 100, 0);} .server:focus {color: rgb(90, 180, 90);}';
const TEXT_TIP_COPY = '双击复制';
const TEXT_TIP_SERVERCHANGE = '点击切换线路';
const TEXT_GUI_DOWNLOAD_IMAGE = '下载图片';
const TEXT_GUI_DOWNLOAD_TEXT = '下载本章';
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 = '下载图片(已完成)';
// 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:
console.log(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
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() {
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"]');
// 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();
}
})
}
// 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) {
// get all images
allImages = document.querySelectorAll('#content > div.divimage img');
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';
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;
}
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
for (let i = 0; i < dlAllCount; i++) {
const imageName = title + '_' + String(i+1) + '.jpg';
const url = allImages[i].src.substr(0,5) === 'blob:' ? toImageFormatURL(allImages[i], 1) : allImages[i].src;
download(url, imageName, button);
}
}
// File download function
function download(url, name, displayElement) {
// Check
if (!url || !name) {
return false;
}
// if .src is already changed loaded with blob by another script, use it directly
if (url.substr(0,5) === 'blob:') {
toImageFormatURL(image, 1);
saveBlobToFile(url, name);
return true;
}
// xmlHTTPRequest
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: function(request) {
// DataURL
let objURL = URL.createObjectURL(request.response);
// toImageFormatURL
const image = document.createElement('img');
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);
// 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';
}
}
};
}
})
return true;
}
// 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('server');
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].style.color = 'green';
serverEles[i].classList.add('server');
serverEles[i].title = TEXT_TIP_SERVERCHANGE;
serverEles[i].addEventListener('click', function() {changeAllServers(this.innerText);});
}
addStyle(CSS_DOWNLOADPAGE);
/* ******************* 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;
}
}
function addStyle(css) {
document.head.appendChild(document.createElement("style")).textContent = css;
}
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);
}
})();