From 1107184ca0161252a6e61ed055531fc020a66edf Mon Sep 17 00:00:00 2001 From: zjamnik Date: Tue, 5 Jul 2022 00:43:09 +0200 Subject: [PATCH] Initial commit --- .gitignore | 134 +++++++++++++++ README.md | 43 +++++ WNtoEmail.js | 406 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 214 ++++++++++++++++++++++++ package.json | 5 + 5 files changed, 802 insertions(+) create mode 100644 .gitignore create mode 100644 WNtoEmail.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12a6984 --- /dev/null +++ b/.gitignore @@ -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.* \ No newline at end of file diff --git a/README.md b/README.md index 21bb9fc..5c6fb73 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ # WNtoEmail 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 +} +``` \ No newline at end of file diff --git a/WNtoEmail.js b/WNtoEmail.js new file mode 100644 index 0000000..974b1e3 --- /dev/null +++ b/WNtoEmail.js @@ -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 += '

' + html.querySelector('span.chapter-text').innerText + '

'; + + 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(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ac5c977 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2f2204c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "node-html-parser": "^5.3.3" + } +}