Updated API

Reworked what data are needed to scrape a game, now the API support the scraping of the user data, especially the watched games threads
pull/4/head
MillenniumEarl 2020-09-29 23:38:08 +02:00
parent 37454a01ee
commit 331dc930b1
6 changed files with 280 additions and 192 deletions

View File

@ -1,6 +1,7 @@
'use strict';
const path = require('path');
const fs = require('fs');
const UserData = require('./scripts/user-data').UserData;
const LoginResult = require('./scripts/login-result').LoginResult;
const GameInfo = require('./scripts/game-info').GameInfo;
const GameDownload = require('./scripts/game-download').GameDownload;
@ -12,7 +13,8 @@ const urlExist = require('url-exist');
const F95_BASE_URL = 'https://f95zone.to';
const F95_SEARCH_URL = 'https://f95zone.to/search';
const F95_LATEST_UPDATES = 'https://f95zone.to/latest';
const F95_LOGIN_URL = 'https://f95zone.to/login'
const F95_LOGIN_URL = 'https://f95zone.to/login';
const F95_WATCHED_THREADS = 'https://f95zone.to/watched/threads';
//#endregion
//#region CSS Selectors
@ -30,6 +32,16 @@ const THREAD_POSTS = 'article.message-body:first-child > div.bbWrapper:first-of-
const GAME_TITLE = 'h1.p-title-value';
const GAME_IMAGES = 'img[src^="https://attachments.f95zone.to"]';
const LOGIN_MESSAGE_ERROR = 'div.blockMessage.blockMessage--error.blockMessage--iconic';
const GAME_TAGS = 'a.tagItem';
const USERNAME_ELEMENT = 'a[href="/account/"] > span.p-navgroup-linkText';
const AVATAR_PIC = 'a[href="/account/"] > span.avatar > img[class^="avatar"]';
const UNREAD_THREAD_CHECKBOX = 'input[type="checkbox"][name="unread"]';
const ONLY_GAMES_THREAD_OPTION = 'select[name="nodes[]"] > option[value="2"]';
const FILTER_THREADS_BUTTON = 'button[class="button--primary button"]';
const GAME_TITLE_PREFIXES = 'h1.p-title-value > a.labelLink > span[dir="auto"]';
const WATCHED_THREAD_URLS = 'a[href^="/threads/"][data-tp-primary]';
const WATCHED_THREAD_NEXT_PAGE = 'a.pageNav-jump--next';
const WATCHED_THREAD_FILTER_POPUP_BUTTON = 'a.filterBar-menuTrigger';
//#endregion CSS Selectors
//#region Game prefixes
@ -83,16 +95,14 @@ let _debug = false;
*
* @param {Boolean} value
*/
module.exports.debug = function(value){
module.exports.debug = function (value) {
_debug = value;
}
module.exports.isLogged = function() {
return _isLogged;
module.exports.isLogged = function () {
return _isLogged;
};
//#endregion Properties
//#region Export methods
/**
* @public
@ -103,8 +113,8 @@ module.exports.isLogged = function() {
* @returns {Promise<LoginResult>} Result of the operation
*/
module.exports.login = async function (username, password) {
if(_isLogged){
if(_debug) console.log("Already logged in");
if (_isLogged) {
if (_debug) console.log("Already logged in");
let result = new LoginResult();
result.success = true;
result.message = 'Already logged in';
@ -150,7 +160,7 @@ module.exports.loadF95BaseData = async function () {
return false;
}
if(_debug) console.log('Loading base data...');
if (_debug) console.log('Loading base data...');
// Prepare a new web page
let browser = await prepareBrowser();
@ -199,7 +209,8 @@ module.exports.getGameVersion = async function (info) {
* You **must** be logged in to the portal before calling this method.
* @param {String} name Name of the game searched
* @param {Boolean} includeMods Indicates whether to also take mods into account when searching
* @returns {Promise<GameInfo>} Information about the game searched or null if no game were found
* @returns {Promise<GameInfo[]>} List of information obtained where each item corresponds to
* an identified game (in the case of homonymy). If no games were found, null is returned
*/
module.exports.getGameData = async function (name, includeMods) {
if (!_isLogged) {
@ -209,19 +220,21 @@ module.exports.getGameData = async function (name, includeMods) {
// Gets the search results of the game being searched for
let browser = await prepareBrowser();
let infoList = await getSearchGameResults(browser, name, _cookies);
let urlList = await getSearchGameResults(browser, name, _cookies);
// Process previous partial results
let result = null;
for (let info of infoList) {
let promiseList = [];
for (let url of urlList) {
// Start looking for information
promiseList.push(getGameInfo(browser, url));
}
// Filter for mods
let result = [];
for (let info of await Promise.all(promiseList)) {
// Skip mods if not required
if (info.isMod && !includeMods) continue;
// TODO
// What if there are more games with the same name?
// For the moment, return the first
result = await getGameInfo(browser, info);
break;
else result.push(info);
}
await browser.close();
@ -233,7 +246,7 @@ module.exports.getGameData = async function (name, includeMods) {
* @param {*} platform
* @param {*} url
*/
module.exports.getDownloadLink = async function(platform, url){
module.exports.getDownloadLink = async function (platform, url) {
if (!_isLogged) {
console.warn('user not authenticated, unable to continue');
return null;
@ -246,11 +259,44 @@ module.exports.getDownloadLink = async function(platform, url){
}
/**
* @public
* Properly closes all instances opened by the API.
* @returns {Promise<void>}
* Gets the data of the currently logged in user.
* @returns {Promise<UserData>} Data of the user currently logged in or null if an error arise
*/
module.exports.close = async function() {
if(_debug) console.log("Closing F95API")
module.exports.getUserData = async function () {
if (!_isLogged) {
console.warn('user not authenticated, unable to continue');
return null;
}
// Prepare a new web page
let browser = await prepareBrowser();
let page = await preparePage(browser); // Set new isolated page
await page.setCookie(..._cookies); // Set cookies to avoid login
await page.goto(F95_BASE_URL); // Go to base page
// Explicitly wait for the required items to load
await page.waitForSelector(USERNAME_ELEMENT);
await page.waitForSelector(AVATAR_PIC);
let threads = getUserWatchedGameThreads(browser);
let username = await page.evaluate((selector) =>
document.querySelector(selector).innerText,
USERNAME_ELEMENT);
let avatarSrc = await page.evaluate((selector) =>
document.querySelector(selector).getAttribute('src'),
AVATAR_PIC);
let ud = new UserData();
ud.username = username;
ud.avatarSrc = isStringAValidURL(avatarSrc) ? new URL(avatarSrc) : null;
ud.watchedThreads = await threads;
await page.close();
await browser.close();
return ud;
}
//#endregion
@ -266,7 +312,7 @@ module.exports.close = async function() {
async function prepareBrowser() {
// Create a headless browser
let browser = await puppeteer.launch({
headless: true,
headless: false,
});
return browser;
@ -424,7 +470,7 @@ function isF95URL(url) {
* @param {String} url String to check for correctness
* @returns {Boolean} true if the string is a valid URL, false otherwise
*/
function isStringAValidURL(url){
function isStringAValidURL(url) {
try {
new URL(url);
return true;
@ -479,25 +525,68 @@ async function loginF95(browser, username, password) {
if (errorMessage === 'Incorrect password. Please try again.') result.message = 'Incorrect password';
else if (errorMessage === "The requested user '" + username + "' could not be found.") result.message = 'Incorrect username';
else result.message = errorMessage;
}
else result.message = "Unknown error";
} else result.message = "Unknown error";
await page.close(); // Close the page
return result;
}
/**
* @deprecated
* @private
* Gets the list of URLs of threads the user follows.
* @param {puppeteer.Browser} browser Browser object used for navigation
* @returns {Promise<URL[]>} URL list
*/
async function getUserData(){
async function getUserWatchedGameThreads(browser) {
let page = await preparePage(browser); // Set new isolated page
await page.goto(F95_WATCHED_THREADS); // Go to the thread page
}
// Explicitly wait for the required items to load
await page.waitForSelector(WATCHED_THREAD_FILTER_POPUP_BUTTON);
/**
* @deprecated
*/
async function getUserWatchedGameThreads(){
// Show the popup
await page.click(WATCHED_THREAD_FILTER_POPUP_BUTTON);
await page.waitForSelector(UNREAD_THREAD_CHECKBOX);
await page.waitForSelector(ONLY_GAMES_THREAD_OPTION);
await page.waitForSelector(FILTER_THREADS_BUTTON);
// Set the filters
await page.evaluate((selector) =>
document.querySelector(selector).removeAttribute('checked'),
UNREAD_THREAD_CHECKBOX); // Also read the threads already read
await page.click(ONLY_GAMES_THREAD_OPTION);
// Filter the threads
await page.click(FILTER_THREADS_BUTTON);
await page.waitForSelector(WATCHED_THREAD_URLS);
// Get the threads urls
let urls = [];
let nextPageExists = false;
do {
// Get all the URLs
for (let handle of await page.$$(WATCHED_THREAD_URLS)) {
let src = await page.evaluate((element) => element.href, handle);
// If 'unread' is left, it will redirect to the last unread post
let url = new URL(src.replace('/unread', ''));
urls.push(url);
}
nextPageExists = await page.evaluate((selector) =>
document.querySelector(selector),
WATCHED_THREAD_NEXT_PAGE);
// Click to next page
if (nextPageExists) {
await page.click(WATCHED_THREAD_NEXT_PAGE);
await page.waitForSelector(WATCHED_THREAD_URLS);
}
}
while (nextPageExists);
await page.close();
return urls;
}
//#endregion User
@ -506,33 +595,42 @@ async function getUserWatchedGameThreads(){
* @private
* Get information from the game's main page.
* @param {puppeteer.Browser} browser Browser object used for navigation
* @param {GameInfo} info Partial game information
* @param {URL} url URL of the game/mod to extract data from
* @return {Promise<GameInfo>} Complete information about the game you are looking for
*/
async function getGameInfo(browser, info) {
if(_debug) console.log('Obtaining game info');
async function getGameInfo(browser, url) {
if (_debug) console.log('Obtaining game info');
// Verify the correctness of the URL
if (!isF95URL(info.f95url)) throw info.f95url + ' is not a valid F95Zone URL';
let exists = await urlExist(info.f95url.toString());
if (!isF95URL(url)) throw url + ' is not a valid F95Zone URL';
let exists = await urlExist(url.toString());
if (!exists) return new GameInfo();
let page = await preparePage(browser); // Set new isolated page
await page.setCookie(..._cookies); // Set cookies to avoid login
await page.goto(info.f95url.toString(), {
await page.goto(url.toString(), {
waitUntil: WAIT_STATEMENT
}); // Go to the game page and wait until it loads
// Object to fill with information
let info = new GameInfo();
// Get the game/mod name (without square brackets)
let title = getGameTitle(page);
// Get the game/mod author (without square brackets)
let author = getGameAuthor(page);
// Get the game tags
let tags = getGameTags(page);
// Get the game title image (the first is what we are searching)
let previewSource = await getGamePreviewSource(page);
if (previewSource === null) console.warn('Cannot find game preview image for ' + await title);
// Parse the prefixes
info = await parsePrefixes(page, info); // Fill status/engines/isMod
// Gets the first post, where are listed all the game's informations
let post = (await page.$$(THREAD_POSTS))[0];
@ -553,12 +651,14 @@ async function getGameInfo(browser, info) {
info.name = await title;
info.author = await author;
info.overview = overview;
info.tags = await tags;
info.f95url = url;
info.version = info.isMod ? parsedInfos['MOD VERSION'] : parsedInfos['VERSION'];
info.lastUpdate = info.isMod ? parsedInfos['UPDATED'] : parsedInfos['THREAD UPDATED'];
info.previewSource = previewSource;
await page.close(); // Close the page
if(_debug) console.log('Founded data for ' + await title);
if (_debug) console.log('Founded data for ' + info.name);
return info;
}
@ -640,11 +740,55 @@ async function getGameTitle(page) {
const structuredTitle = HTMLParser.parse(titleHTML);
// The last element **shoud be** the title without prefixes (engines, status, other...)
var gameTitle = structuredTitle.childNodes.pop().rawText;
let gameTitle = structuredTitle.childNodes.pop().rawText;
const endTitleIndex = gameTitle.indexOf('[');
return gameTitle.substring(0, endTitleIndex).trim();
}
/**
* @private
* Get the list of tags associated with the game.
* @param {puppeteer.Page} page Page containing the tags to be extrapolated
* @returns {Promise<String[]>} List of uppercase tags
*/
async function getGameTags(page) {
let tags = [];
// Get the game tags
for (let handle of await page.$$(GAME_TAGS)) {
let tag = await page.evaluate((element) => element.innerText, handle);
tags.push(tag.toUpperCase());
}
return tags;
}
/**
* @private
* Process the game title prefixes to extract information such as game status,
* graphics engine used, and whether it is a mod or original game.
* @param {puppeteer.Page} page Page containing the prefixes to be extrapolated
* @param {GameInfo} info Object to assign the identified information to
* @returns {Promise<GameInfo>} GameInfo object passed in to which the identified information has been added
*/
async function parsePrefixes(page, info) {
// The 'Ongoing' status is not specified, only 'Abandoned'/'OnHold'/'Complete'
info.status = 'Ongoing';
for (let handle of await page.$$(GAME_TITLE_PREFIXES)) {
let value = await page.evaluate((element) => element.innerText, handle);
// Clean the prefix
let prefix = value.toUpperCase().replace('[', '').replace(']', '').trim();
// Getting infos...
if (_statuses.includes(prefix)) info.status = prefix;
else if (_engines.includes(prefix)) info.engine = prefix;
// This is not a game but a mod
else if (prefix === MOD_PREFIX) info.isMod = true;
}
return info;
}
/**
* @deprecated
* @param {puppeteer.Browser} browser
@ -680,7 +824,7 @@ async function getGameDownloadLink(browser, url) {
* Search the F95Zone portal to find possible conversations regarding the game you are looking for.
* @param {puppeteer.Browser} browser Browser object used for navigation
* @param {String} gamename Name of the game to search for
* @returns {Promise<GameInfo[]>} List of information obtained from the preliminary research on the F95 portal
* @returns {Promise<URL[]>} List of URL of possible games obtained from the preliminary research on the F95 portal
*/
async function getSearchGameResults(browser, gamename) {
if (_debug) console.log('Searching ' + gamename + ' on F95Zone');
@ -690,10 +834,12 @@ async function getSearchGameResults(browser, gamename) {
await page.goto(F95_SEARCH_URL, {
waitUntil: WAIT_STATEMENT
}); // Go to the search form and wait for it
// Explicitly wait for the required items to load
await page.waitForSelector(SEARCH_FORM_TEXTBOX);
await page.waitForSelector(TITLE_ONLY_CHECKBOX);
await page.waitForSelector(SEARCH_BUTTON);
await page.type(SEARCH_FORM_TEXTBOX, gamename) // Type the game we desire
await page.click(TITLE_ONLY_CHECKBOX) // Select only the thread with the game in the titles
await Promise.all([
@ -707,13 +853,13 @@ async function getSearchGameResults(browser, gamename) {
let threadTitleList = await page.$$(THREAD_TITLE);
// For each title extract the info about the conversation
if(_debug) console.log('Extracting info from conversation titles');
if (_debug) console.log('Extracting info from conversation titles');
let results = [];
for (const title of threadTitleList) {
var info = await getSearchThreadInfo(page, title);
for (let title of threadTitleList) {
let gameUrl = await getOnlyGameThreads(page, title);
// Append the game's informations
if (info !== null) results.push(info);
if (gameUrl !== null) results.push(gameUrl);
}
if (_debug) console.log('Find ' + results.length + ' conversations');
await page.close(); // Close the page
@ -723,42 +869,26 @@ async function getSearchGameResults(browser, gamename) {
/**
* @private
* Starting from the title of a conversation on the F95 portal,
* he obtains basic information about the element (in case it is a game)
* Return the link of a conversation if it is a game or a mod
* @param {puppeteer.Page} page Page containing the conversation to be analyzed
* @param {puppeteer.ElementHandle} titleHandle Title of the conversation to be analyzed
* @return {Promise<GameInfo>} Object containing the information obtained from the analysis or null if it is not a game conversation
* @return {Promise<URL>} URL of the game/mod
*/
async function getSearchThreadInfo(page, titleHandle) {
let info = new GameInfo();
async function getOnlyGameThreads(page, titleHandle) {
// Get the URL of the thread from the title
let relativeURLThread = await page.evaluate((element) => element.querySelector('a').href, titleHandle);
info.f95url = new URL(relativeURLThread, F95_BASE_URL);
let url = new URL(relativeURLThread, F95_BASE_URL);
// Select infos like the engine used for the game or it's status
// parsing the title
let elements = await titleHandle.$$('span[dir="auto"]');
// The 'Ongoing' status is not specified, only 'Abandoned'/'OnHold'/'Complete' are
info.status = 'Ongoing';
for (let element of elements) {
// Parse prefixes to ignore game recommendation
for (let element of await titleHandle.$$('span[dir="auto"]')) {
// Elaborate the prefixes
let prefix = await page.evaluate(element => element.textContent.toUpperCase(), element);
prefix = prefix.replace('[', '').replace(']', '');
// This is not a game nor a mod, we can exit
if (prefix === GAME_RECOMMENDATION_PREFIX) return null;
// Getting infos...
else if (_statuses.includes(prefix)) info.status = prefix;
else if (_engines.includes(prefix)) info.engine = prefix;
// This is probably a mod for the game we are searching
else if (prefix === MOD_PREFIX) info.isMod = true;
}
return info;
return url;
}
//#endregion Game search

View File

@ -1,17 +1,17 @@
const UNKNOWN = 'Unknown';
class GameInfo {
constructor() {
this.UNKNOWN = 'Unknown';
/**
* Game name
* @type String
*/
this.name = this.UNKNOWN;
this.name = UNKNOWN;
/**
* Game author
* @type String
*/
this.author = this.UNKNOWN;
this.author = UNKNOWN;
/**
* URL to the game's official conversation on the F95Zone portal
* @type URL
@ -21,17 +21,22 @@ class GameInfo {
* Game description
* @type String
*/
this.overview = this.UNKNOWN;
this.overview = UNKNOWN;
/**
* List of tags associated with the game
* @type String[]
*/
this.tags = [];
/**
* Graphics engine used for game development
* @type String
*/
this.engine = this.UNKNOWN;
this.engine = UNKNOWN;
/**
* Progress of the game
* @type String
*/
this.status = this.UNKNOWN;
this.status = UNKNOWN;
/**
* Game description image URL
* @type URL
@ -41,17 +46,17 @@ class GameInfo {
* Game version
* @type String
*/
this.version = this.UNKNOWN;
this.version = UNKNOWN;
/**
* Last time the game underwent updates
* @type String
*/
this.lastUpdate = this.UNKNOWN;
this.lastUpdate = UNKNOWN;
/**
* Last time the local copy of the game was run
* @type String
*/
this.lastPlayed = this.UNKNOWN;
this.lastPlayed = UNKNOWN;
/**
* Specifies if the game is original or a mod
* @type Boolean
@ -61,7 +66,7 @@ class GameInfo {
* Directory containing the local copy of the game
* @type String
*/
this.gameDir = this.UNKNOWN;
this.gameDir = UNKNOWN;
}
/**

View File

@ -1,6 +1,24 @@
/**
* Class containing the data of the user currently connected to the F95Zone platform.
*/
class UserData {
constructor(){
this.username = "";
this.avatarSrc = null;
}
constructor() {
/**
* User username.
* @type String
*/
this.username = "";
/**
* Path to the user 's profile picture.
* @type URL
*/
this.avatarSrc = null;
/**
* List of followed thread URLs.
* @type URL[]
*/
this.watchedThreads = [];
}
}
module.exports.UserData = UserData;

View File

@ -1,4 +1,4 @@
const { debug, login, close, getGameData, loadF95BaseData } = require("../app/index");
const { debug, login, getGameData, loadF95BaseData, getUserData } = require("../app/index");
debug(true);
main();
@ -7,9 +7,10 @@ async function main() {
let loginResult = await login("MillenniumEarl", "f9vTcRNuvxj4YpK");
if (loginResult.success) {
await loadF95BaseData();
let data = await getGameData("kingdom of deception", false);
// await loadF95BaseData();
// let data = await getGameData("kingdom of deception", false);
// console.log(data.pop());
let data = await getUserData();
console.log(data);
}
await close();
}

125
package-lock.json generated
View File

@ -251,14 +251,12 @@
"version": "14.11.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
"integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==",
"dev": true,
"optional": true
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
@ -268,7 +266,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dev": true,
"requires": {
"event-target-shim": "^5.0.0"
}
@ -276,8 +273,7 @@
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
"dev": true
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g=="
},
"aggregate-error": {
"version": "3.1.0",
@ -365,14 +361,12 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
"dev": true
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"binary-extensions": {
"version": "2.1.0",
@ -384,7 +378,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
"dev": true,
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@ -395,7 +388,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -420,7 +412,6 @@
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"dev": true,
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
@ -429,8 +420,7 @@
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
"dev": true
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"caching-transform": {
"version": "4.0.0",
@ -500,8 +490,7 @@
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"clean-stack": {
"version": "2.2.0",
@ -544,8 +533,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"convert-source-map": {
"version": "1.7.0",
@ -571,7 +559,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
@ -612,8 +599,7 @@
"devtools-protocol": {
"version": "0.0.799653",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.799653.tgz",
"integrity": "sha512-t1CcaZbvm8pOlikqrsIM9GOa7Ipp07+4h/q9u0JXBWjPCjHdBl9KkddX87Vv9vBHoBGtwV79sYQNGnQM6iS5gg==",
"dev": true
"integrity": "sha512-t1CcaZbvm8pOlikqrsIM9GOa7Ipp07+4h/q9u0JXBWjPCjHdBl9KkddX87Vv9vBHoBGtwV79sYQNGnQM6iS5gg=="
},
"diff": {
"version": "4.0.2",
@ -631,7 +617,6 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
}
@ -708,14 +693,12 @@
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"dev": true
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"requires": {
"@types/yauzl": "^2.9.1",
"debug": "^4.1.1",
@ -727,7 +710,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
@ -756,7 +738,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@ -790,14 +771,12 @@
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "2.1.3",
@ -840,7 +819,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
@ -849,7 +827,6 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -920,8 +897,7 @@
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"html-escaper": {
"version": "2.0.2",
@ -933,7 +909,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
"dev": true,
"requires": {
"agent-base": "5",
"debug": "4"
@ -942,8 +917,7 @@
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
"dev": true
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"imurmurhash": {
"version": "0.1.4",
@ -961,7 +935,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -970,14 +943,12 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ip-regex": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz",
"integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A==",
"dev": true
"integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A=="
},
"is-arguments": {
"version": "1.0.4",
@ -1097,7 +1068,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-3.0.0.tgz",
"integrity": "sha512-3faQP+wHCGDQT1qReM5zCPx2mxoal6DzbzquFlCYJLWyy4WPTved33ea2xFbX37z4NoriEwZGIYhFtx8RUB5wQ==",
"dev": true,
"requires": {
"url-regex": "^5.0.0"
}
@ -1269,14 +1239,12 @@
"ky": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/ky/-/ky-0.19.1.tgz",
"integrity": "sha512-ZwciYrfaWpDI72U2HAruuGYGFW3PCfGNdWWSANGGssg9BGm4rRJ9s/sApiiRpj+8Y245/hlZW9c60zudLr6iwA==",
"dev": true
"integrity": "sha512-ZwciYrfaWpDI72U2HAruuGYGFW3PCfGNdWWSANGGssg9BGm4rRJ9s/sApiiRpj+8Y245/hlZW9c60zudLr6iwA=="
},
"ky-universal": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.5.0.tgz",
"integrity": "sha512-O+0wjCua5i45lYBZrBy8TyRDRVodtsmzVC/MlE5FN7ZMFu/Icz7ekbZ85sdFw0F/JwGhXZTaKjXq9IgUGwGedQ==",
"dev": true,
"requires": {
"abort-controller": "^3.0.0",
"node-fetch": "^2.6.0"
@ -1286,7 +1254,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
@ -1377,7 +1344,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -1391,8 +1357,7 @@
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"mocha": {
"version": "8.1.3",
@ -1632,20 +1597,17 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-html-parser": {
"version": "1.2.21",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.21.tgz",
"integrity": "sha512-6vDhgen6J332syN5HUmeT4FfBG7m6bFRrPN+FXY8Am7FGuVpsIxTASVbeoO5PF2IHbX2s+WEIudb1hgxOjllNQ==",
"dev": true,
"requires": {
"he": "1.2.0"
}
@ -1728,7 +1690,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -1737,7 +1698,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
@ -1746,7 +1706,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
@ -1763,8 +1722,7 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"package-hash": {
"version": "4.0.0",
@ -1781,14 +1739,12 @@
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-key": {
"version": "3.1.1",
@ -1811,8 +1767,7 @@
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
},
"picomatch": {
"version": "2.2.2",
@ -1824,7 +1779,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"requires": {
"find-up": "^4.0.0"
}
@ -1841,8 +1795,7 @@
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
},
"promise.allsettled": {
"version": "1.0.2",
@ -1860,14 +1813,12 @@
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@ -1877,7 +1828,6 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.3.1.tgz",
"integrity": "sha512-YTM1RaBeYrj6n7IlRXRYLqJHF+GM7tasbvrNFx6w1S16G76NrPq7oYFKLDO+BQsXNtS8kW2GxWCXjIMPvfDyaQ==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.799653",
@ -1905,7 +1855,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -1961,7 +1910,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
@ -2075,7 +2023,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
@ -2083,8 +2030,7 @@
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
@ -2122,7 +2068,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
"dev": true,
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
@ -2134,7 +2079,6 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz",
"integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==",
"dev": true,
"requires": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
@ -2157,14 +2101,12 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"tlds": {
"version": "1.210.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.210.0.tgz",
"integrity": "sha512-5bzt4JE+NlnwiKpVW9yzWxuc44m+t2opmPG+eSKDp5V5qdyGvjMngKgBb5ZK8GiheQMbRTCKpRwFJeIEO6pV7Q==",
"dev": true
"integrity": "sha512-5bzt4JE+NlnwiKpVW9yzWxuc44m+t2opmPG+eSKDp5V5qdyGvjMngKgBb5ZK8GiheQMbRTCKpRwFJeIEO6pV7Q=="
},
"to-fast-properties": {
"version": "2.0.0",
@ -2206,7 +2148,6 @@
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"dev": true,
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
@ -2216,7 +2157,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/url-exist/-/url-exist-2.0.2.tgz",
"integrity": "sha512-JqLjYS8pU9xZtY3ro4c54CztoP5R8qRyMlg2Cxr4M9YD1NCe57MOsZHF1rP3y+qQcc7cqiZBBd4Cu5oehcJRlQ==",
"dev": true,
"requires": {
"is-url-superb": "^3.0.0",
"ky": "^0.19.0",
@ -2227,7 +2167,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/url-regex/-/url-regex-5.0.0.tgz",
"integrity": "sha512-O08GjTiAFNsSlrUWfqF1jH0H1W3m35ZyadHrGv5krdnmPPoxP27oDTqux/579PtaroiSGm5yma6KT1mHFH6Y/g==",
"dev": true,
"requires": {
"ip-regex": "^4.1.0",
"tlds": "^1.203.0"
@ -2236,8 +2175,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "3.4.0",
@ -2349,8 +2287,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write-file-atomic": {
"version": "3.0.3",
@ -2367,8 +2304,7 @@
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
"dev": true
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
},
"y18n": {
"version": "4.0.0",
@ -2547,7 +2483,6 @@
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"

View File

@ -5,8 +5,7 @@
"main": "./app/index.js",
"scripts": {
"unit-test": "nyc --reporter=text mocha",
"test": "node ./app/test.js",
"debug": "nodemon --inspect ."
"test": "node ./app/test.js"
},
"keywords": [
"f95zone",