Initial commit
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
|||||||
|
# Project specific
|
||||||
|
Download/**/**
|
||||||
|
*.conf
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
@@ -1,2 +1,45 @@
|
|||||||
# WNtoEmail
|
# WNtoEmail
|
||||||
Node.js script to download new WebNovel chapters convert them to eBook and send to email. Mainly intended for sending to Kindle.
|
Node.js script to download new WebNovel chapters convert them to eBook and send to email. Mainly intended for sending to Kindle.
|
||||||
|
Script will pack your WebNovels into convinient eBooks, complete with cover image, metadata and table of contents. For ongoing series if will wait for a configured number of new chapters before sending a new volume to avoid spam.
|
||||||
|
|
||||||
|
# Dependecies
|
||||||
|
It's a Node.js project so install that.
|
||||||
|
Project is using `node-html-parser` and `nodemailer` libraries. It should be enough to:
|
||||||
|
```
|
||||||
|
npm install node-html-parser --save
|
||||||
|
npm install nodemailer --save
|
||||||
|
```
|
||||||
|
|
||||||
|
# Config
|
||||||
|
At first start it will create an empty config file `./novelConfig.conf`, adjust the setting according to comments below
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"downloadLocation": "", // New chapter download location
|
||||||
|
"converterPath": "ebook-convert.exe", // Calibre eBook converter, I recommend adding it to you PATH, NOT tested when it's not in PATH
|
||||||
|
"ebookFormat": "epub", // Desired eBook format, Kindle started supporting epub dso that's default
|
||||||
|
"sendEmail": false, // If the script should send eBooks via email
|
||||||
|
"emailToAddress": "", // Email where to send your eBooks
|
||||||
|
"emailFromAddress": "", // Important for Kindle deliveries, make sure you have it added in Kindle settings
|
||||||
|
"emailProvider": "", // Gmail works fine, just need to set up 2FA and an app password
|
||||||
|
"emailUsername": "", // Usernam to your email account
|
||||||
|
"emailPassword": "", // Password to your email account
|
||||||
|
"emailAttachments": 25, // How many eBooks to attach to a single email
|
||||||
|
"supportedHosting": { // Enum to show which WebNovel host sites are supported, NOT configurable
|
||||||
|
"NF": "https://novelfull.com/"
|
||||||
|
},
|
||||||
|
"template": { // Template for a WebNovel entry in "novels" below
|
||||||
|
"novelURL": "", // WebNovel address, it's enough to copy this template to "novels" and fill only this field to start
|
||||||
|
"title": "", // Autofill
|
||||||
|
"author": "", // Autofill
|
||||||
|
"coverURL": "", // Autofill
|
||||||
|
"lastChapterURL": false, // Autofill; Can be used if not starting from the first chapter, first chapter downloaded will be NEXT from this
|
||||||
|
"lastVolume": 0, // Autofill; Can be used if not starting from the first chapter, first eBook number created will be NEXT from this
|
||||||
|
"completed": false, // Autofill; Set to false with settings above to download chapters again
|
||||||
|
"hosting": "NF", // Hosting code, see "supportedHosting"
|
||||||
|
"volumeChapterCount": 5, // After how many new/unread chapters to send a new eBook, ignored if WebNovel is completed
|
||||||
|
"completedVolumeChapterCount": 50, // How many chapters to pack per eBook
|
||||||
|
"redownload": false // TODO: redownload all chapters, repack into volumes with completedVolumeChapterCount, do not send via email, intended for completed series archiving
|
||||||
|
},
|
||||||
|
"novels": [] // Table of
|
||||||
|
}
|
||||||
|
```
|
||||||
+406
@@ -0,0 +1,406 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const HTMLparser = require('node-html-parser');
|
||||||
|
const exec = require("child_process").execSync;
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
var transporter;
|
||||||
|
var config;
|
||||||
|
|
||||||
|
function clone(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanPath(pathToClean) {
|
||||||
|
let regex = /[<>:"|\?\*]/g;
|
||||||
|
let cleanPath = path.normalize(pathToClean);
|
||||||
|
|
||||||
|
let isAbsolute = path.isAbsolute(cleanPath);
|
||||||
|
cleanPath = cleanPath.replace(regex, '');
|
||||||
|
if (/^win/i.test(process.platform) && isAbsolute) {
|
||||||
|
cleanPath = `${cleanPath.slice(0, 1)}:${cleanPath.slice(1)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(dir, file, data) {
|
||||||
|
let cleanDir = cleanPath(dir);
|
||||||
|
let cleanFile = cleanPath(file);
|
||||||
|
|
||||||
|
if (!fs.existsSync(cleanDir)) {
|
||||||
|
fs.mkdirSync(cleanDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(`${cleanDir}/${cleanFile}`, data, function (err) {
|
||||||
|
if (err != null) console.log(err);
|
||||||
|
return err != null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFile(file) {
|
||||||
|
let fileContent;
|
||||||
|
try {
|
||||||
|
fileContent = fs.readFileSync(cleanPath(file), 'utf8');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fileContent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
let novelConfigDefault = {
|
||||||
|
"downloadLocation": "",
|
||||||
|
"converterPath": "ebook-convert.exe",
|
||||||
|
"ebookFormat": "epub",
|
||||||
|
"sendEmail": false,
|
||||||
|
"emailToAddress": "",
|
||||||
|
"emailFromAddress": "",
|
||||||
|
"emailProvider": "",
|
||||||
|
"emailUsername": "",
|
||||||
|
"emailPassword": "",
|
||||||
|
"emailAttachments": 25,
|
||||||
|
"supportedHosting": {
|
||||||
|
"NF": "https://novelfull.com/"
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"novelURL": "",
|
||||||
|
"title": "",
|
||||||
|
"author": "",
|
||||||
|
"coverURL": "",
|
||||||
|
"lastChapterURL": false,
|
||||||
|
"lastVolume": 0,
|
||||||
|
"completed": false,
|
||||||
|
"hosting": "NF",
|
||||||
|
"volumeChapterCount": 5,
|
||||||
|
"completedVolumeChapterCount": 50,
|
||||||
|
"redownload": false // TODO: redownload all chapters, repack into volumes with completedVolumeChapterCount, do not send via email, intended for completed series archiving
|
||||||
|
},
|
||||||
|
"novels": []
|
||||||
|
};
|
||||||
|
|
||||||
|
let configRead = readFile('./novelConfig.conf');
|
||||||
|
|
||||||
|
if (configRead) {
|
||||||
|
config = JSON.parse(configRead);
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
service: config['emailProvider'], // no need to set host or port etc.
|
||||||
|
auth: {
|
||||||
|
user: config['emailUsername'],
|
||||||
|
pass: config['emailPassword']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
writeFile('.', 'novelConfig.conf', JSON.stringify(config, null, 4));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeFile('.', 'novelConfig.conf', JSON.stringify(novelConfigDefault, null, 4));
|
||||||
|
config = novelConfigDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
writeFile('.', 'novelConfig.conf', JSON.stringify(config, null, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertEbook(dir, file, params = { "cover": false, "authors": false, "title": false }, format = 'html') {
|
||||||
|
let file1Path = cleanPath(`${dir}/${file}.${format}`);
|
||||||
|
let file2Path = cleanPath(`${dir}/${file}.${config['ebookFormat']}`);
|
||||||
|
let convertParams = ' --use-auto-toc';
|
||||||
|
//convertParams += format2 == 'epub' ? ' --epub-inline-toc' : '';
|
||||||
|
convertParams += params['cover'] ? ` --cover "${params['cover']}"` : '';
|
||||||
|
convertParams += params['authors'] ? ` --authors "${params['authors']}"` : '';
|
||||||
|
convertParams += params['title'] ? ` --title "${params['title']}"` : '';
|
||||||
|
|
||||||
|
exec(`${config['converterPath']} "${file1Path}" "${file2Path}"${convertParams}`, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(`error: ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.log(`stderr: ${stderr}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`stdout: ${stdout}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function sendEbook(subject, ebookAttachments) { //(dir, ebook, format = 'epub') {
|
||||||
|
if (config['sendEmail']) {
|
||||||
|
let splicedAttachments = [];
|
||||||
|
while (ebookAttachments.length > config['emailAttachments']) {
|
||||||
|
splicedAttachments.push(ebookAttachments.splice(0, config['emailAttachments']))
|
||||||
|
}
|
||||||
|
if (ebookAttachments.length > 0) {
|
||||||
|
splicedAttachments.push(ebookAttachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < splicedAttachments.length; ++i) {
|
||||||
|
let message = {
|
||||||
|
from: config['emailFromAddress'],
|
||||||
|
to: config['emailToAddress'],
|
||||||
|
subject: subject + ' part ' + i,
|
||||||
|
text: subject + ' part ' + i,
|
||||||
|
attachments: splicedAttachments[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
await transporter.sendMail(message, (err) => {
|
||||||
|
if (err)
|
||||||
|
console.log(err);
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log(`Sent volume ${ebook}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetch_smth(URL, hosting) {
|
||||||
|
let fetchURL = await fetch(URL);
|
||||||
|
|
||||||
|
if (fetchURL.ok) {
|
||||||
|
let response = await fetchURL.text();
|
||||||
|
|
||||||
|
let info;
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
else return fetchURL.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchNovelInfo(URL, hosting) {
|
||||||
|
let fetchURL = await fetch(URL);
|
||||||
|
|
||||||
|
if (fetchURL.ok) {
|
||||||
|
let response = await fetchURL.text();
|
||||||
|
|
||||||
|
let novelInfo = getNovelInfo(response, hosting);
|
||||||
|
|
||||||
|
return novelInfo;
|
||||||
|
}
|
||||||
|
else return fetchURL.ok;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchChapter(URL, hosting) {
|
||||||
|
let fetchURL = await fetch(URL);
|
||||||
|
|
||||||
|
if (fetchURL.ok) {
|
||||||
|
let response = await fetchURL.text();
|
||||||
|
|
||||||
|
let nextChapterURL = getNextChapterURL(response, hosting);
|
||||||
|
let chapterContent = getChapterContent(response, hosting);
|
||||||
|
|
||||||
|
return [chapterContent, nextChapterURL];
|
||||||
|
}
|
||||||
|
else return fetchURL.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(response, hosting) {
|
||||||
|
let html = HTMLparser.parse(response);
|
||||||
|
|
||||||
|
let info;
|
||||||
|
|
||||||
|
switch (hosting) {
|
||||||
|
case 'NF':
|
||||||
|
info = html;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
info = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNovelInfo(response, hosting) {
|
||||||
|
let html = HTMLparser.parse(response);
|
||||||
|
|
||||||
|
let info;
|
||||||
|
|
||||||
|
switch (hosting) {
|
||||||
|
case 'NF':
|
||||||
|
let title = html.querySelector('h3.title').innerText;
|
||||||
|
let author = html.querySelector('#truyen > div.csstransforms3d > div > div.col-xs-12.col-info-desc > div.col-xs-12.col-sm-4.col-md-4.info-holder > div.info > div:nth-child(1) > a:nth-child(2)').innerText;
|
||||||
|
let completed = html.querySelector('#truyen > div.csstransforms3d > div > div.col-xs-12.col-info-desc > div.col-xs-12.col-sm-4.col-md-4.info-holder > div.info > div:nth-child(5) > a').innerText == "Completed";
|
||||||
|
let firstChapterURL = html.querySelector('#list-chapter > div.row > div:nth-child(1) > ul > li:nth-child(1) > a').attrs['href'];
|
||||||
|
let coverURL = html.querySelector('div.book > img').attrs['src'];
|
||||||
|
info = [title, author, completed, 'https://novelfull.com' + firstChapterURL, 'https://novelfull.com' + coverURL];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
info = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextChapterURL(response, hosting) {
|
||||||
|
let html = HTMLparser.parse(response);
|
||||||
|
|
||||||
|
let nextChapterURL;
|
||||||
|
|
||||||
|
switch (hosting) {
|
||||||
|
case 'NF':
|
||||||
|
nextChapterURL = undefined == html.querySelector('a#next_chap').attrs['href'] ? false : 'https://novelfull.com' + html.querySelector('a#next_chap').attrs['href'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
nextChapterURL = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextChapterURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChapterContent(response, hosting) {
|
||||||
|
let html = HTMLparser.parse(response);
|
||||||
|
|
||||||
|
let chapterContent = '';
|
||||||
|
|
||||||
|
switch (hosting) {
|
||||||
|
case 'NF':
|
||||||
|
chapterContent += '<h1 class="chapter">' + html.querySelector('span.chapter-text').innerText + '</h3>';
|
||||||
|
|
||||||
|
html.querySelectorAll('div#chapter-content p').forEach(element => {
|
||||||
|
chapterContent += element.outerHTML;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
chapterContent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapterContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
loadConfig();
|
||||||
|
|
||||||
|
// let novelDirConvert = 'C:/Users/mateu/bin/LightNovel/' + 'Warlock of the Magus World';
|
||||||
|
// let fileName = ((1 + 1) < 10 ? '0' + (1 + 1) : (1 + 1)) + '. ' + 'The Wizard World' + '; ' + 'Get Lost';
|
||||||
|
// let fileName = `${(1 + 1) < 10 ? '0' + (1 + 1) : (1 + 1)}. ${'The Wizard World'}; ${'Get Lost'}`;
|
||||||
|
// let fileName = `21. Warlock of the Magus World; The Plagiarist`;
|
||||||
|
// convertEbook(novelDirConvert, fileName);
|
||||||
|
// await sendEbook(novelDirConvert, fileName);
|
||||||
|
|
||||||
|
for (i = 0; i < config['novels'].length; ++i) {
|
||||||
|
let novel = clone(config['novels'][i]);
|
||||||
|
let chapters = [];
|
||||||
|
let nextChapterURL;
|
||||||
|
|
||||||
|
if (!novel['completed']) {
|
||||||
|
let novelInfo = await fetchNovelInfo(novel['novelURL'], 'NF');
|
||||||
|
|
||||||
|
novel['title'] = novelInfo[0];
|
||||||
|
novel['author'] = novelInfo[1];
|
||||||
|
novel['completed'] = novelInfo[2];
|
||||||
|
novel['coverURL'] = novelInfo[4];
|
||||||
|
|
||||||
|
config['novels'][i] = clone(novel);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
if (!novel['lastChapterURL']) {
|
||||||
|
novel['lastChapterURL'] = novelInfo[3];
|
||||||
|
|
||||||
|
let chapter = await fetchChapter(novelInfo[3], 'NF');
|
||||||
|
console.log('Download chapter ' + chapters.length + ' ' + novelInfo[3]);
|
||||||
|
chapters.push(chapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let novelDir = `${config['downloadLocation']}/${novel['title']}`;
|
||||||
|
|
||||||
|
const nextChapterURLtemp = await fetchChapter(novel['lastChapterURL'], 'NF');
|
||||||
|
nextChapterURL = nextChapterURLtemp[1];
|
||||||
|
|
||||||
|
while (nextChapterURL) {
|
||||||
|
novel['lastChapterURL'] = nextChapterURL;
|
||||||
|
let chapter = await fetchChapter(nextChapterURL, 'NF');
|
||||||
|
console.log('Download chapter ' + chapters.length + ' ' + nextChapterURL);
|
||||||
|
chapters.push(chapter);
|
||||||
|
nextChapterURL = chapter[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFile(novelDir, 'chapters.json`, JSON.stringify(chapters))
|
||||||
|
//chapters = JSON.parse(readFile("C:\\Users\\mateu\\bin\\LightNovel\\chapters.json"))
|
||||||
|
|
||||||
|
let startVol = novel['lastVolume'];
|
||||||
|
let totalChapters = chapters.length;
|
||||||
|
|
||||||
|
const maxVolume = novel['completed'] ? startVol + 1 + Math.floor(totalChapters / novel['completedVolumeChapterCount']) : startVol + Math.floor(totalChapters / novel['completedVolumeChapterCount']);
|
||||||
|
|
||||||
|
let ebookAttachments = [];
|
||||||
|
for (vol = startVol; vol < maxVolume; vol++) {
|
||||||
|
let volContent = '';
|
||||||
|
|
||||||
|
for (chap = 0; chap < novel['completedVolumeChapterCount'] && chap < chapters.length; chap++) {
|
||||||
|
volContent += chapters[chap][0] + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
novel['lastChapterURL'] = chapters[(chap - 2 < 0 ? 0 : chap - 2)][1];
|
||||||
|
novel['lastVolume'] = vol + 1;
|
||||||
|
config['novels'][i] = clone(novel);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
let novelFileName = `${(vol + 1) < 10 ? '0' + (vol + 1) : (vol + 1)}. ${novel['title']}; ${novel['author']}`;
|
||||||
|
|
||||||
|
writeFile(novelDir, `${novelFileName}.html`, volContent);
|
||||||
|
console.log(`Saved volume: ${novelFileName}`);
|
||||||
|
|
||||||
|
await convertEbook(novelDir, novelFileName, {
|
||||||
|
cover: novel['coverURL'],
|
||||||
|
authors: novel['author'],
|
||||||
|
title: `${(vol + 1) < 10 ? '0' + (vol + 1) : (vol + 1)}. ${novel['title']}`
|
||||||
|
});
|
||||||
|
|
||||||
|
ebookAttachments.push({
|
||||||
|
name: cleanPath(`${novelFileName}.${config['ebookFormat']}`),
|
||||||
|
path: cleanPath(`${novelDir}/${novelFileName}.${config['ebookFormat']}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
chapters.splice(0, chap);
|
||||||
|
}
|
||||||
|
await sendEbook(novel['title'], ebookAttachments);
|
||||||
|
|
||||||
|
ebookAttachments = [];
|
||||||
|
startVol = novel['lastVolume'];
|
||||||
|
totalChapters = chapters.length;
|
||||||
|
|
||||||
|
for (vol = startVol; vol < startVol + Math.floor(totalChapters / novel['volumeChapterCount']); vol++) {
|
||||||
|
let volContent = '';
|
||||||
|
|
||||||
|
for (chap = 0; chap < novel['volumeChapterCount'] && chap < chapters.length; chap++) {
|
||||||
|
volContent += chapters[chap][0] + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
novel['lastChapterURL'] = chapters[(chap - 2 < 0 ? 0 : chap - 2)][1];
|
||||||
|
novel['lastVolume'] = vol + 1;
|
||||||
|
config['novels'][i] = clone(novel);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
let novelFileName = `${(vol + 1) < 10 ? '0' + (vol + 1) : (vol + 1)}. ${novel['title']}; ${novel['author']}`;
|
||||||
|
|
||||||
|
writeFile(novelDir, `${novelFileName}.html`, volContent);
|
||||||
|
console.log(`Saved volume: ${novelFileName}`);
|
||||||
|
|
||||||
|
await convertEbook(novelDir, novelFileName, {
|
||||||
|
cover: novel['coverURL'],
|
||||||
|
authors: novel['author'],
|
||||||
|
title: `${(vol + 1) < 10 ? '0' + (vol + 1) : (vol + 1)}. ${novel['title']}`
|
||||||
|
});
|
||||||
|
|
||||||
|
ebookAttachments.push({
|
||||||
|
name: cleanPath(`${novelFileName}.${config['ebookFormat']}`),
|
||||||
|
path: cleanPath(`${novelDir}/${novelFileName}.${config['ebookFormat']}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
chapters.splice(0, chap);
|
||||||
|
}
|
||||||
|
await sendEbook(novel['title'], ebookAttachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Generated
+214
@@ -0,0 +1,214 @@
|
|||||||
|
{
|
||||||
|
"name": "LightNovel",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"node-html-parser": "^5.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||||
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.0.1",
|
||||||
|
"domhandler": "^4.3.1",
|
||||||
|
"domutils": "^2.8.0",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.0.1",
|
||||||
|
"domhandler": "^4.2.0",
|
||||||
|
"entities": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^1.0.1",
|
||||||
|
"domelementtype": "^2.2.0",
|
||||||
|
"domhandler": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
|
"bin": {
|
||||||
|
"he": "bin/he"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-html-parser": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^4.2.1",
|
||||||
|
"he": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||||
|
},
|
||||||
|
"css-select": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.0.1",
|
||||||
|
"domhandler": "^4.3.1",
|
||||||
|
"domutils": "^2.8.0",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"css-what": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
|
||||||
|
},
|
||||||
|
"dom-serializer": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^2.0.1",
|
||||||
|
"domhandler": "^4.2.0",
|
||||||
|
"entities": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
|
||||||
|
},
|
||||||
|
"domhandler": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domutils": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
|
||||||
|
"requires": {
|
||||||
|
"dom-serializer": "^1.0.1",
|
||||||
|
"domelementtype": "^2.2.0",
|
||||||
|
"domhandler": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entities": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
|
||||||
|
},
|
||||||
|
"he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
|
},
|
||||||
|
"node-html-parser": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==",
|
||||||
|
"requires": {
|
||||||
|
"css-select": "^4.2.1",
|
||||||
|
"he": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"node-html-parser": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user