diff --git a/bin/epg-grabber.js b/bin/epg-grabber.js index 35a8c63..bbd7f5d 100755 --- a/bin/epg-grabber.js +++ b/bin/epg-grabber.js @@ -2,15 +2,13 @@ const { Command } = require('commander') const program = new Command() -const fs = require('fs') -const path = require('path') -const EPGGrabber = require('../src/index') -const utils = require('../src/utils') -const { name, version, description } = require('../package.json') const { merge } = require('lodash') const { gzip } = require('node-gzip') -const { createLogger, format, transports } = require('winston') -const { combine, timestamp, printf } = format +const file = require('../src/file') +const EPGGrabber = require('../src/index') +const { create: createLogger } = require('../src/logger') +const { parseInteger, getUTCDate } = require('../src/utils') +const { name, version, description } = require('../package.json') program .name(name) @@ -36,39 +34,13 @@ program .parse(process.argv) const options = program.opts() - -const fileFormat = printf(({ level, message, timestamp }) => { - return `[${timestamp}] ${level.toUpperCase()}: ${message}` -}) - -const consoleFormat = printf(({ level, message, timestamp }) => { - if (level === 'error') return ` Error: ${message}` - - return message -}) - -const t = [new transports.Console({ format: consoleFormat })] - -if (options.log) { - t.push( - new transports.File({ - filename: path.resolve(options.log), - format: combine(timestamp(), fileFormat), - options: { flags: 'w' } - }) - ) -} - -const logger = createLogger({ - level: options.logLevel, - transports: t -}) +const logger = createLogger(options) async function main() { logger.info('Starting...') logger.info(`Loading '${options.config}'...`) - let config = require(path.resolve(options.config)) + let config = require(file.resolve(options.config)) config = merge(config, { days: options.days, debug: options.debug, @@ -83,22 +55,27 @@ async function main() { if (options.cacheTtl) config.request.cache.ttl = options.cacheTtl if (options.channels) config.channels = options.channels else if (config.channels) - config.channels = path.join(path.dirname(options.config), config.channels) + config.channels = file.join(file.dirname(options.config), config.channels) else throw new Error("The required 'channels' property is missing") if (!config.channels) return logger.error('Path to [site].channels.xml is missing') logger.info(`Loading '${config.channels}'...`) - const channelsXML = fs.readFileSync(path.resolve(config.channels), { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(channelsXML) + const grabber = new EPGGrabber(config) + + const channelsXML = file.read(config.channels) + const { channels } = grabber.parseChannels(channelsXML) let programs = [] let i = 1 let days = options.days || 1 const total = channels.length * days - const utcDate = utils.getUTCDate() + const utcDate = getUTCDate() const dates = Array.from({ length: config.days }, (_, i) => utcDate.add(i, 'd')) - const grabber = new EPGGrabber(config) for (let channel of channels) { + if (!channel.logo && config.logo) { + channel.logo = await grabber.loadLogo(channel) + } + for (let date of dates) { await grabber .grab(channel, date, (data, err) => { @@ -118,15 +95,15 @@ async function main() { } } - const xml = utils.convertToXMLTV({ config, channels, programs }) + const xml = grabber.generateXMLTV({ config, channels, programs }) let outputPath = options.output || config.output if (options.gzip) { outputPath = outputPath || 'guide.xml.gz' const compressed = await gzip(xml) - utils.writeToFile(outputPath, compressed) + file.write(outputPath, compressed) } else { outputPath = outputPath || 'guide.xml' - utils.writeToFile(outputPath, xml) + file.write(outputPath, xml) } logger.info(`File '${outputPath}' successfully saved`) @@ -134,7 +111,3 @@ async function main() { } main() - -function parseInteger(val) { - return val ? parseInt(val) : null -} diff --git a/package-lock.json b/package-lock.json index e7cfd8f..51bb6e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "epg-grabber", - "version": "0.25.4", + "version": "0.27.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "epg-grabber", - "version": "0.25.4", + "version": "0.27.2", "license": "MIT", "dependencies": { "axios": "^0.21.1", @@ -30,6 +30,9 @@ "@babel/core": "^7.13.14", "@babel/preset-env": "^7.13.12", "babel-jest": "^26.6.3", + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-es2015": "^6.24.1", "eslint": "^7.22.0", "jest": "^26.6.3", "jest-mock-axios": "^4.4.1" @@ -2439,6 +2442,170 @@ "node": ">=4" } }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true + }, + "node_modules/babel-code-frame/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ==", + "dev": true, + "dependencies": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA==", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==", + "dev": true, + "dependencies": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw==", + "dev": true, + "dependencies": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, "node_modules/babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -2531,6 +2698,24 @@ "node": ">=8" } }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -2610,6 +2795,323 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==", + "dev": true + }, + "node_modules/babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag==", + "dev": true, + "dependencies": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg==", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA==", + "dev": true, + "dependencies": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "dependencies": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg==", + "dev": true, + "dependencies": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw==", + "dev": true, + "dependencies": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA==", + "dev": true, + "dependencies": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ==", + "dev": true, + "dependencies": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ==", + "dev": true, + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ==", + "dev": true, + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex/node_modules/regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex/node_modules/regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==", + "dev": true + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex/node_modules/regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg==", + "dev": true, + "dependencies": { + "regenerator-transform": "^0.10.0" + } + }, + "node_modules/babel-plugin-transform-regenerator/node_modules/regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "node_modules/babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha512-cpGMVC1vt/772y3jx1gwSaTitQVZuFDlllgreMsZ+rTYC6jlYXRyf5FQOgSnckOiA5QmzbXTyBY2A5AmZXF1fA==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -2633,6 +3135,39 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha512-XfwUqG1Ry6R43m4Wfob+vHbIVBIqTg/TJY4Snku1iIzeH7mUnwHA8Vagmv+ZQbPwhS8HgsdQvy28Py3k5zpoFQ==", + "deprecated": "๐Ÿ™Œ Thanks for using Babel: we recommend using babel-preset-env now: please read https://babeljs.io/env to update!", + "dev": true, + "dependencies": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, "node_modules/babel-preset-jest": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", @@ -2649,6 +3184,106 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babel-types/node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -3124,6 +3759,14 @@ "node": ">=0.10.0" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, "node_modules/core-js-compat": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.0.tgz", @@ -4505,6 +5148,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4713,6 +5377,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -6867,6 +7540,18 @@ "triple-beam": "^1.3.0" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7604,6 +8289,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -11858,6 +12552,157 @@ } } }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ==", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA==", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw==", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, "babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -11925,6 +12770,24 @@ } } }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -11989,6 +12852,321 @@ "@babel/helper-define-polyfill-provider": "^0.1.5" } }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==", + "dev": true + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag==", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg==", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA==", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg==", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw==", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA==", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ==", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ==", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ==", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ==", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg==", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + }, + "dependencies": { + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + } + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha512-cpGMVC1vt/772y3jx1gwSaTitQVZuFDlllgreMsZ+rTYC6jlYXRyf5FQOgSnckOiA5QmzbXTyBY2A5AmZXF1fA==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, "babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -12009,6 +13187,38 @@ "@babel/plugin-syntax-top-level-await": "^7.8.3" } }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha512-XfwUqG1Ry6R43m4Wfob+vHbIVBIqTg/TJY4Snku1iIzeH7mUnwHA8Vagmv+ZQbPwhS8HgsdQvy28Py3k5zpoFQ==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, "babel-preset-jest": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", @@ -12019,6 +13229,103 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -12403,6 +13710,12 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, "core-js-compat": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.0.tgz", @@ -13461,6 +14774,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13618,6 +14948,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -15247,6 +16586,15 @@ "triple-beam": "^1.3.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -15814,6 +17162,12 @@ } } }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/src/channels.js b/src/channels.js new file mode 100644 index 0000000..507856f --- /dev/null +++ b/src/channels.js @@ -0,0 +1,26 @@ +const convert = require('xml-js') + +module.exports.parse = parse + +function parse(xml) { + const result = convert.xml2js(xml) + const siteTag = result.elements.find(el => el.name === 'site') || {} + if (!siteTag.elements) return [] + const site = siteTag.attributes.site + + const channelsTag = siteTag.elements.find(el => el.name === 'channels') + if (!channelsTag.elements) return [] + + const channels = channelsTag.elements + .filter(el => el.name === 'channel') + .map(el => { + const channel = el.attributes + if (!el.elements) throw new Error(`Channel '${channel.xmltv_id}' has no valid name`) + channel.name = el.elements.find(el => el.type === 'text').text + channel.site = channel.site || site + + return channel + }) + + return { site, channels } +} diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..57e0d14 --- /dev/null +++ b/src/client.js @@ -0,0 +1,143 @@ +const { merge } = require('lodash') +const { CurlGenerator } = require('curl-generator') +const axios = require('axios').default +const axiosCookieJarSupport = require('axios-cookiejar-support').default +const { setupCache } = require('axios-cache-interceptor') +const { isPromise, getUTCDate } = require('./utils') + +axiosCookieJarSupport(axios) + +module.exports.create = create +module.exports.buildRequest = buildRequest +module.exports.parseResponse = parseResponse + +let timeout + +class Client { + constructor() {} +} + +function create(config) { + const client = setupCache( + axios.create({ + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' + } + }) + ) + + client.interceptors.request.use( + function (request) { + if (config.debug) { + console.log('Request:', JSON.stringify(request, null, 2)) + } + return request + }, + function (error) { + return Promise.reject(error) + } + ) + + client.interceptors.response.use( + function (response) { + if (config.debug) { + const data = + isObject(response.data) || Array.isArray(response.data) + ? JSON.stringify(response.data) + : response.data.toString() + console.log( + 'Response:', + JSON.stringify( + { + headers: response.headers, + data, + cached: response.cached + }, + null, + 2 + ) + ) + } + + clearTimeout(timeout) + return response + }, + function (error) { + clearTimeout(timeout) + return Promise.reject(error) + } + ) + + return client +} + +async function buildRequest({ channel, date, config }) { + date = typeof date === 'string' ? getUTCDate(date) : date + const CancelToken = axios.CancelToken + const source = CancelToken.source() + const request = { ...config.request } + timeout = setTimeout(() => { + source.cancel('Connection timeout') + }, request.timeout) + request.headers = await getRequestHeaders({ channel, date, config }) + request.url = await getRequestUrl({ channel, date, config }) + request.data = await getRequestData({ channel, date, config }) + request.cancelToken = source.token + + if (config.curl) { + const curl = CurlGenerator({ + url: request.url, + method: request.method, + headers: request.headers, + body: request.data + }) + console.log(curl) + } + + return request +} + +function parseResponse(response) { + return { + content: response.data.toString(), + buffer: response.data, + headers: response.headers, + request: response.request, + cached: response.cached + } +} + +async function getRequestHeaders({ channel, date, config }) { + if (typeof config.request.headers === 'function') { + const headers = config.request.headers({ channel, date }) + if (isPromise(headers)) { + return await headers + } + return headers + } + + return config.request.headers || null +} + +async function getRequestData({ channel, date, config }) { + if (typeof config.request.data === 'function') { + const data = config.request.data({ channel, date }) + if (isPromise(data)) { + return await data + } + return data + } + return config.request.data || null +} + +async function getRequestUrl({ channel, date, config }) { + if (typeof config.url === 'function') { + const url = config.url({ channel, date }) + if (isPromise(url)) { + return await url + } + return url + } + return config.url +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..8b553fb --- /dev/null +++ b/src/config.js @@ -0,0 +1,34 @@ +const tough = require('tough-cookie') +const { merge } = require('lodash') + +module.exports.load = load + +function load(config) { + if (!config.site) throw new Error("The required 'site' property is missing") + if (!config.url) throw new Error("The required 'url' property is missing") + if (typeof config.url !== 'function' && typeof config.url !== 'string') + throw new Error("The 'url' property should return the function or string") + if (!config.parser) throw new Error("The required 'parser' function is missing") + if (typeof config.parser !== 'function') + throw new Error("The 'parser' property should return the function") + if (config.logo && typeof config.logo !== 'function') + throw new Error("The 'logo' property should return the function") + + const defaultConfig = { + days: 1, + lang: 'en', + delay: 3000, + output: 'guide.xml', + request: { + method: 'GET', + maxContentLength: 5 * 1024 * 1024, + timeout: 5000, + withCredentials: true, + jar: new tough.CookieJar(), + responseType: 'arraybuffer', + cache: false + } + } + + return merge(defaultConfig, config) +} diff --git a/src/file.js b/src/file.js new file mode 100644 index 0000000..339c438 --- /dev/null +++ b/src/file.js @@ -0,0 +1,33 @@ +const fs = require('fs') +const path = require('path') + +module.exports.read = read +module.exports.write = write +module.exports.resolve = resolve +module.exports.join = join +module.exports.dirname = dirname + +function read(filepath) { + return fs.readFileSync(path.resolve(filepath), { encoding: 'utf-8' }) +} + +function write(filepath, data) { + const dir = path.resolve(path.dirname(filepath)) + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + + fs.writeFileSync(path.resolve(filepath), data) +} + +function resolve(filepath) { + return path.resolve(filepath) +} + +function join(path1, path2) { + return path.join(path1, path2) +} + +function dirname(filepath) { + return path.dirname(filepath) +} diff --git a/src/index.js b/src/index.js index b0b10d9..ad7576b 100755 --- a/src/index.js +++ b/src/index.js @@ -1,41 +1,48 @@ -const utils = require('./utils') +const { merge } = require('lodash') +const { create: createClient, buildRequest, parseResponse } = require('./client') +const { generate: generateXMLTV } = require('./xmltv') +const { parse: parseChannels } = require('./channels') +const { parse: parsePrograms } = require('./programs') +const { load: loadConfig } = require('./config') +const { sleep, isPromise } = require('./utils') class EPGGrabber { constructor(config = {}) { - this.config = utils.loadConfig(config) - this.client = utils.createClient(config) + this.config = loadConfig(config) + this.client = createClient(config) + } + + async loadLogo(channel) { + const logo = this.config.logo({ channel }) + if (isPromise(logo)) { + return await logo + } + return logo } async grab(channel, date, cb = () => {}) { - date = typeof date === 'string' ? utils.getUTCDate(date) : date - channel.lang = channel.lang || this.config.lang || null + await sleep(this.config.delay) - let programs = [] - const item = { date, channel } - await utils - .buildRequest(item, this.config) - .then(request => utils.fetchData(this.client, request)) - .then(response => utils.parseResponse(item, response, this.config)) - .then(results => { - item.programs = results - cb(item, null) - programs = programs.concat(results) + return buildRequest({ channel, date, config: this.config }) + .then(this.client) + .then(parseResponse) + .then(data => merge({ channel, date, config: this.config }, data)) + .then(parsePrograms) + .then(programs => { + cb({ channel, date, programs }) + + return programs }) - .catch(error => { - item.programs = [] - if (this.config.debug) { - console.log('Error:', JSON.stringify(error, null, 2)) - } - cb(item, error) + .catch(err => { + if (this.config.debug) console.log('Error:', JSON.stringify(err, null, 2)) + cb({ channel, date, programs: [] }, err) + + return [] }) - - await utils.sleep(this.config.delay) - - return programs } } -EPGGrabber.convertToXMLTV = utils.convertToXMLTV -EPGGrabber.parseChannels = utils.parseChannels +EPGGrabber.prototype.generateXMLTV = generateXMLTV +EPGGrabber.prototype.parseChannels = parseChannels module.exports = EPGGrabber diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..9261c71 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,33 @@ +const { createLogger, format, transports } = require('winston') +const { combine, timestamp, printf } = format + +module.exports.create = create + +function create(options) { + const fileFormat = printf(({ level, message, timestamp }) => { + return `[${timestamp}] ${level.toUpperCase()}: ${message}` + }) + + const consoleFormat = printf(({ level, message, timestamp }) => { + if (level === 'error') return ` Error: ${message}` + + return message + }) + + const t = [new transports.Console({ format: consoleFormat })] + + if (options.log) { + t.push( + new transports.File({ + filename: path.resolve(options.log), + format: combine(timestamp(), fileFormat), + options: { flags: 'w' } + }) + ) + } + + return createLogger({ + level: options.logLevel, + transports: t + }) +} diff --git a/src/programs.js b/src/programs.js new file mode 100644 index 0000000..dfbb933 --- /dev/null +++ b/src/programs.js @@ -0,0 +1,29 @@ +const dayjs = require('dayjs') +const { isPromise } = require('./utils') + +module.exports.parse = parse + +async function parse(data) { + const { config, channel } = data + let programs = config.parser(data) + + if (isPromise(programs)) { + programs = await programs + } + + if (!Array.isArray(programs)) { + throw new Error('Parser should return an array') + } + + return programs + .filter(i => i) + .map(program => { + program.channel = channel.xmltv_id + + return program + }) +} + +function toBaseObject(data) { + return data +} diff --git a/src/utils.js b/src/utils.js index 5fab4a6..5904034 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,127 +1,34 @@ -const fs = require('fs') -const { padStart } = require('lodash') -const path = require('path') -const axios = require('axios').default -const axiosCookieJarSupport = require('axios-cookiejar-support').default -const { setupCache } = require('axios-cache-interceptor') -const tough = require('tough-cookie') -const convert = require('xml-js') -const { merge } = require('lodash') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') -const { CurlGenerator } = require('curl-generator') + dayjs.extend(utc) -axiosCookieJarSupport(axios) -let timeout -const utils = {} -const defaultUserAgent = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' +module.exports.sleep = sleep +module.exports.getUTCDate = getUTCDate +module.exports.isPromise = isPromise +module.exports.isObject = isObject +module.exports.escapeString = escapeString +module.exports.parseInteger = parseInteger -utils.loadConfig = function (config) { - if (!config.site) throw new Error("The required 'site' property is missing") - if (!config.url) throw new Error("The required 'url' property is missing") - if (typeof config.url !== 'function' && typeof config.url !== 'string') - throw new Error("The 'url' property should return the function or string") - if (!config.parser) throw new Error("The required 'parser' function is missing") - if (typeof config.parser !== 'function') - throw new Error("The 'parser' property should return the function") - if (config.logo && typeof config.logo !== 'function') - throw new Error("The 'logo' property should return the function") - - const defaultConfig = { - days: 1, - lang: 'en', - delay: 3000, - output: 'guide.xml', - request: { - method: 'GET', - maxContentLength: 5 * 1024 * 1024, - timeout: 5000, - withCredentials: true, - jar: new tough.CookieJar(), - responseType: 'arraybuffer', - cache: false - } - } - - return merge(defaultConfig, config) -} - -utils.createClient = function (config) { - const client = setupCache(axios.create()) - client.interceptors.request.use( - function (request) { - if (config.debug) { - console.log('Request:', JSON.stringify(request, null, 2)) - } - return request - }, - function (error) { - return Promise.reject(error) - } - ) - client.interceptors.response.use( - function (response) { - if (config.debug) { - const data = - utils.isObject(response.data) || Array.isArray(response.data) - ? JSON.stringify(response.data) - : response.data.toString() - console.log( - 'Response:', - JSON.stringify( - { - headers: response.headers, - data, - cached: response.cached - }, - null, - 2 - ) - ) - } - - clearTimeout(timeout) - return response - }, - function (error) { - clearTimeout(timeout) - return Promise.reject(error) - } - ) - - return client -} - -utils.parseChannels = function (xml) { - const result = convert.xml2js(xml) - const siteTag = result.elements.find(el => el.name === 'site') || {} - if (!siteTag.elements) return [] - const site = siteTag.attributes.site - - const channelsTag = siteTag.elements.find(el => el.name === 'channels') - if (!channelsTag.elements) return [] - - const channels = channelsTag.elements - .filter(el => el.name === 'channel') - .map(el => { - const channel = el.attributes - if (!el.elements) throw new Error(`Channel '${channel.xmltv_id}' has no valid name`) - channel.name = el.elements.find(el => el.type === 'text').text - channel.site = channel.site || site - - return channel - }) - - return { site, channels } -} - -utils.sleep = function (ms) { +function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } -utils.escapeString = function (string, defaultValue = '') { +function isObject(a) { + return !!a && a.constructor === Object +} + +function isPromise(promise) { + return !!promise && typeof promise.then === 'function' +} + +function getUTCDate(d = null) { + if (typeof d === 'string') return dayjs.utc(d).startOf('d') + + return dayjs.utc().startOf('d') +} + +function escapeString(string, defaultValue = '') { if (!string) return defaultValue const regex = new RegExp( @@ -149,372 +56,6 @@ utils.escapeString = function (string, defaultValue = '') { .trim() } -utils.convertToXMLTV = function ({ channels, programs, date = dayjs.utc() }) { - let output = `\r\n` - for (let channel of channels) { - const id = utils.escapeString(channel['xmltv_id']) - const displayName = utils.escapeString(channel.name) - output += `${displayName}` - if (channel.logo) { - const logo = utils.escapeString(channel.logo) - output += `` - } - if (channel.site) { - const url = channel.site ? 'https://' + channel.site : null - output += `${url}` - } - output += `\r\n` - } - - for (let program of programs) { - if (!program) continue - - const channel = utils.escapeString(program.channel) - const title = utils.escapeString(program.title) - const description = utils.escapeString(program.description) - const categories = Array.isArray(program.category) ? program.category : [program.category] - const start = program.start ? dayjs.unix(program.start).utc().format('YYYYMMDDHHmmss ZZ') : '' - const stop = program.stop ? dayjs.unix(program.stop).utc().format('YYYYMMDDHHmmss ZZ') : '' - const lang = program.lang || 'en' - const xmltv_ns = createXMLTVNS(program.season, program.episode) - const onscreen = createOnScreen(program.season, program.episode) - const date = program.date || '' - const credits = createCredits({ - director: program.director, - actor: program.actor, - writer: program.writer, - adapter: program.adapter, - producer: program.producer, - composer: program.composer, - editor: program.editor, - presenter: program.presenter, - commentator: program.commentator, - guest: program.guest - }) - const icon = utils.escapeString(program.icon) - const sub_title = utils.escapeString(program.sub_title) - const url = program.url ? createURL(program.url, channel) : '' - - if (start && stop && title) { - output += `${title}` - - if (sub_title) { - output += `${sub_title}` - } - - if (description) { - output += `${description}` - } - - if (categories.length) { - categories.forEach(category => { - if (category) { - output += `${utils.escapeString(category)}` - } - }) - } - - if (url) { - output += url - } - - if (xmltv_ns) { - output += `${xmltv_ns}` - } - - if (onscreen) { - output += `${onscreen}` - } - if (date) { - output += `${date}` - } - - if (icon) { - output += `` - } - - if (credits) { - output += `${credits}` - } - - output += '\r\n' - } - } - - output += '' - - function createXMLTVNS(s, e) { - if (!e) return null - s = s || 1 - - return `${s - 1}.${e - 1}.0/1` - } - - function createOnScreen(s, e) { - if (!e) return null - s = s || 1 - - s = padStart(s, 2, '0') - e = padStart(e, 2, '0') - - return `S${s}E${e}` - } - - function createURL(urlObj, channel = '') { - const urls = Array.isArray(urlObj) ? urlObj : [urlObj] - let output = '' - for (let url of urls) { - if (typeof url === 'string' || url instanceof String) { - url = { value: url } - } - - let attr = url.system ? ` system="${url.system}"` : '' - if (url.value.includes('http')) { - output += `${url.value}` - } else if (channel) { - let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0) - if (chan && chan.site) { - output += `https://${chan.site}${url.value}` - } - } - } - - return output - } - - function createImage(imgObj, channel = '') { - const imgs = Array.isArray(imgObj) ? imgObj : [imgObj] - let output = '' - for (let img of imgs) { - if (typeof img === 'string' || img instanceof String) { - img = { value: img } - } - - const imageTypes = ['poster', 'backdrop', 'still', 'person', 'character'] - const imageSizes = ['1', '2', '3'] - const imageOrients = ['P', 'L'] - - let attr = '' - - if (img.type && imageTypes.some(el => img.type.includes(el))) { - attr += ` type="${img.type}"` - } - - if (img.size && imageSizes.some(el => img.size.includes(el))) { - attr += ` size="${img.size}"` - } - - if (img.orient && imageOrients.some(el => img.orient.includes(el))) { - attr += ` orient="${img.orient}"` - } - - if (img.system) { - attr += ` system="${img.system}"` - } - - if (img.value.includes('http')) { - output += `${img.value}` - } else if (channel) { - let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0) - if (chan && chan.site) { - output += `https://${chan.site}${img.value}` - } - } - } - - return output - } - - function createCredits(obj) { - let cast = Object.entries(obj) - .filter(x => x[1]) - .map(([name, value]) => ({ name, value })) - - let output = '' - for (let type of cast) { - const r = Array.isArray(type.value) ? type.value : [type.value] - for (let person of r) { - if (typeof person === 'string' || person instanceof String) { - person = { value: person } - } - - let attr = '' - if (type.name.localeCompare('actor') === 0 && type.value.role) { - attr += ` role="${type.value.role}"` - } - if (type.name.localeCompare('actor') === 0 && type.value.guest) { - attr += ` guest="${type.value.guest}"` - } - output += `<${type.name}${attr}>${person.value}` - - if (person.url) { - output += createURL(person.url) - } - if (person.image) { - output += createImage(person.image) - } - - output += `` - } - } - return output - } - return output +function parseInteger(val) { + return val ? parseInt(val) : null } - -utils.writeToFile = function (filename, data) { - const dir = path.resolve(path.dirname(filename)) - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }) - } - - fs.writeFileSync(path.resolve(filename), data) -} - -utils.buildRequest = async function (item, config) { - const CancelToken = axios.CancelToken - const source = CancelToken.source() - const request = { ...config.request } - timeout = setTimeout(() => { - source.cancel('Connection timeout') - }, request.timeout) - const headers = await utils.getRequestHeaders(item, config) - request.headers = { 'User-Agent': defaultUserAgent, ...headers } - request.url = await utils.getRequestUrl(item, config) - request.data = await utils.getRequestData(item, config) - request.cancelToken = source.token - - if (config.curl) { - const curl = CurlGenerator({ - url: request.url, - method: request.method, - headers: request.headers, - body: request.data - }) - console.log(curl) - } - - return request -} - -utils.fetchData = function (client, request) { - return client(request) -} - -utils.getRequestHeaders = async function (item, config) { - if (typeof config.request.headers === 'function') { - const headers = config.request.headers(item) - if (this.isPromise(headers)) { - return await headers - } - return headers - } - return config.request.headers || null -} - -utils.getRequestData = async function (item, config) { - if (typeof config.request.data === 'function') { - const data = config.request.data(item) - if (this.isPromise(data)) { - return await data - } - return data - } - return config.request.data || null -} - -utils.getRequestUrl = async function (item, config) { - if (typeof config.url === 'function') { - const url = config.url(item) - if (this.isPromise(url)) { - return await url - } - return url - } - return config.url -} - -utils.getUTCDate = function (d = null) { - if (typeof d === 'string') return dayjs.utc(d).startOf('d') - - return dayjs.utc().startOf('d') -} - -utils.parseResponse = async (item, response, config) => { - const data = merge(item, config, { - content: response.data.toString(), - buffer: response.data, - headers: response.headers, - request: response.request, - cached: response.cached - }) - - if (!item.channel.logo && config.logo) { - data.channel.logo = await utils.loadLogo(data, config) - } - - return await utils.parsePrograms(data, config) -} - -utils.parsePrograms = async function (data, config) { - let programs = config.parser(data) - - if (this.isPromise(programs)) { - programs = await programs - } - - if (!Array.isArray(programs)) { - throw new Error('Parser should return an array') - } - - const channel = data.channel - return programs - .filter(i => i) - .map(program => { - return { - title: program.title, - description: program.description || null, - category: program.category || null, - season: program.season || null, - episode: program.episode || null, - sub_title: program.sub_title || null, - url: program.url || null, - icon: program.icon || null, - channel: channel.xmltv_id, - lang: program.lang || channel.lang || config.lang || 'en', - start: program.start ? dayjs(program.start).unix() : null, - stop: program.stop ? dayjs(program.stop).unix() : null, - date: program.date || null, - director: program.director || null, - actor: program.actor || null, - writer: program.writer || null, - adapter: program.adapter || null, - producer: program.producer || null, - composer: program.composer || null, - editor: program.editor || null, - presenter: program.presenter || null, - commentator: program.commentator || null, - guest: program.guest || null - } - }) -} - -utils.loadLogo = async function (options, config) { - const logo = config.logo(options) - if (this.isPromise(logo)) { - return await logo - } - return logo -} - -utils.isPromise = function (promise) { - return !!promise && typeof promise.then === 'function' -} - -utils.isObject = function (a) { - return !!a && a.constructor === Object -} - -module.exports = utils diff --git a/src/xmltv.js b/src/xmltv.js new file mode 100644 index 0000000..acac35f --- /dev/null +++ b/src/xmltv.js @@ -0,0 +1,224 @@ +const { padStart } = require('lodash') +const { escapeString, getUTCDate } = require('./utils') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports.generate = generate + +function generate({ channels, programs, date = getUTCDate() }) { + let output = `\r\n` + for (let channel of channels) { + const id = escapeString(channel['xmltv_id']) + const displayName = escapeString(channel.name) + output += `${displayName}` + if (channel.logo) { + const logo = escapeString(channel.logo) + output += `` + } + if (channel.site) { + const url = channel.site ? 'https://' + channel.site : null + output += `${url}` + } + output += `\r\n` + } + + for (let program of programs) { + if (!program) continue + + const channel = escapeString(program.channel) + const title = escapeString(program.title) + const description = escapeString(program.description) + const categories = Array.isArray(program.category) ? program.category : [program.category] + const start = program.start ? dayjs.unix(program.start).utc().format('YYYYMMDDHHmmss ZZ') : '' + const stop = program.stop ? dayjs.unix(program.stop).utc().format('YYYYMMDDHHmmss ZZ') : '' + const lang = program.lang || 'en' + const xmltv_ns = createXMLTVNS(program.season, program.episode) + const onscreen = createOnScreen(program.season, program.episode) + const date = program.date || '' + const credits = createCredits({ + director: program.director, + actor: program.actor, + writer: program.writer, + adapter: program.adapter, + producer: program.producer, + composer: program.composer, + editor: program.editor, + presenter: program.presenter, + commentator: program.commentator, + guest: program.guest + }) + const icon = escapeString(program.icon) + const sub_title = escapeString(program.sub_title) + const url = program.url ? createURL(program.url, channel) : '' + + if (start && stop && title) { + output += `${title}` + + if (sub_title) { + output += `${sub_title}` + } + + if (description) { + output += `${description}` + } + + if (categories.length) { + categories.forEach(category => { + if (category) { + output += `${escapeString(category)}` + } + }) + } + + if (url) { + output += url + } + + if (xmltv_ns) { + output += `${xmltv_ns}` + } + + if (onscreen) { + output += `${onscreen}` + } + if (date) { + output += `${date}` + } + + if (icon) { + output += `` + } + + if (credits) { + output += `${credits}` + } + + output += '\r\n' + } + } + + output += '' + + return output +} + +function createXMLTVNS(s, e) { + if (!e) return null + s = s || 1 + + return `${s - 1}.${e - 1}.0/1` +} + +function createOnScreen(s, e) { + if (!e) return null + s = s || 1 + + s = padStart(s, 2, '0') + e = padStart(e, 2, '0') + + return `S${s}E${e}` +} + +function createURL(urlObj, channel = '') { + const urls = Array.isArray(urlObj) ? urlObj : [urlObj] + let output = '' + for (let url of urls) { + if (typeof url === 'string' || url instanceof String) { + url = { value: url } + } + + let attr = url.system ? ` system="${url.system}"` : '' + if (url.value.includes('http')) { + output += `${url.value}` + } else if (channel) { + let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0) + if (chan && chan.site) { + output += `https://${chan.site}${url.value}` + } + } + } + + return output +} + +function createImage(imgObj, channel = '') { + const imgs = Array.isArray(imgObj) ? imgObj : [imgObj] + let output = '' + for (let img of imgs) { + if (typeof img === 'string' || img instanceof String) { + img = { value: img } + } + + const imageTypes = ['poster', 'backdrop', 'still', 'person', 'character'] + const imageSizes = ['1', '2', '3'] + const imageOrients = ['P', 'L'] + + let attr = '' + + if (img.type && imageTypes.some(el => img.type.includes(el))) { + attr += ` type="${img.type}"` + } + + if (img.size && imageSizes.some(el => img.size.includes(el))) { + attr += ` size="${img.size}"` + } + + if (img.orient && imageOrients.some(el => img.orient.includes(el))) { + attr += ` orient="${img.orient}"` + } + + if (img.system) { + attr += ` system="${img.system}"` + } + + if (img.value.includes('http')) { + output += `${img.value}` + } else if (channel) { + let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0) + if (chan && chan.site) { + output += `https://${chan.site}${img.value}` + } + } + } + + return output +} + +function createCredits(obj) { + let cast = Object.entries(obj) + .filter(x => x[1]) + .map(([name, value]) => ({ name, value })) + + let output = '' + for (let type of cast) { + const r = Array.isArray(type.value) ? type.value : [type.value] + for (let person of r) { + if (typeof person === 'string' || person instanceof String) { + person = { value: person } + } + + let attr = '' + if (type.name.localeCompare('actor') === 0 && type.value.role) { + attr += ` role="${type.value.role}"` + } + if (type.name.localeCompare('actor') === 0 && type.value.guest) { + attr += ` guest="${type.value.guest}"` + } + output += `<${type.name}${attr}>${person.value}` + + if (person.url) { + output += createURL(person.url) + } + if (person.image) { + output += createImage(person.image) + } + + output += `` + } + } + return output +} diff --git a/tests/channels.test.js b/tests/channels.test.js new file mode 100644 index 0000000..b51558a --- /dev/null +++ b/tests/channels.test.js @@ -0,0 +1,26 @@ +import { parse as parseChannels } from '../src/channels' +import path from 'path' +import fs from 'fs' + +it('can parse valid channels.xml', () => { + const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) + const { channels } = parseChannels(file) + expect(channels).toEqual([ + { + name: '1 TV', + xmltv_id: '1TV.com', + site_id: '1', + site: 'example.com', + lang: 'fr', + logo: 'https://example.com/logos/1TV.png' + }, + { + name: '2 TV', + xmltv_id: '2TV.com', + site_id: '2', + site: 'example.com', + lang: undefined, + logo: undefined + } + ]) +}) diff --git a/tests/client.test.js b/tests/client.test.js new file mode 100644 index 0000000..06b18e3 --- /dev/null +++ b/tests/client.test.js @@ -0,0 +1,47 @@ +import { buildRequest, create as createClient } from '../src/client' + +const config = { + days: 1, + lang: 'en', + delay: 3000, + output: 'guide.xml', + request: { + method: 'POST', + maxContentLength: 5 * 1024 * 1024, + timeout: 5000, + withCredentials: true, + responseType: 'arraybuffer', + cache: false, + data: { accountID: '123' }, + headers: { + 'Content-Type': 'application/json', + Cookie: 'abc=123', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' + } + }, + url: 'http://example.com/20210319/1tv.json' +} + +it('can build request', done => { + buildRequest({ config }) + .then(request => { + expect(request).toMatchObject({ + data: { accountID: '123' }, + headers: { + 'Content-Type': 'application/json', + Cookie: 'abc=123', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' + }, + maxContentLength: 5242880, + method: 'POST', + responseType: 'arraybuffer', + timeout: 5000, + url: 'http://example.com/20210319/1tv.json', + withCredentials: true + }) + done() + }) + .catch(done) +}) diff --git a/tests/config.test.js b/tests/config.test.js new file mode 100644 index 0000000..c2a51ce --- /dev/null +++ b/tests/config.test.js @@ -0,0 +1,26 @@ +import { load as loadConfig } from '../src/config' +import path from 'path' +import fs from 'fs' + +it('can load config', () => { + const config = loadConfig(require(path.resolve('./tests/input/example.com.config.js'))) + expect(config).toMatchObject({ + days: 1, + delay: 3000, + lang: 'en', + site: 'example.com' + }) + expect(config.request).toMatchObject({ + timeout: 5000, + headers: { + 'Content-Type': 'application/json', + Cookie: 'abc=123' + } + }) + expect(typeof config.request.data).toEqual('function') + expect(typeof config.url).toEqual('function') + expect(typeof config.logo).toEqual('function') + expect(config.request.data()).toEqual({ accountID: '123' }) + expect(config.url()).toEqual('http://example.com/20210319/1tv.json') + expect(config.logo()).toEqual('http://example.com/logos/1TV.png?x=ัˆะตะปะปั‹&sid=777') +}) diff --git a/tests/programs.test.js b/tests/programs.test.js new file mode 100644 index 0000000..01106ed --- /dev/null +++ b/tests/programs.test.js @@ -0,0 +1,66 @@ +import { parse as parsePrograms } from '../src/programs' + +const channel = { xmltv_id: '1tv', lang: 'en' } + +it('can parse programs', done => { + const config = { + parser: () => [ + { + title: 'Title', + description: 'Description', + lang: 'en', + category: ['Category1', 'Category2'], + icon: 'https://example.com/image.jpg', + season: 9, + episode: 238, + start: 1640995200, + stop: 1640998800 + } + ] + } + + parsePrograms({ channel, config }) + .then(programs => { + expect(programs).toMatchObject([ + { + title: 'Title', + description: 'Description', + lang: 'en', + category: ['Category1', 'Category2'], + icon: 'https://example.com/image.jpg', + season: 9, + episode: 238, + start: 1640995200, + stop: 1640998800, + channel: '1tv' + } + ]) + done() + }) + .catch(done) +}) + +it('can parse programs async', done => { + const config = { + parser: async () => [ + { + title: 'Title', + description: 'Description', + lang: 'en', + category: ['Category1', 'Category2'], + icon: 'https://example.com/image.jpg', + season: 9, + episode: 238, + start: 1640995200, + stop: 1640998800 + } + ] + } + + parsePrograms({ channel, config }) + .then(programs => { + expect(programs.length).toBe(1) + done() + }) + .catch(done) +}) diff --git a/tests/utils.test.js b/tests/utils.test.js index 3694501..4ebef97 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,401 +1,11 @@ -import mockAxios from 'jest-mock-axios' -import utils from '../src/utils' -import axios from 'axios' -import path from 'path' -import fs from 'fs' - -jest.useFakeTimers('modern').setSystemTime(new Date('2022-05-05')) - -it('can load valid config.js', () => { - const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js'))) - expect(config).toMatchObject({ - days: 1, - delay: 3000, - lang: 'en', - site: 'example.com' - }) - expect(config.request).toMatchObject({ - timeout: 5000, - headers: { - 'Content-Type': 'application/json', - Cookie: 'abc=123' - } - }) - expect(typeof config.request.data).toEqual('function') - expect(typeof config.url).toEqual('function') - expect(typeof config.logo).toEqual('function') - expect(config.request.data()).toEqual({ accountID: '123' }) - expect(config.url()).toEqual('http://example.com/20210319/1tv.json') - expect(config.logo()).toEqual('http://example.com/logos/1TV.png?x=ัˆะตะปะปั‹&sid=777') -}) - -it('can parse valid channels.xml', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - expect(channels).toEqual([ - { - name: '1 TV', - xmltv_id: '1TV.com', - site_id: '1', - site: 'example.com', - lang: 'fr', - logo: 'https://example.com/logos/1TV.png' - }, - { - name: '2 TV', - xmltv_id: '2TV.com', - site_id: '2', - site: 'example.com', - lang: undefined, - logo: undefined - } - ]) -}) - -it('can convert object to xmltv string', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - sub_title: 'Sub-title & 1', - description: 'Description for Program 1', - url: 'http://example.com/title.html', - start: 1616133600, - stop: 1616135400, - category: 'Test', - season: 9, - episode: 239, - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it', - date: '20220505', - director: { - value: 'Director 1', - url: { value: 'http://example.com/director1.html', system: 'TestSystem' } - }, - actor: ['Actor 1', 'Actor 2'], - writer: 'Writer 1' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Sub-title & 1Description for Program 1Testhttp://example.com/title.html8.238.0/1S09E23920220505Director 1http://example.com/director1.htmlActor 1Actor 2Writer 1\r\n' - ) -}) - -it('can convert object to xmltv string without season number', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - description: 'Description for Program 1', - start: 1616133600, - stop: 1616135400, - category: 'Test', - episode: 239, - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test0.238.0/1S01E239\r\n' - ) -}) - -it('can convert object to xmltv string without episode number', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - description: 'Description for Program 1', - start: 1616133600, - stop: 1616135400, - category: 'Test', - season: 1, - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test\r\n' - ) -}) - -it('can convert object to xmltv string without categories', () => { - const channels = [ - { - name: '1 TV', - xmltv_id: '1TV.com', - site_id: '1', - site: 'example.com', - lang: 'fr', - logo: 'https://example.com/logos/1TV.png' - } - ] - const programs = [ - { - title: 'Program 1', - start: 1616133600, - stop: 1616135400, - channel: '1TV.com', - lang: 'it' - } - ] - const config = { site: 'example.com' } - const output = utils.convertToXMLTV({ config, channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\nProgram 1\r\n' - ) -}) - -it('can convert object to xmltv string with multiple categories', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - description: 'Description for Program 1', - start: 1616133600, - stop: 1616135400, - category: ['Test1', 'Test2'], - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2\r\n' - ) -}) - -it('can convert object to xmltv string with multiple urls', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - description: 'Description for Program 1', - start: 1616133600, - stop: 1616135400, - category: ['Test1', 'Test2'], - url: [ - 'https://example.com/noattr.html', - { value: 'https://example.com/attr.html', system: 'TestSystem' } - ], - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2https://example.com/noattr.htmlhttps://example.com/attr.html\r\n' - ) -}) - -it('can convert object to xmltv string with multiple images', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - description: 'Description for Program 1', - start: 1616133600, - stop: 1616135400, - category: ['Test1', 'Test2'], - url: [ - 'https://example.com/noattr.html', - { value: 'https://example.com/attr.html', system: 'TestSystem' } - ], - actor: { - value: 'Actor 1', - image: [ - 'https://example.com/image1.jpg', - { - value: 'https://example.com/image2.jpg', - type: 'person', - size: '2', - system: 'TestSystem', - orient: 'P' - } - ] - }, - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it' - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2https://example.com/noattr.htmlhttps://example.com/attr.htmlActor 1https://example.com/image1.jpghttps://example.com/image2.jpg\r\n' - ) -}) - -it('can convert object to xmltv string with multiple credits member', () => { - const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' }) - const { channels } = utils.parseChannels(file) - const programs = [ - { - title: 'Program 1', - sub_title: 'Sub-title 1', - description: 'Description for Program 1', - url: 'http://example.com/title.html', - start: 1616133600, - stop: 1616135400, - category: 'Test', - season: 9, - episode: 239, - icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', - channel: '1TV.com', - lang: 'it', - date: '20220505', - director: { - value: 'Director 1', - url: { value: 'http://example.com/director1.html', system: 'TestSystem' } - }, - actor: { - value: 'Actor 1', - role: 'Manny', - guest: 'yes', - url: { value: 'http://example.com/actor1.html', system: 'TestSystem' } - }, - writer: [ - { value: 'Writer 1', url: { value: 'http://example.com/w1.html', system: 'TestSystem' } }, - { value: 'Writer 2', url: { value: 'http://example.com/w2.html', system: 'TestSystem' } } - ] - } - ] - const output = utils.convertToXMLTV({ channels, programs }) - expect(output).toBe( - '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Sub-title 1Description for Program 1Testhttp://example.com/title.html8.238.0/1S09E23920220505Director 1http://example.com/director1.htmlActor 1http://example.com/actor1.htmlWriter 1http://example.com/w1.htmlWriter 2http://example.com/w2.html\r\n' - ) -}) +import { escapeString } from '../src/utils' it('can escape string', () => { const string = 'Mรบsica ั‚ะตัั‚ dun. &<>"\'\r\n' - expect(utils.escapeString(string)).toBe('Mรบsica ั‚ะตัั‚ dun. &<>"'') + expect(escapeString(string)).toBe('Mรบsica ั‚ะตัั‚ dun. &<>"'') }) it('can escape url', () => { const string = 'http://example.com/logos/1TV.png?param1=val¶m2=val' - expect(utils.escapeString(string)).toBe( - 'http://example.com/logos/1TV.png?param1=val&param2=val' - ) -}) - -it('can fetch data', () => { - const request = { - data: { accountID: '123' }, - headers: { - 'Content-Type': 'application/json', - Cookie: 'abc=123', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' - }, - maxContentLength: 5242880, - method: 'POST', - responseType: 'arraybuffer', - timeout: 5000, - url: 'http://example.com/20210319/1tv.json', - withCredentials: true - } - utils.fetchData(mockAxios, request).then(jest.fn).catch(jest.fn) - expect(mockAxios).toHaveBeenCalledWith( - expect.objectContaining({ - data: { accountID: '123' }, - headers: { - 'Content-Type': 'application/json', - Cookie: 'abc=123', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' - }, - method: 'POST', - responseType: 'arraybuffer', - timeout: 5000, - url: 'http://example.com/20210319/1tv.json', - withCredentials: true - }) - ) -}) - -it('can build request async', done => { - const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js'))) - - utils - .buildRequest({}, config) - .then(request => { - expect(request).toMatchObject({ - data: { accountID: '123' }, - headers: { - 'Content-Type': 'application/json', - Cookie: 'abc=123', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71' - }, - maxContentLength: 5242880, - method: 'POST', - responseType: 'arraybuffer', - timeout: 5000, - url: 'http://example.com/20210319/1tv.json', - withCredentials: true - }) - done() - }) - .catch(done) -}) - -it('can load logo async', done => { - const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js'))) - - utils - .loadLogo({}, config) - .then(logo => { - expect(logo).toBe('http://example.com/logos/1TV.png?x=ัˆะตะปะปั‹&sid=777') - done() - }) - .catch(done) -}) - -it('can parse programs', done => { - const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js'))) - - utils - .parsePrograms({ channel: { xmltv_id: '1tv', lang: 'en' } }, config) - .then(programs => { - expect(programs).toMatchObject([ - { - title: 'Title', - description: 'Description', - lang: 'en', - category: ['Category1', 'Category2'], - icon: 'https://example.com/image.jpg', - season: 9, - episode: 238, - start: 1640995200, - stop: 1640998800 - } - ]) - done() - }) - .catch(done) -}) - -it('can parse programs async', done => { - const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js'))) - - utils - .parsePrograms({ channel: { xmltv_id: '1tv', lang: 'en' } }, config) - .then(programs => { - expect(programs.length).toBe(0) - done() - }) - .catch(done) + expect(escapeString(string)).toBe('http://example.com/logos/1TV.png?param1=val&param2=val') }) diff --git a/tests/xmltv.test.js b/tests/xmltv.test.js new file mode 100644 index 0000000..a1d70a8 --- /dev/null +++ b/tests/xmltv.test.js @@ -0,0 +1,220 @@ +import xmltv from '../src/xmltv' + +jest.useFakeTimers('modern').setSystemTime(new Date('2022-05-05')) + +const channels = [ + { + xmltv_id: '1TV.com', + name: '1 TV', + logo: 'https://example.com/logos/1TV.png', + site: 'example.com' + }, + { + xmltv_id: '2TV.com', + name: '2 TV', + site: 'example.com' + } +] + +it('can generate xmltv', () => { + const programs = [ + { + title: 'Program 1', + sub_title: 'Sub-title & 1', + description: 'Description for Program 1', + url: 'http://example.com/title.html', + start: 1616133600, + stop: 1616135400, + category: 'Test', + season: 9, + episode: 239, + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it', + date: '20220505', + director: { + value: 'Director 1', + url: { value: 'http://example.com/director1.html', system: 'TestSystem' } + }, + actor: ['Actor 1', 'Actor 2'], + writer: 'Writer 1' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Sub-title & 1Description for Program 1Testhttp://example.com/title.html8.238.0/1S09E23920220505Director 1http://example.com/director1.htmlActor 1Actor 2Writer 1\r\n' + ) +}) + +it('can generate xmltv without season number', () => { + const programs = [ + { + title: 'Program 1', + description: 'Description for Program 1', + start: 1616133600, + stop: 1616135400, + category: 'Test', + episode: 239, + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test0.238.0/1S01E239\r\n' + ) +}) + +it('can generate xmltv without episode number', () => { + const programs = [ + { + title: 'Program 1', + description: 'Description for Program 1', + start: 1616133600, + stop: 1616135400, + category: 'Test', + season: 1, + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test\r\n' + ) +}) + +it('can generate xmltv without categories', () => { + const programs = [ + { + title: 'Program 1', + start: 1616133600, + stop: 1616135400, + channel: '1TV.com', + lang: 'it' + } + ] + const config = { site: 'example.com' } + const output = xmltv.generate({ config, channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1\r\n' + ) +}) + +it('can generate xmltv with multiple categories', () => { + const programs = [ + { + title: 'Program 1', + description: 'Description for Program 1', + start: 1616133600, + stop: 1616135400, + category: ['Test1', 'Test2'], + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2\r\n' + ) +}) + +it('can generate xmltv with multiple urls', () => { + const programs = [ + { + title: 'Program 1', + description: 'Description for Program 1', + start: 1616133600, + stop: 1616135400, + category: ['Test1', 'Test2'], + url: [ + 'https://example.com/noattr.html', + { value: 'https://example.com/attr.html', system: 'TestSystem' } + ], + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2https://example.com/noattr.htmlhttps://example.com/attr.html\r\n' + ) +}) + +it('can generate xmltv with multiple images', () => { + const programs = [ + { + title: 'Program 1', + description: 'Description for Program 1', + start: 1616133600, + stop: 1616135400, + category: ['Test1', 'Test2'], + url: [ + 'https://example.com/noattr.html', + { value: 'https://example.com/attr.html', system: 'TestSystem' } + ], + actor: { + value: 'Actor 1', + image: [ + 'https://example.com/image1.jpg', + { + value: 'https://example.com/image2.jpg', + type: 'person', + size: '2', + system: 'TestSystem', + orient: 'P' + } + ] + }, + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it' + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Description for Program 1Test1Test2https://example.com/noattr.htmlhttps://example.com/attr.htmlActor 1https://example.com/image1.jpghttps://example.com/image2.jpg\r\n' + ) +}) + +it('can generate xmltv with multiple credits member', () => { + const programs = [ + { + title: 'Program 1', + sub_title: 'Sub-title 1', + description: 'Description for Program 1', + url: 'http://example.com/title.html', + start: 1616133600, + stop: 1616135400, + category: 'Test', + season: 9, + episode: 239, + icon: 'https://example.com/images/Program1.png?x=ัˆะตะปะปั‹&sid=777', + channel: '1TV.com', + lang: 'it', + date: '20220505', + director: { + value: 'Director 1', + url: { value: 'http://example.com/director1.html', system: 'TestSystem' } + }, + actor: { + value: 'Actor 1', + role: 'Manny', + guest: 'yes', + url: { value: 'http://example.com/actor1.html', system: 'TestSystem' } + }, + writer: [ + { value: 'Writer 1', url: { value: 'http://example.com/w1.html', system: 'TestSystem' } }, + { value: 'Writer 2', url: { value: 'http://example.com/w2.html', system: 'TestSystem' } } + ] + } + ] + const output = xmltv.generate({ channels, programs }) + expect(output).toBe( + '\r\n1 TVhttps://example.com\r\n2 TVhttps://example.com\r\nProgram 1Sub-title 1Description for Program 1Testhttp://example.com/title.html8.238.0/1S09E23920220505Director 1http://example.com/director1.htmlActor 1http://example.com/actor1.htmlWriter 1http://example.com/w1.htmlWriter 2http://example.com/w2.html\r\n' + ) +})