commit fe9d1ee61979925454015bc8263fba662d8c6c71 Author: Sebastian Hugentobler Date: Wed Dec 23 22:21:47 2020 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3c2b39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +.DS_Store +*.swp +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..68d8a82 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# NZZ Downloader +The [NZZ](https://en.wikipedia.org/wiki/Neue_Z%C3%BCrcher_Zeitung) is the Swiss +Swiss newspaper of record. Its first issue was all the way back in 1780. It's +even better that you can download every single issue ever released (if +you have a subscription of course). + +This little tool helps you with downloading all released issues in a specified +time span. + +It was written because the archive website is not very friendly in the author's +opinion and of course because it is not possible to download everything in a time +span. + +Because the archive website makes heavy use of javascript this is done with +[selenium](https://www.selenium.dev/) to remote control a browser (firefox in +this case). This is also why it is not all that fast but that is ok. + +Please only use this with your own credentials, the journalists deserve to be +paid for their work. + +## Installation +You need to be comfortable with the command line to use the nzz downloader and +it has only been tested on linux systems though it should work fine on Windows +or macOS. + +- [NodeJS](https://nodejs.org/en/download/) (the LTS version is fine) +- [Firefox](https://www.mozilla.org/en-US/firefox/download/thanks/) +- [geckodriver](https://github.com/mozilla/geckodriver/releases) +- [nzz.js]() + +## Usage +``` +Usage: nzz.js -f [date] -t [date] -o [path] -u [usernane] -p [password] + +Options: + --version Show version number [boolean] + -h, --help Show help [boolean] + -f, --from Earliest issue to download. [default: "2020-12-23"] + -t, --to Latest issue to download. [default: "2020-12-23"] + -o, --out Download directory. [default: "./nzz"] + -u, --user Username for the nzz archive. [required] + -p, --password Password for the user. [required] +``` + +### Examples +Download all existing issues from 01-01-1780 until 30-02-1780 to the default +directory "./nzz" +``` +./nzz.js -u 'myuser@example.com' -p 'mypassword' -f 1780-01-01 -t 1780-02-30 +``` diff --git a/nzz.js b/nzz.js new file mode 100755 index 0000000..98efc28 --- /dev/null +++ b/nzz.js @@ -0,0 +1,256 @@ +#!/usr/bin/env node +/* Copyright (c) Sebastian Hugentobler 2020. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +const tmp = require('tmp'); +var path = require('path'); +const fs = require('fs'); +const { Builder, By, Key, until } = require('selenium-webdriver'); +const fx = require('selenium-webdriver/firefox'); +const yargs = require('yargs/yargs') +const { hideBin } = require('yargs/helpers'); + +const URL = 'https://zeitungsarchiv.nzz.ch/'; +const RETRY_DELAY = 2000; +const WAIT_TIMEOUT = 2000; +const TIMEOUT_MSG = `Timeout after ${WAIT_TIMEOUT / 1000} seconds.`; +const SEARCH_WAIT_TIMEOUT = 5000; +const SEARCH_TIMEOUT_MSG = `Timeout after ${SEARCH_WAIT_TIMEOUT / 1000} seconds.`; + +Date.prototype.isoDate = function () { + return `${this.getFullYear()}-${String(this.getMonth() + 1).padStart(2, '0')}-${String(this.getDate()).padStart(2, '0')}`; +} + +Date.prototype.nzzDate = function () { + return `${String(this.getDate()).padStart(2, '0')}.${String(this.getMonth() + 1).padStart(2, '0')}.${this.getFullYear()}`; +} + +Date.prototype.addDays = function (days) { + let date = new Date(this.valueOf()); + date.setDate(date.getDate() + days); + return date; +} + +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +/** + * Wait for an in progress download to finish and move the file to the correct + * destination. + * + * @param {fs.PathLike} tmpDir Download directory. + * @param {fs.PathLike} outDir Final destination directory. + * @param {Date} date Date of the issue. + */ +async function moveDownload(tmpDir, outDir, date) { + return new Promise(resolve => { + let needsOffset = false; + + // Dates in filenames are off by one before 1894 + if (date.getFullYear() < 1894) { + date = date.addDays(-1); + needsOffset = true; + } + + let srcFile = path.join(tmpDir, `Gesamtausgabe_NZZ_-_Neue_Zürcher_Zeitung_${date.isoDate()}.pdf`); + + + fs.watchFile(srcFile, () => { + if (fs.existsSync(srcFile)) { + fs.unwatchFile(srcFile); + if (needsOffset) { + date = date.addDays(1); + } + + let destFile = path.join(outDir, `${date.isoDate()}.pdf`); + fs.copyFileSync(srcFile, destFile); + + resolve(); + } + }); + }); +} + +/** + * Enter the dates of the issue to download. + * + * @param {WebDriver} driver Selenium driver to use. + * @param {Date} date Date of the issue. + */ +async function enterDate(driver, date) { + console.log(driver); + let dateString = date.nzzDate(); + + let startDate = await driver.wait(until.elementLocated(By.css('.fup-s-date-start')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await startDate.clear(); + await startDate.sendKeys(dateString); + + let endDate = await driver.wait(until.elementLocated(By.css('.fup-s-date-end')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await endDate.clear(); + await endDate.sendKeys(dateString + Key.ENTER); +} + +/** + * Login to the NZZ archive. + * + * @param {WebDriver} driver Selenium driver to use. + * @param {String} user Username for the login. + * @param {String} password Password for the user. + */ +async function login(driver, user, password) { + console.log('logging in...'); + + await driver.get(URL); + await sleep(500); + + let loginButton = await driver.findElement(By.css('.fup-menu-login-container')); + await driver.wait(until.elementIsVisible(loginButton), WAIT_TIMEOUT); + await sleep(1000); + await loginButton.click(); + + let emailField = await driver.wait(until.elementLocated(By.id('c1-login-field')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await emailField.sendKeys(user + Key.ENTER); + + let pwField = await driver.wait(until.elementLocated(By.id('c1-password-field')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await pwField.sendKeys(password + Key.ENTER); +} + +/** + * Start the downloa of a full issue. + * + * @param {WebDriver} driver Selenium driver to use. + */ +async function download(driver) { + let menu = await driver.wait(until.elementLocated(By.css('.fup-menu-item-download')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await menu.click(); + + let download = await driver.wait(until.elementLocated(By.css('.fup-s-menu-download-edition')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await download.click(); + + let back = await driver.wait(until.elementLocated(By.css('.fup-s-menu-back')), WAIT_TIMEOUT, TIMEOUT_MSG, RETRY_DELAY); + await driver.wait(until.elementIsVisible(back), WAIT_TIMEOUT); + await sleep(500) +} + +/** + * Download all issues in a certain time span. + * + * @param {WebDriver} driver Selenium driver to use. + * @param {Date} from Earliest issue to download. + * @param {Date} to Latest issue to download. + * @param {fs.PathLike} tmpDir Download directory. + * @param {fs.PathLike} outDir Final destination directory. + */ +async function findIssues(driver, from, to, tmpDir, outDir) { + from = from.addDays(-1); + + while (from.toDateString() !== to.toDateString()) { + from = from.addDays(1); + await enterDate(driver, from); + + try { + let articles = await driver.wait(until.elementsLocated(By.css('.fup-archive-result-item-article-title')), SEARCH_WAIT_TIMEOUT, SEARCH_TIMEOUT_MSG, RETRY_DELAY); + await articles[0].click(); + + await download(driver); + console.log(`downloading ${from.isoDate()}...`); + + // do this in the background + moveDownload(tmpDir, outDir, from); + + await driver.get(URL); + } catch { + // this means there is no issue on the searched date + // move along with the next date + continue; + } + } +} + +/** + * Setup the headless browser and download all issues in the specified time span. + * + * @param {Date} from Earliest issue to download. + * @param {Date} to Latest issue to download. + * @param {String} user Username for the nzz archive. + * @param {String} password Password for the user. + * @param {fs.PathLike} outDir Final destination directory. + */ +async function run(from, to, user, password, outDir) { + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir); + } + + let tmpDir = tmp.dirSync(); + console.log(`downloading to ${outDir} (tmp dir: ${tmpDir.name})...`); + + let fxOptions = new fx.Options() + .addArguments('-headless') + .setPreference('pdfjs.disabled', true) + .setPreference('browser.helperApps.neverAsk.openFile', 'application/pdf') + .setPreference('browser.download.folderList', 2) + .setPreference('browser.download.manager.showWhenStartingout', false) + .setPreference('browser.download.dir', tmpDir.name) + .setPreference('browser.helperApps.neverAsk.saveToDisk', 'application/pdf'); + + let driver = await new Builder() + .forBrowser('firefox') + .setFirefoxOptions(fxOptions) + .build(); + + try { + await login(driver, user, password); + await findIssues(driver, from, to, tmpDir.name, outDir); + await sleep(1000); + } + finally { + driver.quit(); + fs.rmdirSync(tmpDir.name, { recursive: true }); + } +} + +/** + * Parse arguments and start the downloading off the issues. + */ +(async function init() { + let now = new Date(); + let nowString = now.isoDate(); + + const argv = yargs(hideBin(process.argv)) + .usage('Usage: $0 -f [date] -t [date] -o [path] -u [usernane] -p [password]') + .demandOption(['u', 'p']) + .help('h') + .describe('f', 'Earliest issue to download.') + .describe('t', 'Latest issue to download.') + .describe('o', 'Download directory.') + .describe('u', 'Username for the nzz archive.') + .describe('p', 'Password for the user.') + .alias('h', 'help') + .alias('f', 'from') + .alias('t', 'to') + .alias('o', 'out') + .alias('u', 'user') + .alias('p', 'password') + .default('f', nowString) + .default('t', nowString) + .default('o', './nzz') + .epilog('Copyright (c) Sebastian Hugentobler 2020') + .example("$0 -u 'myuser@example.com' -p 'mypassword' -f 1780-01-01 -t 1780-02-30", 'Download all existing issues from 01-01-1780 until 30-02-1780 to the default directory "./nzz"') + .argv; + + let from = new Date(argv.from); + let to = new Date(argv.to); + + if (from > to) { + console.error('"from" date must be before "to" date'); + process.exit(1); + } + + run(from, to, argv.user, argv.password, argv.out) +})(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a82bc26 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,331 @@ +{ + "name": "nzz-downloader", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jszip": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "selenium-webdriver": { + "version": "4.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.8.tgz", + "integrity": "sha512-yPSaiWySZTEbxuuWQMDqdXh3H3N4Aiw/bSUjpkKMPWWCysfPqUncrq6FewBqdxWD1wQKzy5yWaQMGsgTY/0rCQ==", + "requires": { + "jszip": "^3.5.0", + "rimraf": "^2.7.1", + "tmp": "^0.1.0", + "ws": "^7.3.1" + }, + "dependencies": { + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "requires": { + "rimraf": "^2.6.3" + } + } + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "requires": { + "rimraf": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f00728f --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "nzz-downloader", + "version": "0.1.0", + "description": "", + "main": "nzz.js", + "author": "Sebastian Hugentobler", + "license": "MPL-2.0", + "dependencies": { + "selenium-webdriver": "^4.0.0-alpha.8", + "tmp": "^0.2.1", + "yargs": "^16.2.0" + } +}