From 5c3d18b0e2a096368efcb0603fa7277d89aa6f6d Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Thu, 19 Mar 2020 23:20:33 +0000 Subject: [PATCH 01/11] Initial refactor (routes and utils to respective folders) --- index.js | 1013 +------------------------- src/databases/databases.js | 27 + src/routes/addUserAsVIP.js | 46 ++ src/routes/getDaysSavedFormatted.js | 12 + src/routes/getSavedTimeForUser.js | 31 + src/routes/getTopUsers.js | 51 ++ src/routes/getTotalStats.js | 53 ++ src/routes/getUsername.js | 36 + src/routes/getVideoSponsorTimes.js | 270 +++++++ src/routes/getViewsForUser.js | 33 + src/routes/setUsername.js | 54 ++ src/routes/shadowBanUser.js | 66 ++ src/routes/submitSponsorTimes.js | 181 +++++ src/routes/viewedVideoSponsorTime.js | 16 + src/routes/voteOnSponsorTime.js | 158 ++++ src/utils/getFormattedTime.js | 13 + src/utils/getHash.js | 10 + src/utils/getIP.js | 6 + 18 files changed, 1094 insertions(+), 982 deletions(-) create mode 100644 src/databases/databases.js create mode 100644 src/routes/addUserAsVIP.js create mode 100644 src/routes/getDaysSavedFormatted.js create mode 100644 src/routes/getSavedTimeForUser.js create mode 100644 src/routes/getTopUsers.js create mode 100644 src/routes/getTotalStats.js create mode 100644 src/routes/getUsername.js create mode 100644 src/routes/getVideoSponsorTimes.js create mode 100644 src/routes/getViewsForUser.js create mode 100644 src/routes/setUsername.js create mode 100644 src/routes/shadowBanUser.js create mode 100644 src/routes/submitSponsorTimes.js create mode 100644 src/routes/viewedVideoSponsorTime.js create mode 100644 src/routes/voteOnSponsorTime.js create mode 100644 src/utils/getFormattedTime.js create mode 100644 src/utils/getHash.js create mode 100644 src/utils/getIP.js diff --git a/index.js b/index.js index c3b00f7..e88f988 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,29 @@ var http = require('http'); // Create a service (the app object is just a callback). var app = express(); -//hashing service -var crypto = require('crypto'); let config = JSON.parse(fs.readFileSync('config.json')); -var request = require('request'); + +// Utils +var getHash = require('./src/utils/getHash.js'); +var getIP = require('./src/utils/getIP.js'); +var getFormattedTime = require('./src/utils/getFormattedTime.js'); + +// Routes +var getVideoSponsorTimes = require('./src/routes/getVideoSponsorTimes.js'); +var submitSponsorTimes = require('./src/routes/submitSponsorTimes.js'); +var voteOnSponsorTime = require('./src/routes/voteOnSponsorTime.js'); +var viewedVideoSponsorTime = require('./src/routes/viewedVideoSponsorTime.js'); +var setUsername = require('./src/routes/setUsername.js'); +var getUsername = require('./src/routes/getUsername.js'); +var shadowBanUser = require('./src/routes/shadowBanUser.js'); +var addUserAsVIP = require('./src/routes/addUserAsVIP.js'); +var getSavedTimeForUser = require('./src/routes/getSavedTimeForUser.js'); +var getViewsForUser = require('./src/routes/getViewsForUser.js'); +var getTopUsers = require('./src/routes/getTopUsers.js'); +var getTotalStats = require('./src/routes/getTotalStats.js'); +var getDaysSavedFormatted = require('./src/routes/getDaysSavedFormatted.js'); // YouTube API const YouTubeAPI = require("youtube-api"); @@ -32,27 +49,6 @@ var privateDB = new Sqlite3(config.privateDB, options); // Create an HTTP service. http.createServer(app).listen(config.port); -var globalSalt = config.globalSalt; -var adminUserID = config.adminUserID; - -//if so, it will use the x-forwarded header instead of the ip address of the connection -var behindProxy = config.behindProxy; - -// A cache of the number of chrome web store users -var chromeUsersCache = null; -var firefoxUsersCache = null; -var lastUserCountCheck = 0; - -// Enable WAL mode checkpoint number -if (!config.readOnly && config.mode === "production") { - db.exec("PRAGMA journal_mode=WAL;"); - db.exec("PRAGMA wal_autocheckpoint=1;"); -} - -// Enable Memory-Mapped IO -db.exec("pragma mmap_size= 500000000;"); -privateDB.exec("pragma mmap_size= 500000000;"); - //setup CORS correctly app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); @@ -61,998 +57,51 @@ app.use(function(req, res, next) { }); //add the get function -app.get('/api/getVideoSponsorTimes', function (req, res) { - let videoID = req.query.videoID; - - let sponsorTimes = []; - let votes = [] - let UUIDs = []; - - let hashedIP = getHash(getIP(req) + globalSalt); - - try { - let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? ORDER BY startTime").all(videoID); - - for (let i = 0; i < rows.length; i++) { - //check if votes are above -1 - if (rows[i].votes < -1) { - //too untrustworthy, just ignore it - continue; - } - - //check if shadowHidden - //this means it is hidden to everyone but the original ip that submitted it - if (rows[i].shadowHidden == 1) { - //get the ip - //await the callback - let hashedIPRow = privateDB.prepare("SELECT hashedIP FROM sponsorTimes WHERE videoID = ?").all(videoID); - - if (!hashedIPRow.some((e) => e.hashedIP === hashedIP)) { - //this isn't their ip, don't send it to them - continue; - } - } - - sponsorTimes.push([]); - - let index = sponsorTimes.length - 1; - - sponsorTimes[index][0] = rows[i].startTime; - sponsorTimes[index][1] = rows[i].endTime; - - votes[index] = rows[i].votes; - UUIDs[index] = rows[i].UUID; - } - - if (sponsorTimes.length == 0) { - res.sendStatus(404); - return; - } - - organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs); - sponsorTimes = organisedData.sponsorTimes; - UUIDs = organisedData.UUIDs; - - if (sponsorTimes.length == 0) { - res.sendStatus(404); - } else { - //send result - res.send({ - sponsorTimes: sponsorTimes, - UUIDs: UUIDs - }) - } - } catch(error) { - console.error(error); - res.send(500); - } - - -}); - -function getIP(req) { - return behindProxy ? req.headers['x-forwarded-for'] : req.connection.remoteAddress; -} +app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes); //add the post function app.get('/api/postVideoSponsorTimes', submitSponsorTimes); app.post('/api/postVideoSponsorTimes', submitSponsorTimes); -async function submitSponsorTimes(req, res) { - let videoID = req.query.videoID; - let startTime = req.query.startTime; - let endTime = req.query.endTime; - let userID = req.query.userID; - - //check if all correct inputs are here and the length is 1 second or more - if (videoID == undefined || startTime == undefined || endTime == undefined || userID == undefined - || Math.abs(startTime - endTime) < 1) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - //hash the ip 5000 times so no one can get it from the database - let hashedIP = getHash(getIP(req) + globalSalt); - - startTime = parseFloat(startTime); - endTime = parseFloat(endTime); - - if (isNaN(startTime) || isNaN(endTime)) { - //invalid request - res.sendStatus(400); - return; - } - - if (startTime === Infinity || endTime === Infinity) { - //invalid request - res.sendStatus(400); - return; - } - - if (startTime > endTime) { - //time can't go backwards - res.sendStatus(400); - return; - } - - try { - //check if this user is on the vip list - let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID); - - //this can just be a hash of the data - //it's better than generating an actual UUID like what was used before - //also better for duplication checking - let hashCreator = crypto.createHash('sha256'); - let UUID = hashCreator.update(videoID + startTime + endTime + userID).digest('hex'); - - //get current time - let timeSubmitted = Date.now(); - - let yesterday = timeSubmitted - 86400000; - - //check to see if this ip has submitted too many sponsors today - let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]); - - if (rateLimitCheckRow.count >= 10) { - //too many sponsors for the same video from the same ip address - res.sendStatus(429); - } else { - //check to see if the user has already submitted sponsors for this video - let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]); - - if (duplicateCheckRow.count >= 8) { - //too many sponsors for the same video from the same user - res.sendStatus(429); - } else { - //check if this info has already been submitted first - let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]); - - //check to see if this user is shadowbanned - let shadowBanRow = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID); - - let shadowBanned = shadowBanRow.userCount; - - if (!(await isUserTrustworthy(userID))) { - //hide this submission as this user is untrustworthy - shadowBanned = 1; - } - - let startingVotes = 0; - if (vipRow.userCount > 0) { - //this user is a vip, start them at a higher approval rating - startingVotes = 10; - } - - if (duplicateCheck2Row == null) { - //not a duplicate, execute query - try { - db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, startTime, endTime, startingVotes, UUID, userID, timeSubmitted, 0, shadowBanned); - - //add to private db as well - privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted); - - res.sendStatus(200); - } catch (err) { - //a DB change probably occurred - res.sendStatus(502); - console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + startTime + ", " + "endTime" + ", " + userID); - - return; - } - } else { - res.sendStatus(409); - } - - //check if they are a first time user - //if so, send a notification to discord - if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null && duplicateCheck2Row == null) { - let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID); - - // If it is a first time submission - if (userSubmissionCountRow.submissionCount === 0) { - YouTubeAPI.videos.list({ - part: "snippet", - id: videoID - }, function (err, data) { - if (err) { - console.log(err); - return; - } - - request.post(config.discordFirstTimeSubmissionsWebhookURL, { - json: { - "embeds": [{ - "title": data.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2), - "description": "Submission ID: " + UUID + - "\n\nTimestamp: " + - getFormattedTime(startTime) + " to " + getFormattedTime(endTime), - "color": 10813440, - "author": { - "name": userID - }, - "thumbnail": { - "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } - }); - }); - } - } - } - } - } catch (err) { - console.error(err); - - res.send(500); - } -} - //voting endpoint app.get('/api/voteOnSponsorTime', voteOnSponsorTime); app.post('/api/voteOnSponsorTime', voteOnSponsorTime); -async function voteOnSponsorTime(req, res) { - let UUID = req.query.UUID; - let userID = req.query.userID; - let type = req.query.type; - - if (UUID == undefined || userID == undefined || type == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - let nonAnonUserID = getHash(userID); - userID = getHash(userID + UUID); - - //x-forwarded-for if this server is behind a proxy - let ip = getIP(req); - - //hash the ip 5000 times so no one can get it from the database - let hashedIP = getHash(ip + globalSalt); - - try { - //check if vote has already happened - let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID); - - //-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future - let incrementAmount = 0; - let oldIncrementAmount = 0; - - if (type == 1) { - //upvote - incrementAmount = 1; - } else if (type == 0) { - //downvote - incrementAmount = -1; - } else { - //unrecongnised type of vote - res.sendStatus(400); - return; - } - if (votesRow != undefined) { - if (votesRow.type == 1) { - //upvote - oldIncrementAmount = 1; - } else if (votesRow.type == 0) { - //downvote - oldIncrementAmount = -1; - } else if (votesRow.type == 2) { - //extra downvote - oldIncrementAmount = -4; - } else if (votesRow.type < 0) { - //vip downvote - oldIncrementAmount = votesRow.type; - } - } - - //check if this user is on the vip list - let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID); - - //check if the increment amount should be multiplied (downvotes have more power if there have been many views) - let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID); - - if (vipRow.userCount != 0 && incrementAmount < 0) { - //this user is a vip and a downvote - incrementAmount = - (row.votes + 2 - oldIncrementAmount); - type = incrementAmount; - } else if (row !== undefined && (row.votes > 8 || row.views > 15) && incrementAmount < 0) { - //increase the power of this downvote - incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount)); - type = incrementAmount; - } - - // Send discord message - if (type != 1) { - // Get video ID - let submissionInfoRow = db.prepare("SELECT videoID, userID, startTime, endTime FROM sponsorTimes WHERE UUID = ?").get(UUID); - - let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID); - - if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) { - YouTubeAPI.videos.list({ - part: "snippet", - id: submissionInfoRow.videoID - }, function (err, data) { - if (err) { - console.log(err); - return; - } - - request.post(config.discordReportChannelWebhookURL, { - json: { - "embeds": [{ - "title": data.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID + - "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), - "description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views + - " Views**\n\nSubmission ID: " + UUID + - "\n\nSubmitted by: " + submissionInfoRow.userID + "\n\nTimestamp: " + - getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), - "color": 10813440, - "author": { - "name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "") - }, - "thumbnail": { - "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } - }); - }); - } - } - - //update the votes table - if (votesRow != undefined) { - privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID); - } else { - privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); - } - - //update the vote count on this sponsorTime - //oldIncrementAmount will be zero is row is null - db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID); - - //for each positive vote, see if a hidden submission can be shown again - if (incrementAmount > 0) { - //find the UUID that submitted the submission that was voted on - let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID; - - //check if any submissions are hidden - let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID); - - if (hiddenSubmissionsRow.hiddenSubmissions > 0) { - //see if some of this users submissions should be visible again - - if (await isUserTrustworthy(submissionUserID)) { - //they are trustworthy again, show 2 of their submissions again, if there are two to show - db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID) - } - } - } - - //added to db - res.sendStatus(200); - } catch (err) { - console.error(err); - } -} - //Endpoint when a sponsorTime is used up app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); -function viewedVideoSponsorTime(req, res) { - let UUID = req.query.UUID; - - if (UUID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //up the view count by one - db.prepare("UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?").run(UUID); - - res.sendStatus(200); -} - - //To set your username for the stats view -app.post('/api/setUsername', function (req, res) { - let userID = req.query.userID; - let userName = req.query.username; - - let adminUserIDInput = req.query.adminUserID; - - if (userID == undefined || userName == undefined || userID === "undefined") { - //invalid request - res.sendStatus(400); - return; - } - - if (adminUserIDInput != undefined) { - //this is the admin controlling the other users account, don't hash the controling account's ID - adminUserIDInput = getHash(adminUserIDInput); - - if (adminUserIDInput != adminUserID) { - //they aren't the admin - res.sendStatus(403); - return; - } - } else { - //hash the userID - userID = getHash(userID); - } - - try { - //check if username is already set - let row = db.prepare("SELECT count(*) as count FROM userNames WHERE userID = ?").get(userID); - - if (row.count > 0) { - //already exists, update this row - db.prepare("UPDATE userNames SET userName = ? WHERE userID = ?").run(userName, userID); - } else { - //add to the db - db.prepare("INSERT INTO userNames VALUES(?, ?)").run(userID, userName); - } - - res.sendStatus(200); - } catch (err) { - console.log(err); - res.sendStatus(500); - - return; - } -}); +app.post('/api/setUsername', setUsername); //get what username this user has -app.get('/api/getUsername', function (req, res) { - let userID = req.query.userID; - - if (userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - try { - let row = db.prepare("SELECT userName FROM userNames WHERE userID = ?").get(userID); - - if (row !== undefined) { - res.send({ - userName: row.userName - }); - } else { - //no username yet, just send back the userID - res.send({ - userName: userID - }); - } - } catch (err) { - console.log(err); - res.sendStatus(500); - - return; - } -}); +app.get('/api/getUsername', getUsername); //Endpoint used to hide a certain user's data -app.post('/api/shadowBanUser', async function (req, res) { - let userID = req.query.userID; - let adminUserIDInput = req.query.adminUserID; - - let enabled = req.query.enabled; - if (enabled === undefined){ - enabled = true; - } else { - enabled = enabled === "true"; - } - - //if enabled is false and the old submissions should be made visible again - let unHideOldSubmissions = req.query.unHideOldSubmissions; - if (enabled === undefined){ - unHideOldSubmissions = true; - } else { - unHideOldSubmissions = unHideOldSubmissions === "true"; - } - - if (adminUserIDInput == undefined || userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - adminUserIDInput = getHash(adminUserIDInput); - - if (adminUserIDInput !== adminUserID) { - //not authorized - res.sendStatus(403); - return; - } - - //check to see if this user is already shadowbanned - let row = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID); - - if (enabled && row.userCount == 0) { - //add them to the shadow ban list - - //add it to the table - privateDB.prepare("INSERT INTO shadowBannedUsers VALUES(?)").run(userID); - - //find all previous submissions and hide them - db.prepare("UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?").run(userID); - } else if (!enabled && row.userCount > 0) { - //remove them from the shadow ban list - privateDB.prepare("DELETE FROM shadowBannedUsers WHERE userID = ?").run(userID); - - //find all previous submissions and unhide them - if (unHideOldSubmissions) { - db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?").run(userID); - } - } - - res.sendStatus(200); -}); +app.post('/api/shadowBanUser', shadowBanUser); //Endpoint used to make a user a VIP user with special privileges -app.post('/api/addUserAsVIP', async function (req, res) { - let userID = req.query.userID; - let adminUserIDInput = req.query.adminUserID; - - let enabled = req.query.enabled; - if (enabled === undefined){ - enabled = true; - } else { - enabled = enabled === "true"; - } - - if (userID == undefined || adminUserIDInput == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - adminUserIDInput = getHash(adminUserIDInput); - - if (adminUserIDInput !== adminUserID) { - //not authorized - res.sendStatus(403); - return; - } - - //check to see if this user is already a vip - let row = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID); - - if (enabled && row.userCount == 0) { - //add them to the vip list - db.prepare("INSERT INTO vipUsers VALUES(?)").run(userID); - } else if (!enabled && row.userCount > 0) { - //remove them from the shadow ban list - db.prepare("DELETE FROM vipUsers WHERE userID = ?").run(userID); - } - - res.sendStatus(200); -}); +app.post('/api/addUserAsVIP', addUserAsVIP); //Gets all the views added up for one userID //Useful to see how much one user has contributed -app.get('/api/getViewsForUser', function (req, res) { - let userID = req.query.userID; - - if (userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - try { - let row = db.prepare("SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?").get(userID); - - //increase the view count by one - if (row.viewCount != null) { - res.send({ - viewCount: row.viewCount - }); - } else { - res.sendStatus(404); - } - } catch (err) { - console.log(err); - res.sendStatus(500); - - return; - } -}); +app.get('/api/getViewsForUser', getViewsForUser); //Gets all the saved time added up (views * sponsor length) for one userID //Useful to see how much one user has contributed //In minutes -app.get('/api/getSavedTimeForUser', function (req, res) { - let userID = req.query.userID; +app.get('/api/getSavedTimeForUser', getSavedTimeForUser); - if (userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - try { - let row = db.prepare("SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ").get(userID); - - if (row.minutesSaved != null) { - res.send({ - timeSaved: row.minutesSaved - }); - } else { - res.sendStatus(404); - } - } catch (err) { - console.log(err); - res.sendStatus(500); - - return; - } -}); - -app.get('/api/getTopUsers', function (req, res) { - let sortType = req.query.sortType; - - if (sortType == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //setup which sort type to use - let sortBy = ""; - if (sortType == 0) { - sortBy = "minutesSaved"; - } else if (sortType == 1) { - sortBy = "viewCount"; - } else if (sortType == 2) { - sortBy = "totalSubmissions"; - } else { - //invalid request - res.sendStatus(400); - return; - } - - let userNames = []; - let viewCounts = []; - let totalSubmissions = []; - let minutesSaved = []; - - let rows = db.prepare("SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," + - "SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " + - "IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " + - "WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 GROUP BY IFNULL(userName, sponsorTimes.userID) ORDER BY " + sortBy + " DESC LIMIT 100").all(); - - for (let i = 0; i < rows.length; i++) { - userNames[i] = rows[i].userName; - - viewCounts[i] = rows[i].viewCount; - totalSubmissions[i] = rows[i].totalSubmissions; - minutesSaved[i] = rows[i].minutesSaved; - } - - //send this result - res.send({ - userNames: userNames, - viewCounts: viewCounts, - totalSubmissions: totalSubmissions, - minutesSaved: minutesSaved - }); -}); +app.get('/api/getTopUsers', getTopUsers); //send out totals //send the total submissions, total views and total minutes saved -app.get('/api/getTotalStats', function (req, res) { - let row = db.prepare("SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " + - "SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1").get(); - - if (row !== undefined) { - //send this result - res.send({ - userCount: row.userCount, - activeUsers: chromeUsersCache + firefoxUsersCache, - viewCount: row.viewCount, - totalSubmissions: row.totalSubmissions, - minutesSaved: row.minutesSaved - }); - - // Check if the cache should be updated (every ~14 hours) - let now = Date.now(); - if (now - lastUserCountCheck > 5000000) { - lastUserCountCheck = now; - - // Get total users - request.get("https://addons.mozilla.org/api/v3/addons/addon/sponsorblock/", function (err, firefoxResponse, body) { - try { - firefoxUsersCache = parseInt(JSON.parse(body).average_daily_users); - - request.get("https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone", function(err, chromeResponse, body) { - if (body !== undefined) { - try { - chromeUsersCache = parseInt(body.match(/(?<=\)/)[0].replace(",", "")); - } catch (error) { - // Re-check later - lastUserCountCheck = 0; - } - } else { - lastUserCountCheck = 0; - } - }); - } catch (error) { - // Re-check later - lastUserCountCheck = 0; - } - }); - } - } -}); +app.get('/api/getTotalStats', getTotalStats); //send out a formatted time saved total -app.get('/api/getDaysSavedFormatted', function (req, res) { - let row = db.prepare("SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved FROM sponsorTimes WHERE shadowHidden != 1").get(); - - if (row !== undefined) { - //send this result - res.send({ - daysSaved: row.daysSaved.toFixed(2) - }); - } -}); +app.get('/api/getdayssavedformatted', getDaysSavedFormatted); app.get('/database.db', function (req, res) { - res.sendFile("./databases/sponsorTimes.db", { root: __dirname }); + res.sendfile("./databases/sponsortimes.db", { root: __dirname }); }); -//returns true if the user is considered trustworthy -//this happens after a user has made 5 submissions and has less than 60% downvoted submissions -async function isUserTrustworthy(userID) { - //check to see if this user how many submissions this user has submitted - let totalSubmissionsRow = db.prepare("SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?").get(userID); - - if (totalSubmissionsRow.totalSubmissions > 5) { - //check if they have a high downvote ratio - let downvotedSubmissionsRow = db.prepare("SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)").get(userID); - - return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 || - (totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions); - } - - return true; -} - -//This function will find sponsor times that are contained inside of eachother, called similar sponsor times -//Only one similar time will be returned, randomly generated based on the sqrt of votes. -//This allows new less voted items to still sometimes appear to give them a chance at getting votes. -//Sponsor times with less than -1 votes are already ignored before this function is called -function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) { - //list of sponsors that are contained inside eachother - let similarSponsors = []; - - for (let i = 0; i < sponsorTimes.length; i++) { - //see if the start time is located between the start and end time of the other sponsor time. - for (let j = i + 1; j < sponsorTimes.length; j++) { - if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) { - //sponsor j is contained in sponsor i - similarSponsors.push([i, j]); - } - } - } - - let similarSponsorsGroups = []; - //once they have been added to a group, they don't need to be dealt with anymore - let dealtWithSimilarSponsors = []; - - //create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3) - for (let i = 0; i < similarSponsors.length; i++) { - if (dealtWithSimilarSponsors.includes(i)) { - //dealt with already - continue; - } - - //this is the group of indexes that are similar - let group = similarSponsors[i]; - for (let j = 0; j < similarSponsors.length; j++) { - if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) { - //this is a similar group - group.push(similarSponsors[j][0]); - group.push(similarSponsors[j][1]); - dealtWithSimilarSponsors.push(j); - } - } - similarSponsorsGroups.push(group); - } - - //remove duplicate indexes in group arrays - for (let i = 0; i < similarSponsorsGroups.length; i++) { - uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) { - return self.indexOf(item) == pos; - }); - - similarSponsorsGroups[i] = uniqueArray; - } - - let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes); - - let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices; - //the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added - let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith; - - let voteSums = weightedRandomIndexes.weightSums; - //convert these into the votes - for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { - //it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor. - votes[finalSponsorTimeIndexes[i]] = voteSums[i]; - } - - //find the indexes never dealt with and add them - for (let i = 0; i < sponsorTimes.length; i++) { - if (!finalSponsorTimeIndexesDealtWith.includes(i)) { - finalSponsorTimeIndexes.push(i) - } - } - - //if there are too many indexes, find the best 4 - if (finalSponsorTimeIndexes.length > 8) { - finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices; - } - - //convert this to a final array to return - let finalSponsorTimes = []; - for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { - finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]); - } - - //convert this to a final array of UUIDs as well - let finalUUIDs = []; - for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { - finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]); - } - - return { - sponsorTimes: finalSponsorTimes, - UUIDs: finalUUIDs - }; -} - -//gets the getWeightedRandomChoice for each group in an array of groups -function getWeightedRandomChoiceForArray(choiceGroups, weights) { - let finalChoices = []; - //the indexes either chosen to be added to final indexes or chosen not to be added - let choicesDealtWith = []; - //for each choice group, what are the sums of the weights - let weightSums = []; - - for (let i = 0; i < choiceGroups.length; i++) { - //find weight sums for this group - weightSums.push(0); - for (let j = 0; j < choiceGroups[i].length; j++) { - //only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time - if (weights[choiceGroups[i][j]] > 0) { - weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]]; - } - } - - //create a random choice for this group - let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1) - finalChoices.push(randomChoice.finalChoices); - - for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) { - choicesDealtWith.push(randomChoice.choicesDealtWith[j]) - } - } - - return { - finalChoices: finalChoices, - choicesDealtWith: choicesDealtWith, - weightSums: weightSums - }; -} - -//gets a weighted random choice from the indexes array based on the weights. -//amountOfChoices speicifies the amount of choices to return, 1 or more. -//choices are unique -function getWeightedRandomChoice(choices, weights, amountOfChoices) { - if (amountOfChoices > choices.length) { - //not possible, since all choices must be unique - return null; - } - - let finalChoices = []; - let choicesDealtWith = []; - - let sqrtWeightsList = []; - //the total of all the weights run through the cutom sqrt function - let totalSqrtWeights = 0; - for (let j = 0; j < choices.length; j++) { - //multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3) - //The 3 makes -2 the minimum votes before being ignored completely - //https://www.desmos.com/calculator/ljftxolg9j - //this can be changed if this system increases in popularity. - let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10); - sqrtWeightsList.push(sqrtVote) - totalSqrtWeights += sqrtVote; - - //this index has now been deat with - choicesDealtWith.push(choices[j]); - } - - //iterate and find amountOfChoices choices - let randomNumber = Math.random(); - - //this array will keep adding to this variable each time one sqrt vote has been dealt with - //this is the sum of all the sqrtVotes under this index - let currentVoteNumber = 0; - for (let j = 0; j < sqrtWeightsList.length; j++) { - if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) { - //this one was randomly generated - finalChoices.push(choices[j]); - //remove that from original array, for next recursion pass if it happens - choices.splice(j, 1); - break; - } - - //add on to the count - currentVoteNumber += sqrtWeightsList[j]; - } - - //add on the other choices as well using recursion - if (amountOfChoices > 1) { - let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices; - //add all these choices to the finalChoices array being returned - for (let i = 0; i < otherChoices.length; i++) { - finalChoices.push(otherChoices[i]); - } - } - - return { - finalChoices: finalChoices, - choicesDealtWith: choicesDealtWith - }; -} - -function getHash(value, times=5000) { - for (let i = 0; i < times; i++) { - let hashCreator = crypto.createHash('sha256'); - value = hashCreator.update(value).digest('hex'); - } - - return value; -} - -//converts time in seconds to minutes:seconds -function getFormattedTime(seconds) { - let minutes = Math.floor(seconds / 60); - let secondsDisplay = Math.round(seconds - minutes * 60); - if (secondsDisplay < 10) { - //add a zero - secondsDisplay = "0" + secondsDisplay; - } - - let formatted = minutes+ ":" + secondsDisplay; - - return formatted; -} \ No newline at end of file diff --git a/src/databases/databases.js b/src/databases/databases.js new file mode 100644 index 0000000..2f18e52 --- /dev/null +++ b/src/databases/databases.js @@ -0,0 +1,27 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); +var Sqlite3 = require('better-sqlite3'); + +let options = { + readonly: config.readOnly +}; + +var db = new Sqlite3(config.db, options); +var privateDB = new Sqlite3(config.privateDB, options); + +// Enable WAL mode checkpoint number +if (!config.readOnly && config.mode === "production") { + db.exec("PRAGMA journal_mode=WAL;"); + db.exec("PRAGMA wal_autocheckpoint=1;"); +} + +// Enable Memory-Mapped IO +db.exec("pragma mmap_size= 500000000;"); +privateDB.exec("pragma mmap_size= 500000000;"); + +console.log('databases.js has been executed...'); + +module.exports = { + db: db, + privateDB: privateDB +}; \ No newline at end of file diff --git a/src/routes/addUserAsVIP.js b/src/routes/addUserAsVIP.js new file mode 100644 index 0000000..6f55090 --- /dev/null +++ b/src/routes/addUserAsVIP.js @@ -0,0 +1,46 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var db = require('../databases/databases.js').db; +var getHash = require('../utils/getHash.js'); + + +module.exports = async function addUserAsVIP (req, res) { + let userID = req.query.userID; + let adminUserIDInput = req.query.adminUserID; + + let enabled = req.query.enabled; + if (enabled === undefined){ + enabled = true; + } else { + enabled = enabled === "true"; + } + + if (userID == undefined || adminUserIDInput == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + adminUserIDInput = getHash(adminUserIDInput); + + if (adminUserIDInput !== adminUserID) { + //not authorized + res.sendStatus(403); + return; + } + + //check to see if this user is already a vip + let row = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID); + + if (enabled && row.userCount == 0) { + //add them to the vip list + db.prepare("INSERT INTO vipUsers VALUES(?)").run(userID); + } else if (!enabled && row.userCount > 0) { + //remove them from the shadow ban list + db.prepare("DELETE FROM vipUsers WHERE userID = ?").run(userID); + } + + res.sendStatus(200); +} \ No newline at end of file diff --git a/src/routes/getDaysSavedFormatted.js b/src/routes/getDaysSavedFormatted.js new file mode 100644 index 0000000..7fece0c --- /dev/null +++ b/src/routes/getDaysSavedFormatted.js @@ -0,0 +1,12 @@ +var db = require('../databases/databases.js'); + +module.exports = function getDaysSavedFormatted (req, res) { + let row = db.prepare("select sum((endtime - starttime) / 60 / 60 / 24 * views) as dayssaved from sponsortimes where shadowhidden != 1").get(); + + if (row !== undefined) { + //send this result + res.send({ + dayssaved: row.dayssaved.tofixed(2) + }); + } +} \ No newline at end of file diff --git a/src/routes/getSavedTimeForUser.js b/src/routes/getSavedTimeForUser.js new file mode 100644 index 0000000..0b874ce --- /dev/null +++ b/src/routes/getSavedTimeForUser.js @@ -0,0 +1,31 @@ +var db = require('../databases/databases.js').db; + +module.exports = function getSavedTimeForUser (req, res) { + let userID = req.query.userID; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let row = db.prepare("SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ").get(userID); + + if (row.minutesSaved != null) { + res.send({ + timeSaved: row.minutesSaved + }); + } else { + res.sendStatus(404); + } + } catch (err) { + console.log(err); + res.sendStatus(500); + + return; + } +} \ No newline at end of file diff --git a/src/routes/getTopUsers.js b/src/routes/getTopUsers.js new file mode 100644 index 0000000..8230b53 --- /dev/null +++ b/src/routes/getTopUsers.js @@ -0,0 +1,51 @@ +var db = require('../databases/databases.js').db; + +module.exports = function getTopUsers (req, res) { + let sortType = req.query.sortType; + + if (sortType == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //setup which sort type to use + let sortBy = ""; + if (sortType == 0) { + sortBy = "minutesSaved"; + } else if (sortType == 1) { + sortBy = "viewCount"; + } else if (sortType == 2) { + sortBy = "totalSubmissions"; + } else { + //invalid request + res.sendStatus(400); + return; + } + + let userNames = []; + let viewCounts = []; + let totalSubmissions = []; + let minutesSaved = []; + + let rows = db.prepare("SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," + + "SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " + + "IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " + + "WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 GROUP BY IFNULL(userName, sponsorTimes.userID) ORDER BY " + sortBy + " DESC LIMIT 100").all(); + + for (let i = 0; i < rows.length; i++) { + userNames[i] = rows[i].userName; + + viewCounts[i] = rows[i].viewCount; + totalSubmissions[i] = rows[i].totalSubmissions; + minutesSaved[i] = rows[i].minutesSaved; + } + + //send this result + res.send({ + userNames: userNames, + viewCounts: viewCounts, + totalSubmissions: totalSubmissions, + minutesSaved: minutesSaved + }); +} \ No newline at end of file diff --git a/src/routes/getTotalStats.js b/src/routes/getTotalStats.js new file mode 100644 index 0000000..86b1531 --- /dev/null +++ b/src/routes/getTotalStats.js @@ -0,0 +1,53 @@ +var db = require('../databases/databases.js').db; +var request = require('request'); + +// A cache of the number of chrome web store users +var chromeUsersCache = null; +var firefoxUsersCache = null; +var lastUserCountCheck = 0; + + +module.exports = function getTotalStats (req, res) { + let row = db.prepare("SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " + + "SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1").get(); + + if (row !== undefined) { + //send this result + res.send({ + userCount: row.userCount, + activeUsers: chromeUsersCache + firefoxUsersCache, + viewCount: row.viewCount, + totalSubmissions: row.totalSubmissions, + minutesSaved: row.minutesSaved + }); + + // Check if the cache should be updated (every ~14 hours) + let now = Date.now(); + if (now - lastUserCountCheck > 5000000) { + lastUserCountCheck = now; + + // Get total users + request.get("https://addons.mozilla.org/api/v3/addons/addon/sponsorblock/", function (err, firefoxResponse, body) { + try { + firefoxUsersCache = parseInt(JSON.parse(body).average_daily_users); + + request.get("https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone", function(err, chromeResponse, body) { + if (body !== undefined) { + try { + chromeUsersCache = parseInt(body.match(/(?<=\)/)[0].replace(",", "")); + } catch (error) { + // Re-check later + lastUserCountCheck = 0; + } + } else { + lastUserCountCheck = 0; + } + }); + } catch (error) { + // Re-check later + lastUserCountCheck = 0; + } + }); + } + } +} \ No newline at end of file diff --git a/src/routes/getUsername.js b/src/routes/getUsername.js new file mode 100644 index 0000000..290b6cd --- /dev/null +++ b/src/routes/getUsername.js @@ -0,0 +1,36 @@ +var db = require('../databases/databases.js').db; + +var getHash = require('../utils/getHash.js'); + +module.exports = function getUsername (req, res) { + let userID = req.query.userID; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let row = db.prepare("SELECT userName FROM userNames WHERE userID = ?").get(userID); + + if (row !== undefined) { + res.send({ + userName: row.userName + }); + } else { + //no username yet, just send back the userID + res.send({ + userName: userID + }); + } + } catch (err) { + console.log(err); + res.sendStatus(500); + + return; + } +} \ No newline at end of file diff --git a/src/routes/getVideoSponsorTimes.js b/src/routes/getVideoSponsorTimes.js new file mode 100644 index 0000000..746a342 --- /dev/null +++ b/src/routes/getVideoSponsorTimes.js @@ -0,0 +1,270 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var databases = require('../databases/databases.js'); +var db = databases.db; +var privateDB = databases.privateDB; + +var getHash = require('../utils/getHash.js'); +var getIP = require('../utils/getIP.js'); + + +//gets the getWeightedRandomChoice for each group in an array of groups +function getWeightedRandomChoiceForArray(choiceGroups, weights) { + let finalChoices = []; + //the indexes either chosen to be added to final indexes or chosen not to be added + let choicesDealtWith = []; + //for each choice group, what are the sums of the weights + let weightSums = []; + + for (let i = 0; i < choiceGroups.length; i++) { + //find weight sums for this group + weightSums.push(0); + for (let j = 0; j < choiceGroups[i].length; j++) { + //only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time + if (weights[choiceGroups[i][j]] > 0) { + weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]]; + } + } + + //create a random choice for this group + let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1) + finalChoices.push(randomChoice.finalChoices); + + for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) { + choicesDealtWith.push(randomChoice.choicesDealtWith[j]) + } + } + + return { + finalChoices: finalChoices, + choicesDealtWith: choicesDealtWith, + weightSums: weightSums + }; +} + +//gets a weighted random choice from the indexes array based on the weights. +//amountOfChoices speicifies the amount of choices to return, 1 or more. +//choices are unique +function getWeightedRandomChoice(choices, weights, amountOfChoices) { + if (amountOfChoices > choices.length) { + //not possible, since all choices must be unique + return null; + } + + let finalChoices = []; + let choicesDealtWith = []; + + let sqrtWeightsList = []; + //the total of all the weights run through the cutom sqrt function + let totalSqrtWeights = 0; + for (let j = 0; j < choices.length; j++) { + //multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3) + //The 3 makes -2 the minimum votes before being ignored completely + //https://www.desmos.com/calculator/ljftxolg9j + //this can be changed if this system increases in popularity. + let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10); + sqrtWeightsList.push(sqrtVote) + totalSqrtWeights += sqrtVote; + + //this index has now been deat with + choicesDealtWith.push(choices[j]); + } + + //iterate and find amountOfChoices choices + let randomNumber = Math.random(); + + //this array will keep adding to this variable each time one sqrt vote has been dealt with + //this is the sum of all the sqrtVotes under this index + let currentVoteNumber = 0; + for (let j = 0; j < sqrtWeightsList.length; j++) { + if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) { + //this one was randomly generated + finalChoices.push(choices[j]); + //remove that from original array, for next recursion pass if it happens + choices.splice(j, 1); + break; + } + + //add on to the count + currentVoteNumber += sqrtWeightsList[j]; + } + + //add on the other choices as well using recursion + if (amountOfChoices > 1) { + let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices; + //add all these choices to the finalChoices array being returned + for (let i = 0; i < otherChoices.length; i++) { + finalChoices.push(otherChoices[i]); + } + } + + return { + finalChoices: finalChoices, + choicesDealtWith: choicesDealtWith + }; +} + + +//This function will find sponsor times that are contained inside of eachother, called similar sponsor times +//Only one similar time will be returned, randomly generated based on the sqrt of votes. +//This allows new less voted items to still sometimes appear to give them a chance at getting votes. +//Sponsor times with less than -1 votes are already ignored before this function is called +function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) { + //list of sponsors that are contained inside eachother + let similarSponsors = []; + + for (let i = 0; i < sponsorTimes.length; i++) { + //see if the start time is located between the start and end time of the other sponsor time. + for (let j = i + 1; j < sponsorTimes.length; j++) { + if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) { + //sponsor j is contained in sponsor i + similarSponsors.push([i, j]); + } + } + } + + let similarSponsorsGroups = []; + //once they have been added to a group, they don't need to be dealt with anymore + let dealtWithSimilarSponsors = []; + + //create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3) + for (let i = 0; i < similarSponsors.length; i++) { + if (dealtWithSimilarSponsors.includes(i)) { + //dealt with already + continue; + } + + //this is the group of indexes that are similar + let group = similarSponsors[i]; + for (let j = 0; j < similarSponsors.length; j++) { + if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) { + //this is a similar group + group.push(similarSponsors[j][0]); + group.push(similarSponsors[j][1]); + dealtWithSimilarSponsors.push(j); + } + } + similarSponsorsGroups.push(group); + } + + //remove duplicate indexes in group arrays + for (let i = 0; i < similarSponsorsGroups.length; i++) { + uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) { + return self.indexOf(item) == pos; + }); + + similarSponsorsGroups[i] = uniqueArray; + } + + let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes); + + let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices; + //the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added + let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith; + + let voteSums = weightedRandomIndexes.weightSums; + //convert these into the votes + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + //it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor. + votes[finalSponsorTimeIndexes[i]] = voteSums[i]; + } + + //find the indexes never dealt with and add them + for (let i = 0; i < sponsorTimes.length; i++) { + if (!finalSponsorTimeIndexesDealtWith.includes(i)) { + finalSponsorTimeIndexes.push(i) + } + } + + //if there are too many indexes, find the best 4 + if (finalSponsorTimeIndexes.length > 8) { + finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices; + } + + //convert this to a final array to return + let finalSponsorTimes = []; + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]); + } + + //convert this to a final array of UUIDs as well + let finalUUIDs = []; + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]); + } + + return { + sponsorTimes: finalSponsorTimes, + UUIDs: finalUUIDs + }; +} + + + +module.exports = function (req, res) { + let videoID = req.query.videoID; + + let sponsorTimes = []; + let votes = [] + let UUIDs = []; + + let hashedIP = getHash(getIP(req) + config.globalSalt); + + try { + let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? ORDER BY startTime").all(videoID); + + for (let i = 0; i < rows.length; i++) { + //check if votes are above -1 + if (rows[i].votes < -1) { + //too untrustworthy, just ignore it + continue; + } + + //check if shadowHidden + //this means it is hidden to everyone but the original ip that submitted it + if (rows[i].shadowHidden == 1) { + //get the ip + //await the callback + let hashedIPRow = privateDB.prepare("SELECT hashedIP FROM sponsorTimes WHERE videoID = ?").all(videoID); + + if (!hashedIPRow.some((e) => e.hashedIP === hashedIP)) { + //this isn't their ip, don't send it to them + continue; + } + } + + sponsorTimes.push([]); + + let index = sponsorTimes.length - 1; + + sponsorTimes[index][0] = rows[i].startTime; + sponsorTimes[index][1] = rows[i].endTime; + + votes[index] = rows[i].votes; + UUIDs[index] = rows[i].UUID; + } + + if (sponsorTimes.length == 0) { + res.sendStatus(404); + return; + } + + organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs); + sponsorTimes = organisedData.sponsorTimes; + UUIDs = organisedData.UUIDs; + + if (sponsorTimes.length == 0) { + res.sendStatus(404); + } else { + //send result + res.send({ + sponsorTimes: sponsorTimes, + UUIDs: UUIDs + }) + } + } catch(error) { + console.error(error); + res.send(500); + } +} \ No newline at end of file diff --git a/src/routes/getViewsForUser.js b/src/routes/getViewsForUser.js new file mode 100644 index 0000000..e3a0ebf --- /dev/null +++ b/src/routes/getViewsForUser.js @@ -0,0 +1,33 @@ +var db = require('../databases/databases.js').db; +var getHash = require('../utils/getHash.js'); + +module.exports = function getViewsForUser(req, res) { + let userID = req.query.userID; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let row = db.prepare("SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?").get(userID); + + //increase the view count by one + if (row.viewCount != null) { + res.send({ + viewCount: row.viewCount + }); + } else { + res.sendStatus(404); + } + } catch (err) { + console.log(err); + res.sendStatus(500); + + return; + } +} \ No newline at end of file diff --git a/src/routes/setUsername.js b/src/routes/setUsername.js new file mode 100644 index 0000000..2278ec4 --- /dev/null +++ b/src/routes/setUsername.js @@ -0,0 +1,54 @@ + +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var db = require('../databases/databases.js').db; +var getHash = require('../utils/getHash.js'); + + +module.exports = function setUsername(req, res) { + let userID = req.query.userID; + let userName = req.query.username; + + let adminUserIDInput = req.query.adminUserID; + + if (userID == undefined || userName == undefined || userID === "undefined") { + //invalid request + res.sendStatus(400); + return; + } + + if (adminUserIDInput != undefined) { + //this is the admin controlling the other users account, don't hash the controling account's ID + adminUserIDInput = getHash(adminUserIDInput); + + if (adminUserIDInput != config.adminUserID) { + //they aren't the admin + res.sendStatus(403); + return; + } + } else { + //hash the userID + userID = getHash(userID); + } + + try { + //check if username is already set + let row = db.prepare("SELECT count(*) as count FROM userNames WHERE userID = ?").get(userID); + + if (row.count > 0) { + //already exists, update this row + db.prepare("UPDATE userNames SET userName = ? WHERE userID = ?").run(userName, userID); + } else { + //add to the db + db.prepare("INSERT INTO userNames VALUES(?, ?)").run(userID, userName); + } + + res.sendStatus(200); + } catch (err) { + console.log(err); + res.sendStatus(500); + + return; + } +} \ No newline at end of file diff --git a/src/routes/shadowBanUser.js b/src/routes/shadowBanUser.js new file mode 100644 index 0000000..ff028f1 --- /dev/null +++ b/src/routes/shadowBanUser.js @@ -0,0 +1,66 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var databases = require('../databases/databases.js'); +var db = databases.db; +var privateDB = databases.privateDB; + +var getHash = require('../utils/getHash.js'); + +module.exports = async function shadowBanUser(req, res) { + let userID = req.query.userID; + let adminUserIDInput = req.query.adminUserID; + + let enabled = req.query.enabled; + if (enabled === undefined){ + enabled = true; + } else { + enabled = enabled === "true"; + } + + //if enabled is false and the old submissions should be made visible again + let unHideOldSubmissions = req.query.unHideOldSubmissions; + if (enabled === undefined){ + unHideOldSubmissions = true; + } else { + unHideOldSubmissions = unHideOldSubmissions === "true"; + } + + if (adminUserIDInput == undefined || userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + adminUserIDInput = getHash(adminUserIDInput); + + if (adminUserIDInput !== config.adminUserID) { + //not authorized + res.sendStatus(403); + return; + } + + //check to see if this user is already shadowbanned + let row = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID); + + if (enabled && row.userCount == 0) { + //add them to the shadow ban list + + //add it to the table + privateDB.prepare("INSERT INTO shadowBannedUsers VALUES(?)").run(userID); + + //find all previous submissions and hide them + db.prepare("UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?").run(userID); + } else if (!enabled && row.userCount > 0) { + //remove them from the shadow ban list + privateDB.prepare("DELETE FROM shadowBannedUsers WHERE userID = ?").run(userID); + + //find all previous submissions and unhide them + if (unHideOldSubmissions) { + db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?").run(userID); + } + } + + res.sendStatus(200); +} \ No newline at end of file diff --git a/src/routes/submitSponsorTimes.js b/src/routes/submitSponsorTimes.js new file mode 100644 index 0000000..84ce219 --- /dev/null +++ b/src/routes/submitSponsorTimes.js @@ -0,0 +1,181 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var databases = require('../databases/databases.js'); +var db = databases.db; +var privateDB = databases.privateDB; + +var getHash = require('../utils/getHash.js'); +var getIP = require('../utils/getIP.js'); + +// TODO: might need to be a util +//returns true if the user is considered trustworthy +//this happens after a user has made 5 submissions and has less than 60% downvoted submissions +async function isUserTrustworthy(userID) { + //check to see if this user how many submissions this user has submitted + let totalSubmissionsRow = db.prepare("SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?").get(userID); + + if (totalSubmissionsRow.totalSubmissions > 5) { + //check if they have a high downvote ratio + let downvotedSubmissionsRow = db.prepare("SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)").get(userID); + + return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 || + (totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions); + } + + return true; +} + +module.exports = async function submitSponsorTimes(req, res) { + let videoID = req.query.videoID; + let startTime = req.query.startTime; + let endTime = req.query.endTime; + let userID = req.query.userID; + + //check if all correct inputs are here and the length is 1 second or more + if (videoID == undefined || startTime == undefined || endTime == undefined || userID == undefined + || Math.abs(startTime - endTime) < 1) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + //hash the ip 5000 times so no one can get it from the database + let hashedIP = getHash(getIP(req) + config.globalSalt); + + startTime = parseFloat(startTime); + endTime = parseFloat(endTime); + + if (isNaN(startTime) || isNaN(endTime)) { + //invalid request + res.sendStatus(400); + return; + } + + if (startTime === Infinity || endTime === Infinity) { + //invalid request + res.sendStatus(400); + return; + } + + if (startTime > endTime) { + //time can't go backwards + res.sendStatus(400); + return; + } + + try { + //check if this user is on the vip list + let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID); + + //this can just be a hash of the data + //it's better than generating an actual UUID like what was used before + //also better for duplication checking + let UUID = getHash(videoID + startTime + endTime + userID, 1); + + //get current time + let timeSubmitted = Date.now(); + + let yesterday = timeSubmitted - 86400000; + + //check to see if this ip has submitted too many sponsors today + let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]); + + if (rateLimitCheckRow.count >= 10) { + //too many sponsors for the same video from the same ip address + res.sendStatus(429); + } else { + //check to see if the user has already submitted sponsors for this video + let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]); + + if (duplicateCheckRow.count >= 8) { + //too many sponsors for the same video from the same user + res.sendStatus(429); + } else { + //check if this info has already been submitted first + let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]); + + //check to see if this user is shadowbanned + let shadowBanRow = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID); + + let shadowBanned = shadowBanRow.userCount; + + if (!(await isUserTrustworthy(userID))) { + //hide this submission as this user is untrustworthy + shadowBanned = 1; + } + + let startingVotes = 0; + if (vipRow.userCount > 0) { + //this user is a vip, start them at a higher approval rating + startingVotes = 10; + } + + if (duplicateCheck2Row == null) { + //not a duplicate, execute query + try { + db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, startTime, endTime, startingVotes, UUID, userID, timeSubmitted, 0, shadowBanned); + + //add to private db as well + privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted); + + res.sendStatus(200); + } catch (err) { + //a DB change probably occurred + res.sendStatus(502); + console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + startTime + ", " + "endTime" + ", " + userID); + + return; + } + } else { + res.sendStatus(409); + } + + //check if they are a first time user + //if so, send a notification to discord + if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null && duplicateCheck2Row == null) { + let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID); + + // If it is a first time submission + if (userSubmissionCountRow.submissionCount === 0) { + YouTubeAPI.videos.list({ + part: "snippet", + id: videoID + }, function (err, data) { + if (err) { + console.log(err); + return; + } + + request.post(config.discordFirstTimeSubmissionsWebhookURL, { + json: { + "embeds": [{ + "title": data.items[0].snippet.title, + "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2), + "description": "Submission ID: " + UUID + + "\n\nTimestamp: " + + getFormattedTime(startTime) + " to " + getFormattedTime(endTime), + "color": 10813440, + "author": { + "name": userID + }, + "thumbnail": { + "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", + } + }] + } + }); + }); + } + } + } + } + } catch (err) { + console.error(err); + + res.send(500); + } +} diff --git a/src/routes/viewedVideoSponsorTime.js b/src/routes/viewedVideoSponsorTime.js new file mode 100644 index 0000000..9af6960 --- /dev/null +++ b/src/routes/viewedVideoSponsorTime.js @@ -0,0 +1,16 @@ +var db = require('../databases/databases.js').db; + +module.exports = function viewedVideoSponsorTime(req, res) { + let UUID = req.query.UUID; + + if (UUID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //up the view count by one + db.prepare("UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?").run(UUID); + + res.sendStatus(200); +} diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js new file mode 100644 index 0000000..ee8f36e --- /dev/null +++ b/src/routes/voteOnSponsorTime.js @@ -0,0 +1,158 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +var getHash = require('../utils/getHash.js'); +var getIP = require('../utils/getIP.js'); + +var databases = require('../databases/databases.js'); +var db = databases.db; +var privateDB = databases.privateDB; + +module.exports = async function voteOnSponsorTime(req, res) { + let UUID = req.query.UUID; + let userID = req.query.userID; + let type = req.query.type; + + if (UUID == undefined || userID == undefined || type == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + let nonAnonUserID = getHash(userID); + userID = getHash(userID + UUID); + + //x-forwarded-for if this server is behind a proxy + let ip = getIP(req); + + //hash the ip 5000 times so no one can get it from the database + let hashedIP = getHash(ip + config.globalSalt); + + try { + //check if vote has already happened + let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID); + + //-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future + let incrementAmount = 0; + let oldIncrementAmount = 0; + + if (type == 1) { + //upvote + incrementAmount = 1; + } else if (type == 0) { + //downvote + incrementAmount = -1; + } else { + //unrecongnised type of vote + res.sendStatus(400); + return; + } + if (votesRow != undefined) { + if (votesRow.type == 1) { + //upvote + oldIncrementAmount = 1; + } else if (votesRow.type == 0) { + //downvote + oldIncrementAmount = -1; + } else if (votesRow.type == 2) { + //extra downvote + oldIncrementAmount = -4; + } else if (votesRow.type < 0) { + //vip downvote + oldIncrementAmount = votesRow.type; + } + } + + //check if this user is on the vip list + let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID); + + //check if the increment amount should be multiplied (downvotes have more power if there have been many views) + let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID); + + if (vipRow.userCount != 0 && incrementAmount < 0) { + //this user is a vip and a downvote + incrementAmount = - (row.votes + 2 - oldIncrementAmount); + type = incrementAmount; + } else if (row !== undefined && (row.votes > 8 || row.views > 15) && incrementAmount < 0) { + //increase the power of this downvote + incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount)); + type = incrementAmount; + } + + // Send discord message + if (type != 1) { + // Get video ID + let submissionInfoRow = db.prepare("SELECT videoID, userID, startTime, endTime FROM sponsorTimes WHERE UUID = ?").get(UUID); + + let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID); + + if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) { + YouTubeAPI.videos.list({ + part: "snippet", + id: submissionInfoRow.videoID + }, function (err, data) { + if (err) { + console.log(err); + return; + } + + request.post(config.discordReportChannelWebhookURL, { + json: { + "embeds": [{ + "title": data.items[0].snippet.title, + "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID + + "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), + "description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views + + " Views**\n\nSubmission ID: " + UUID + + "\n\nSubmitted by: " + submissionInfoRow.userID + "\n\nTimestamp: " + + getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), + "color": 10813440, + "author": { + "name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "") + }, + "thumbnail": { + "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", + } + }] + } + }); + }); + } + } + + //update the votes table + if (votesRow != undefined) { + privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID); + } else { + privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); + } + + //update the vote count on this sponsorTime + //oldIncrementAmount will be zero is row is null + db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID); + + //for each positive vote, see if a hidden submission can be shown again + if (incrementAmount > 0) { + //find the UUID that submitted the submission that was voted on + let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID; + + //check if any submissions are hidden + let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID); + + if (hiddenSubmissionsRow.hiddenSubmissions > 0) { + //see if some of this users submissions should be visible again + + if (await isUserTrustworthy(submissionUserID)) { + //they are trustworthy again, show 2 of their submissions again, if there are two to show + db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID) + } + } + } + + //added to db + res.sendStatus(200); + } catch (err) { + console.error(err); + } +} \ No newline at end of file diff --git a/src/utils/getFormattedTime.js b/src/utils/getFormattedTime.js new file mode 100644 index 0000000..6f3ef40 --- /dev/null +++ b/src/utils/getFormattedTime.js @@ -0,0 +1,13 @@ +//converts time in seconds to minutes:seconds +module.exports = function getFormattedTime(seconds) { + let minutes = Math.floor(seconds / 60); + let secondsDisplay = Math.round(seconds - minutes * 60); + if (secondsDisplay < 10) { + //add a zero + secondsDisplay = "0" + secondsDisplay; + } + + let formatted = minutes+ ":" + secondsDisplay; + + return formatted; +} \ No newline at end of file diff --git a/src/utils/getHash.js b/src/utils/getHash.js new file mode 100644 index 0000000..a7a23e6 --- /dev/null +++ b/src/utils/getHash.js @@ -0,0 +1,10 @@ +var crypto = require('crypto'); + +module.exports = function (value, times=5000) { + for (let i = 0; i < times; i++) { + let hashCreator = crypto.createHash('sha256'); + value = hashCreator.update(value).digest('hex'); + } + + return value; +} diff --git a/src/utils/getIP.js b/src/utils/getIP.js new file mode 100644 index 0000000..8379ec0 --- /dev/null +++ b/src/utils/getIP.js @@ -0,0 +1,6 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + +module.exports = function getIP(req) { + return config.behindProxy ? req.headers['x-forwarded-for'] : req.connection.remoteAddress; +} \ No newline at end of file From 8543647cbc90d95779abef4cdded54a15b12a47a Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Thu, 19 Mar 2020 23:32:18 +0000 Subject: [PATCH 02/11] added youtube util and cors middleware --- index.js | 25 +++---------------------- src/middleware/cors.js | 5 +++++ src/routes/submitSponsorTimes.js | 1 + src/routes/voteOnSponsorTime.js | 1 + src/utils/youtubeAPI.js | 7 +++++++ 5 files changed, 17 insertions(+), 22 deletions(-) create mode 100644 src/middleware/cors.js create mode 100644 src/utils/youtubeAPI.js diff --git a/index.js b/index.js index e88f988..fe7742a 100644 --- a/index.js +++ b/index.js @@ -4,14 +4,10 @@ var http = require('http'); // Create a service (the app object is just a callback). var app = express(); - let config = JSON.parse(fs.readFileSync('config.json')); - -// Utils -var getHash = require('./src/utils/getHash.js'); -var getIP = require('./src/utils/getIP.js'); -var getFormattedTime = require('./src/utils/getFormattedTime.js'); +// Routes +var corsMiddleware = require('./src/routes/corsMiddleware.js'); // Routes var getVideoSponsorTimes = require('./src/routes/getVideoSponsorTimes.js'); @@ -35,26 +31,11 @@ YouTubeAPI.authenticate({ key: config.youtubeAPIKey }); -var Sqlite3 = require('better-sqlite3'); - -let options = { - readonly: config.readOnly -}; - -//load database -var db = new Sqlite3(config.db, options); -//where the more sensitive data such as IP addresses are stored -var privateDB = new Sqlite3(config.privateDB, options); - // Create an HTTP service. http.createServer(app).listen(config.port); //setup CORS correctly -app.use(function(req, res, next) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); -}); +app.use(corsMiddleware); //add the get function app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes); diff --git a/src/middleware/cors.js b/src/middleware/cors.js new file mode 100644 index 0000000..490d230 --- /dev/null +++ b/src/middleware/cors.js @@ -0,0 +1,5 @@ +module.exports = function corsMiddleware(req, res, next) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); +} \ No newline at end of file diff --git a/src/routes/submitSponsorTimes.js b/src/routes/submitSponsorTimes.js index 84ce219..1841e79 100644 --- a/src/routes/submitSponsorTimes.js +++ b/src/routes/submitSponsorTimes.js @@ -4,6 +4,7 @@ var config = JSON.parse(fs.readFileSync('config.json')); var databases = require('../databases/databases.js'); var db = databases.db; var privateDB = databases.privateDB; +var YouTubeAPI = require('../utils/youtubeAPI.js'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index ee8f36e..8f81ee7 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -7,6 +7,7 @@ var getIP = require('../utils/getIP.js'); var databases = require('../databases/databases.js'); var db = databases.db; var privateDB = databases.privateDB; +var YouTubeAPI = require('../utils/youtubeAPI.js'); module.exports = async function voteOnSponsorTime(req, res) { let UUID = req.query.UUID; diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js new file mode 100644 index 0000000..2f11b64 --- /dev/null +++ b/src/utils/youtubeAPI.js @@ -0,0 +1,7 @@ +// YouTube API +const YouTubeAPI = require("youtube-api"); +YouTubeAPI.authenticate({ + type: "key", + key: config.youtubeAPIKey +}); +module.exports = YouTubeAPI; \ No newline at end of file From 9bf065e1f08cee790b5b36dc41476a096693a9f9 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Thu, 19 Mar 2020 23:38:03 +0000 Subject: [PATCH 03/11] added request debug log without condition --- index.js | 10 +++------- src/middleware/logger.js | 3 +++ src/utils/youtubeAPI.js | 3 +++ 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 src/middleware/logger.js diff --git a/index.js b/index.js index fe7742a..c04416c 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,8 @@ var app = express(); let config = JSON.parse(fs.readFileSync('config.json')); // Routes -var corsMiddleware = require('./src/routes/corsMiddleware.js'); +var corsMiddleware = require('./src/middleware/cors.js'); +var loggerMiddleware = require('./src/middleware/logger.js'); // Routes var getVideoSponsorTimes = require('./src/routes/getVideoSponsorTimes.js'); @@ -24,18 +25,13 @@ var getTopUsers = require('./src/routes/getTopUsers.js'); var getTotalStats = require('./src/routes/getTotalStats.js'); var getDaysSavedFormatted = require('./src/routes/getDaysSavedFormatted.js'); -// YouTube API -const YouTubeAPI = require("youtube-api"); -YouTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey -}); // Create an HTTP service. http.createServer(app).listen(config.port); //setup CORS correctly app.use(corsMiddleware); +app.use(loggerMiddleware); //add the get function app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes); diff --git a/src/middleware/logger.js b/src/middleware/logger.js new file mode 100644 index 0000000..4b26fce --- /dev/null +++ b/src/middleware/logger.js @@ -0,0 +1,3 @@ +module.exports = function logger (req, res, next) { + console.log('Request recieved: ' + req.url); +} \ No newline at end of file diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js index 2f11b64..0cf9d72 100644 --- a/src/utils/youtubeAPI.js +++ b/src/utils/youtubeAPI.js @@ -1,3 +1,6 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + // YouTube API const YouTubeAPI = require("youtube-api"); YouTubeAPI.authenticate({ From be6d75c1ff9adf036e61923e85b93bafa89ff737 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Thu, 19 Mar 2020 23:44:43 +0000 Subject: [PATCH 04/11] add mode condition to logger util --- src/middleware/logger.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/middleware/logger.js b/src/middleware/logger.js index 4b26fce..ed911b2 100644 --- a/src/middleware/logger.js +++ b/src/middleware/logger.js @@ -1,3 +1,7 @@ +var fs = require('fs'); +var config = JSON.parse(fs.readFileSync('config.json')); + module.exports = function logger (req, res, next) { - console.log('Request recieved: ' + req.url); + (config.mode === "development") && console.log('Request recieved: ' + req.url); + next(); } \ No newline at end of file From f1ee7bb430b5eadc9d51c59590c1e4d5a7ffd8e4 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Fri, 20 Mar 2020 21:50:57 +0000 Subject: [PATCH 05/11] Removed console.log --- src/databases/databases.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/databases/databases.js b/src/databases/databases.js index 2f18e52..da0e4aa 100644 --- a/src/databases/databases.js +++ b/src/databases/databases.js @@ -19,8 +19,6 @@ if (!config.readOnly && config.mode === "production") { db.exec("pragma mmap_size= 500000000;"); privateDB.exec("pragma mmap_size= 500000000;"); -console.log('databases.js has been executed...'); - module.exports = { db: db, privateDB: privateDB From 1bff019a64dd8223fdc030a5671b19bc634981c1 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 19:28:21 +0100 Subject: [PATCH 06/11] fixed youtube / discord calls --- src/routes/submitSponsorTimes.js | 5 +++-- src/routes/voteOnSponsorTime.js | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/routes/submitSponsorTimes.js b/src/routes/submitSponsorTimes.js index 1841e79..caffc00 100644 --- a/src/routes/submitSponsorTimes.js +++ b/src/routes/submitSponsorTimes.js @@ -8,6 +8,7 @@ var YouTubeAPI = require('../utils/youtubeAPI.js'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); +var getFormattedTime = require('../utils/getFormattedTime.js'); // TODO: might need to be a util //returns true if the user is considered trustworthy @@ -146,8 +147,8 @@ module.exports = async function submitSponsorTimes(req, res) { part: "snippet", id: videoID }, function (err, data) { - if (err) { - console.log(err); + if (err || data.items.length === 0) { + err && console.log(err); return; } diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index 8f81ee7..6a08a8c 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -3,11 +3,13 @@ var config = JSON.parse(fs.readFileSync('config.json')); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); +var getFormattedTime = require('../utils/getFormattedTime.js'); var databases = require('../databases/databases.js'); var db = databases.db; var privateDB = databases.privateDB; var YouTubeAPI = require('../utils/youtubeAPI.js'); +var request = require('request'); module.exports = async function voteOnSponsorTime(req, res) { let UUID = req.query.UUID; @@ -93,8 +95,8 @@ module.exports = async function voteOnSponsorTime(req, res) { part: "snippet", id: submissionInfoRow.videoID }, function (err, data) { - if (err) { - console.log(err); + if (err || data.items.length === 0) { + err && console.log(err); return; } @@ -117,7 +119,7 @@ module.exports = async function voteOnSponsorTime(req, res) { } }] } - }); + }, () => {}); }); } } From 5369d48eae483e45086a0ab5c464fbe283b4119c Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 21:04:04 +0100 Subject: [PATCH 07/11] Added testing --- index.js | 89 +- package-lock.json | 1540 +++++++++++++++++++-------- package.json | 5 +- src/app.js | 82 ++ src/config.js | 12 + src/databases/databases.js | 9 +- src/middleware/logger.js | 2 +- src/routes/addUserAsVIP.js | 2 +- src/routes/getVideoSponsorTimes.js | 2 +- src/routes/setUsername.js | 3 +- src/routes/shadowBanUser.js | 3 +- src/routes/submitSponsorTimes.js | 3 +- src/routes/voteOnSponsorTime.js | 3 +- src/utils/getHash.js | 2 + src/utils/youtubeAPI.js | 3 +- test.js | 35 + test.json | 17 + test/cases/getHash.js | 29 + test/databases/_private.db.sql | 22 + test/databases/_sponsorTimes.db.sql | 26 + test/databases/private.db | Bin 0 -> 24576 bytes test/databases/sponsorTimes.db | Bin 0 -> 28672 bytes test/mocks.js | 16 + 23 files changed, 1388 insertions(+), 517 deletions(-) create mode 100644 src/app.js create mode 100644 src/config.js create mode 100644 test.js create mode 100644 test.json create mode 100644 test/cases/getHash.js create mode 100644 test/databases/_private.db.sql create mode 100644 test/databases/_sponsorTimes.db.sql create mode 100644 test/databases/private.db create mode 100644 test/databases/sponsorTimes.db create mode 100644 test/mocks.js diff --git a/index.js b/index.js index c04416c..5e4b977 100644 --- a/index.js +++ b/index.js @@ -1,84 +1,5 @@ -var express = require('express'); -var fs = require('fs'); -var http = require('http'); -// Create a service (the app object is just a callback). -var app = express(); - -let config = JSON.parse(fs.readFileSync('config.json')); - -// Routes -var corsMiddleware = require('./src/middleware/cors.js'); -var loggerMiddleware = require('./src/middleware/logger.js'); - -// Routes -var getVideoSponsorTimes = require('./src/routes/getVideoSponsorTimes.js'); -var submitSponsorTimes = require('./src/routes/submitSponsorTimes.js'); -var voteOnSponsorTime = require('./src/routes/voteOnSponsorTime.js'); -var viewedVideoSponsorTime = require('./src/routes/viewedVideoSponsorTime.js'); -var setUsername = require('./src/routes/setUsername.js'); -var getUsername = require('./src/routes/getUsername.js'); -var shadowBanUser = require('./src/routes/shadowBanUser.js'); -var addUserAsVIP = require('./src/routes/addUserAsVIP.js'); -var getSavedTimeForUser = require('./src/routes/getSavedTimeForUser.js'); -var getViewsForUser = require('./src/routes/getViewsForUser.js'); -var getTopUsers = require('./src/routes/getTopUsers.js'); -var getTotalStats = require('./src/routes/getTotalStats.js'); -var getDaysSavedFormatted = require('./src/routes/getDaysSavedFormatted.js'); - - -// Create an HTTP service. -http.createServer(app).listen(config.port); - -//setup CORS correctly -app.use(corsMiddleware); -app.use(loggerMiddleware); - -//add the get function -app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes); - -//add the post function -app.get('/api/postVideoSponsorTimes', submitSponsorTimes); -app.post('/api/postVideoSponsorTimes', submitSponsorTimes); - -//voting endpoint -app.get('/api/voteOnSponsorTime', voteOnSponsorTime); -app.post('/api/voteOnSponsorTime', voteOnSponsorTime); - -//Endpoint when a sponsorTime is used up -app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); -app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); - -//To set your username for the stats view -app.post('/api/setUsername', setUsername); - -//get what username this user has -app.get('/api/getUsername', getUsername); - -//Endpoint used to hide a certain user's data -app.post('/api/shadowBanUser', shadowBanUser); - -//Endpoint used to make a user a VIP user with special privileges -app.post('/api/addUserAsVIP', addUserAsVIP); - -//Gets all the views added up for one userID -//Useful to see how much one user has contributed -app.get('/api/getViewsForUser', getViewsForUser); - -//Gets all the saved time added up (views * sponsor length) for one userID -//Useful to see how much one user has contributed -//In minutes -app.get('/api/getSavedTimeForUser', getSavedTimeForUser); - -app.get('/api/getTopUsers', getTopUsers); - -//send out totals -//send the total submissions, total views and total minutes saved -app.get('/api/getTotalStats', getTotalStats); - -//send out a formatted time saved total -app.get('/api/getdayssavedformatted', getDaysSavedFormatted); - -app.get('/database.db', function (req, res) { - res.sendfile("./databases/sponsortimes.db", { root: __dirname }); -}); - +var config = require('./src/config.js'); +var createServer = require('./src/app.js'); +var server = createServer(() => { + console.log("Server started."); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 493c47c..f0a6d9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,16 +13,11 @@ "negotiator": "0.6.2" } }, - "ajv": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", - "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true }, "ansi-regex": { "version": "2.1.1", @@ -34,6 +29,25 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -48,29 +62,30 @@ } }, "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base64url": { "version": "1.0.6", @@ -98,6 +113,12 @@ "tar": "^4.4.10" } }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, "bl": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", @@ -106,10 +127,10 @@ "readable-stream": "~2.0.5" }, "dependencies": { - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.0.6", @@ -123,11 +144,6 @@ "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, @@ -156,6 +172,31 @@ "hoek": "2.x.x" } }, + "brace-expansion": { + "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" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -181,9 +222,9 @@ } }, "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" }, "chalk": { "version": "1.1.3", @@ -197,10 +238,80 @@ "supports-color": "^2.0.0" } }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -215,6 +326,12 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "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 + }, "concat-stream": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", @@ -223,29 +340,6 @@ "inherits": "~2.0.1", "readable-stream": "~1.1.9", "typedarray": "~0.0.5" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } } }, "content-disposition": { @@ -254,6 +348,13 @@ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", "requires": { "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "content-type": { @@ -290,6 +391,13 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } } }, "debug": { @@ -300,6 +408,21 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -315,6 +438,12 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -337,11 +466,47 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -352,6 +517,12 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -392,6 +563,13 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "extend": { @@ -404,15 +582,14 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } }, "finalhandler": { "version": "1.1.2", @@ -428,19 +605,47 @@ "unpipe": "~1.0.0" } }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "async": "^2.0.1", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.11" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + } } }, "forwarded": { @@ -454,13 +659,32 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-minipass": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", - "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "gapitoken": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", @@ -486,6 +710,12 @@ "is-property": "^1.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -497,6 +727,36 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" } }, "google-auth-library": { @@ -512,82 +772,16 @@ "string-template": "~0.2.0" }, "dependencies": { - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, "async": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz", "integrity": "sha1-bJ7csRztTw3S8tQNsNSaEJwIiqs=" }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", - "requires": { - "async": "^2.0.1", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - } - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, "qs": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", @@ -633,11 +827,6 @@ "requires": { "punycode": "^1.4.1" } - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" } } }, @@ -660,119 +849,14 @@ "request": "~2.72.0", "string-template": "~1.0.0", "url": "^0.11.0" - }, - "dependencies": { - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", - "requires": { - "async": "^2.0.1", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - } - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "qs": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.2.tgz", - "integrity": "sha1-tZ2JJdDJme9tY6z0rFq7CtqiS1Q=" - }, - "request": { - "version": "2.72.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz", - "integrity": "sha1-DOOheVEmILEEQfFMguIcEsDdtOE=", - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "bl": "~1.1.2", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~1.0.0-rc3", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "node-uuid": "~1.4.7", - "oauth-sign": "~0.8.1", - "qs": "~6.1.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.2.0", - "tunnel-agent": "~0.4.1" - } - }, - "tough-cookie": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", - "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" - } } }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "gtoken": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-1.2.3.tgz", @@ -784,18 +868,24 @@ "request": "^2.72.0" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -806,6 +896,18 @@ "ansi-regex": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -817,6 +919,12 @@ "sntp": "1.x.x" } }, + "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 + }, "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", @@ -840,11 +948,11 @@ } }, "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "^0.2.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } @@ -865,13 +973,16 @@ "get-stdin": "^4.0.1", "minimist": "^1.1.0", "repeating": "^1.1.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } + } + }, + "inflight": { + "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" } }, "inherits": { @@ -885,16 +996,61 @@ "integrity": "sha512-vBtiSgrEiNocWvvZX1RVfeOKa2mCHLZQ2p9nkQkQZ/BvEiY+6CcUz0eyjvIiewjJoeNidzg2I+tpPJvpyspL1w==" }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "is-extglob": "^2.1.1" } }, "is-my-ip-valid": { @@ -914,26 +1070,66 @@ "xtend": "^4.0.0" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -944,11 +1140,6 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -968,6 +1159,13 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } } }, "jwa": { @@ -996,6 +1194,16 @@ "jwa": "~1.0.0" } }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", @@ -1006,6 +1214,46 @@ "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -1025,18 +1273,6 @@ "indent-string": "^1.1.0", "minimist": "^1.1.0", "object-assign": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "object-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", - "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" - } } }, "merge-descriptors": { @@ -1055,46 +1291,122 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.43.0" + } + }, + "minimatch": { + "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" } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.3", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "ms": { @@ -1107,20 +1419,70 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, "node-forge": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", + "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } }, "on-finished": { "version": "2.3.0", @@ -1130,20 +1492,66 @@ "ee-first": "1.1.1" } }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "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 + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true }, "pinkie": { "version": "2.0.4", @@ -1158,24 +1566,24 @@ "pinkie": "^2.0.0" } }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "ipaddr.js": "1.9.1" } }, - "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" - }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { "version": "6.7.0", @@ -1203,6 +1611,26 @@ "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", @@ -1212,49 +1640,73 @@ } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.72.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz", + "integrity": "sha1-DOOheVEmILEEQfFMguIcEsDdtOE=", "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "bl": "~1.1.2", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", + "form-data": "~1.0.0-rc3", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "mime-types": "~2.1.7", + "node-uuid": "~1.4.7", + "oauth-sign": "~0.8.1", + "qs": "~6.1.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.2.0", + "tunnel-agent": "~0.4.1" }, "dependencies": { + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.2.tgz", + "integrity": "sha1-tZ2JJdDJme9tY6z0rFq7CtqiS1Q=" } } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -1293,6 +1745,12 @@ "send": "0.17.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -1306,6 +1764,12 @@ "hoek": "2.x.x" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -1320,6 +1784,13 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } } }, "statuses": { @@ -1332,6 +1803,80 @@ "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz", "integrity": "sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=" }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", + "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", + "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, "stringstream": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", @@ -1345,53 +1890,54 @@ "ansi-regex": "^2.0.0" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.5", + "minipass": "^2.8.6", "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.3" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" }, "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" }, "tweetnacl": { "version": "0.14.5", @@ -1417,14 +1963,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -1452,9 +1990,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "vary": { "version": "1.1.2", @@ -1469,17 +2007,183 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } }, "youtube-api": { "version": "2.0.10", diff --git a/package.json b/package.json index 46fc897..ba724c9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Server that holds the SponsorBlock database", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "node test.js", "start": "node index.js" }, "author": "Ajay Ramachandran", @@ -15,5 +15,8 @@ "http": "0.0.0", "uuid": "^3.3.2", "youtube-api": "^2.0.10" + }, + "devDependencies": { + "mocha": "^7.1.1" } } diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..5924715 --- /dev/null +++ b/src/app.js @@ -0,0 +1,82 @@ +var express = require('express'); +// Create a service (the app object is just a callback). +var app = express(); +var config = require('./config.js'); + +// Middleware +var corsMiddleware = require('./middleware/cors.js'); +var loggerMiddleware = require('./middleware/logger.js'); + +// Routes +var getVideoSponsorTimes = require('./routes/getVideoSponsorTimes.js'); +var submitSponsorTimes = require('./routes/submitSponsorTimes.js'); +var voteOnSponsorTime = require('./routes/voteOnSponsorTime.js'); +var viewedVideoSponsorTime = require('./routes/viewedVideoSponsorTime.js'); +var setUsername = require('./routes/setUsername.js'); +var getUsername = require('./routes/getUsername.js'); +var shadowBanUser = require('./routes/shadowBanUser.js'); +var addUserAsVIP = require('./routes/addUserAsVIP.js'); +var getSavedTimeForUser = require('./routes/getSavedTimeForUser.js'); +var getViewsForUser = require('./routes/getViewsForUser.js'); +var getTopUsers = require('./routes/getTopUsers.js'); +var getTotalStats = require('./routes/getTotalStats.js'); +var getDaysSavedFormatted = require('./routes/getDaysSavedFormatted.js'); + + +//setup CORS correctly +app.use(corsMiddleware); +app.use(loggerMiddleware); + +//add the get function +app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes); + +//add the post function +app.get('/api/postVideoSponsorTimes', submitSponsorTimes); +app.post('/api/postVideoSponsorTimes', submitSponsorTimes); + +//voting endpoint +app.get('/api/voteOnSponsorTime', voteOnSponsorTime); +app.post('/api/voteOnSponsorTime', voteOnSponsorTime); + +//Endpoint when a sponsorTime is used up +app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); +app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); + +//To set your username for the stats view +app.post('/api/setUsername', setUsername); + +//get what username this user has +app.get('/api/getUsername', getUsername); + +//Endpoint used to hide a certain user's data +app.post('/api/shadowBanUser', shadowBanUser); + +//Endpoint used to make a user a VIP user with special privileges +app.post('/api/addUserAsVIP', addUserAsVIP); + +//Gets all the views added up for one userID +//Useful to see how much one user has contributed +app.get('/api/getViewsForUser', getViewsForUser); + +//Gets all the saved time added up (views * sponsor length) for one userID +//Useful to see how much one user has contributed +//In minutes +app.get('/api/getSavedTimeForUser', getSavedTimeForUser); + +app.get('/api/getTopUsers', getTopUsers); + +//send out totals +//send the total submissions, total views and total minutes saved +app.get('/api/getTotalStats', getTotalStats); + +//send out a formatted time saved total +app.get('/api/getdayssavedformatted', getDaysSavedFormatted); + +app.get('/database.db', function (req, res) { + res.sendfile("./databases/sponsortimes.db", { root: __dirname }); +}); + +// Create an HTTP service. +module.exports = function createServer (callback) { + return app.listen(config.port, callback); +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..1bc3721 --- /dev/null +++ b/src/config.js @@ -0,0 +1,12 @@ + +var fs = require('fs'); +var config = undefined; + +// Check to see if launched in test mode +if (process.env.npm_lifecycle_script === 'node test.js') { + config = JSON.parse(fs.readFileSync('test.json')); +} else { + config = JSON.parse(fs.readFileSync('config.json')); +} + +module.exports = config; \ No newline at end of file diff --git a/src/databases/databases.js b/src/databases/databases.js index da0e4aa..8676292 100644 --- a/src/databases/databases.js +++ b/src/databases/databases.js @@ -1,6 +1,6 @@ -var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var Sqlite3 = require('better-sqlite3'); +var fs = require('fs'); let options = { readonly: config.readOnly @@ -9,6 +9,11 @@ let options = { var db = new Sqlite3(config.db, options); var privateDB = new Sqlite3(config.privateDB, options); +if (config.createDatabaseIfNotExist && !config.readOnly) { + if (fs.existsSync(config.dbSchema)) db.exec(fs.readFileSync(config.dbSchema).toString()); + if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString()); +} + // Enable WAL mode checkpoint number if (!config.readOnly && config.mode === "production") { db.exec("PRAGMA journal_mode=WAL;"); diff --git a/src/middleware/logger.js b/src/middleware/logger.js index ed911b2..23ffb16 100644 --- a/src/middleware/logger.js +++ b/src/middleware/logger.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); module.exports = function logger (req, res, next) { (config.mode === "development") && console.log('Request recieved: ' + req.url); diff --git a/src/routes/addUserAsVIP.js b/src/routes/addUserAsVIP.js index 6f55090..a5eadd3 100644 --- a/src/routes/addUserAsVIP.js +++ b/src/routes/addUserAsVIP.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var db = require('../databases/databases.js').db; var getHash = require('../utils/getHash.js'); diff --git a/src/routes/getVideoSponsorTimes.js b/src/routes/getVideoSponsorTimes.js index 746a342..67447bc 100644 --- a/src/routes/getVideoSponsorTimes.js +++ b/src/routes/getVideoSponsorTimes.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var databases = require('../databases/databases.js'); var db = databases.db; diff --git a/src/routes/setUsername.js b/src/routes/setUsername.js index 2278ec4..666afb1 100644 --- a/src/routes/setUsername.js +++ b/src/routes/setUsername.js @@ -1,6 +1,5 @@ -var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var db = require('../databases/databases.js').db; var getHash = require('../utils/getHash.js'); diff --git a/src/routes/shadowBanUser.js b/src/routes/shadowBanUser.js index ff028f1..6eb2d21 100644 --- a/src/routes/shadowBanUser.js +++ b/src/routes/shadowBanUser.js @@ -1,5 +1,4 @@ -var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var databases = require('../databases/databases.js'); var db = databases.db; diff --git a/src/routes/submitSponsorTimes.js b/src/routes/submitSponsorTimes.js index caffc00..30b1f0f 100644 --- a/src/routes/submitSponsorTimes.js +++ b/src/routes/submitSponsorTimes.js @@ -1,5 +1,4 @@ -var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var databases = require('../databases/databases.js'); var db = databases.db; diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index 6a08a8c..1cd17a7 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); @@ -99,6 +99,7 @@ module.exports = async function voteOnSponsorTime(req, res) { err && console.log(err); return; } + console.log(config.test); request.post(config.discordReportChannelWebhookURL, { json: { diff --git a/src/utils/getHash.js b/src/utils/getHash.js index a7a23e6..d5b3130 100644 --- a/src/utils/getHash.js +++ b/src/utils/getHash.js @@ -1,6 +1,8 @@ var crypto = require('crypto'); module.exports = function (value, times=5000) { + if (times <= 0) return ""; + for (let i = 0; i < times; i++) { let hashCreator = crypto.createHash('sha256'); value = hashCreator.update(value).digest('hex'); diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js index 0cf9d72..7449815 100644 --- a/src/utils/youtubeAPI.js +++ b/src/utils/youtubeAPI.js @@ -1,5 +1,4 @@ -var fs = require('fs'); -var config = JSON.parse(fs.readFileSync('config.json')); +var config = require('../config.js'); // YouTube API const YouTubeAPI = require("youtube-api"); diff --git a/test.js b/test.js new file mode 100644 index 0000000..0241386 --- /dev/null +++ b/test.js @@ -0,0 +1,35 @@ +var Mocha = require('mocha'), + fs = require('fs'), + path = require('path'); + +var createServer = require('./src/app.js'); +var createMockServer = require('./test/mocks.js'); + +// Instantiate a Mocha instance. +var mocha = new Mocha(); + +var testDir = './test/cases' + +// Add each .js file to the mocha instance +fs.readdirSync(testDir).filter(function(file) { + // Only keep the .js files + return file.substr(-3) === '.js'; + +}).forEach(function(file) { + mocha.addFile( + path.join(testDir, file) + ); +}); + +var mockServer = createMockServer(() => { + console.log("Started mock HTTP Server"); + var server = createServer(() => { + console.log("Started main HTTP server"); + // Run the tests. + mocha.run(function(failures) { + mockServer.close(); + server.close(); + process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures + }); + }); +}); diff --git a/test.json b/test.json new file mode 100644 index 0000000..9b57e70 --- /dev/null +++ b/test.json @@ -0,0 +1,17 @@ +{ + "port": 8080, + "mockPort": 8081, + "globalSalt": "testSalt", + "adminUserID": "testUserId", + "youtubeAPIKey": "", + "discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook", + "discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook", + "behindProxy": true, + "db": "./test/databases/sponsorTimes.db", + "privateDB": "./test/databases/private.db", + "createDatabaseIfNotExist": true, + "dbSchema": "./test/databases/_sponsorTimes.db.sql", + "privateDBSchema": "./test/databases/_private.db.sql", + "mode": "development", + "readOnly": false +} diff --git a/test/cases/getHash.js b/test/cases/getHash.js new file mode 100644 index 0000000..15d7cc5 --- /dev/null +++ b/test/cases/getHash.js @@ -0,0 +1,29 @@ +var getHash = require('../../src/utils/getHash.js'); + +var assert = require('assert'); +describe('getHash', () => { + it('Should not output the input string', () => { + assert(getHash("test") !== "test"); + assert(getHash("test", -1) !== "test"); + assert(getHash("test", 0) !== "test"); + assert(getHash("test", null) !== "test"); + }); + + it('Should return a hashed value', () => { + assert.equal(getHash("test"), "2f327ef967ade1ebf4319163f7debbda9cc17bb0c8c834b00b30ca1cf1c256ee"); + }); + + it ('Should take a variable number of passes', () => { + assert.equal(getHash("test", 1), "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); + assert.equal(getHash("test", 2), "7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c"); + assert.equal(getHash("test", 3), "5b24f7aa99f1e1da5698a4f91ae0f4b45651a1b625c61ed669dd25ff5b937972"); + }); + + it ('Should default to 5000 passes', () => { + assert.equal(getHash("test"), getHash("test", 5000)); + }); + + it ('Should not take a negative number of passes', () => { + assert.equal(getHash("test", -1), ""); + }); +}); \ No newline at end of file diff --git a/test/databases/_private.db.sql b/test/databases/_private.db.sql new file mode 100644 index 0000000..5179803 --- /dev/null +++ b/test/databases/_private.db.sql @@ -0,0 +1,22 @@ +BEGIN TRANSACTION; +DROP TABLE "shadowBannedUsers"; +DROP TABLE "votes"; +DROP TABLE "sponsorTimes"; + +CREATE TABLE IF NOT EXISTS "shadowBannedUsers" ( + "userID" TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS "votes" ( + "UUID" TEXT NOT NULL, + "userID" INTEGER NOT NULL, + "hashedIP" INTEGER NOT NULL, + "type" INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS "sponsorTimes" ( + "videoID" TEXT NOT NULL, + "hashedIP" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS sponsorTimes_hashedIP on sponsorTimes(hashedIP); +CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID); +COMMIT; diff --git a/test/databases/_sponsorTimes.db.sql b/test/databases/_sponsorTimes.db.sql new file mode 100644 index 0000000..9896332 --- /dev/null +++ b/test/databases/_sponsorTimes.db.sql @@ -0,0 +1,26 @@ +BEGIN TRANSACTION; +DROP TABLE "vipUsers"; +DROP TABLE "sponsorTimes"; +DROP TABLE "userNames"; + +CREATE TABLE IF NOT EXISTS "vipUsers" ( + "userID" TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS "sponsorTimes" ( + "videoID" TEXT NOT NULL, + "startTime" REAL NOT NULL, + "endTime" REAL NOT NULL, + "votes" INTEGER NOT NULL, + "UUID" TEXT NOT NULL UNIQUE, + "userID" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL, + "views" INTEGER NOT NULL, + "shadowHidden" INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS "userNames" ( + "userID" TEXT NOT NULL, + "userName" TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS sponsorTimes_videoID on sponsorTimes(videoID); +CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID); +COMMIT; \ No newline at end of file diff --git a/test/databases/private.db b/test/databases/private.db new file mode 100644 index 0000000000000000000000000000000000000000..086e4095af891d8eda34992bb79887196d0c6698 GIT binary patch literal 24576 zcmeI&Pj4GV6aa8g;J^pS$3O_FLPFwzcx%^{)1;R=mzKBI+T$7ToA-XRK8%ywH@6N3 zA&tG0Lo-Ne>E%+Al-^3yQmNF0a{01BW03ZMWATqw}0EHu^{$wvoAK74tMgQ>GW{gI~u=V-`#C*H1CwF4Y`&KE2B95_#~&{ym>yW4#Ayy+o3A$3V6q1TV?PViADV7g9?j9ceq*bZu1*iFrt8Z~ ztM?Cl=(RUim%6RJZo1upb9Za&4H%hBb-~a8YPx;T9v%z^!LKg0x4W%(TDKlwz1$pL zovSt)jpXZBM%j(bMo;Cl`HcF;i4=ktOf_DI^K+cd@W~8{(ZPe`aQ?dG=G~;)xONRz zX$-e-eD8}J=IAK+U0Cye|H+QIvnih&AE!Ub^901BW03ZMWA zJhQ-Xv%EO06_3TtY;9+6OBcF$%^6Q8p4Q2~&a_ zo*N^Si-K9rc%YVO&$-Vt??j|AMoOW@AU2^{mI?23<|L6q8%gsJA-ZRlOwRbmyT)&7e)?M2d$-45jsMYhU9`n zS(-!@nRQU>jHSFZSG`g!%2Kgu<-}W+NhS!>#wsq92iicj_uMflGeM;ZIz;PCE=?{% zFkW*Z3AGmJXU=9bc?7#;t+245Oc?7F2LLW9qeLnO(=w`guC>y6Mg*+9kdOiOa8C$k zG9!e_M9x%{o<;?W$wjo@P%br;6)D^*A&VUD07y7S0k?#q#xt%w&nVBB%<@@Fnf(2K z<)@X8S8Da2>v!u{YroZc&;0$t3sC?CPyhu`00mG01yBG5PyhuAf%g|HuMcY#f9KO) zy!qY-hkI_;4P5fM_~*Mnlfew5?v zDgHj7JDu`n2JAolwKIR20ej#4*_pf4fVI<3a)|-o{r9WW$^IX|o+y9BE%7Qm-H_I%0XCOB(0fgKqcVLp!f>iWpw&U1&s7K z_0i$*U^KkGyMHZ?uI}w^ZO#_d|9pAy#O|QKwX-?6(x0uVZtV{bP82>^w%>SZso5*m zvq^SV{*C<*hbL7uZ?EF`?lW4B%Qp@do4tz{v&q9_>t2aRqj-JCLN9J58pon{pZL@E zpucvje69arr(I>YHrLv_gDboJooC_P+unZUxS|wvs;aZ`_rXH5x4xeJWY1ACF`Bb;&pO<7>B3I{KRp!`GhN4%vL= z)8gN7dS|0sO1-E2sNVG(aGgD?3j9CO=@+hD@tvQEWsG}j zq>V&);gX;+q(UoMD5(UY$|b{@3DGG*t%f>DwM*0~Dzq$E;!MS4q_QE>2<TTw^30A z25R_AT8knGO{IuMVOcCFSZsm?n+uyvVuCuYbTDLY(+Z|cs4uy{(s*cPA)Xgn6^Z1(99YV3>P-R-I^-! zk>!ah*enH%$$|!2NELnLG6nAgrBq8#U7^yQFzsLOPA)7huVyEx)yn~4Ea>%KYo2&A zq);|lDK!s*0-8_|&AIR_!dL;5yx@dL7gRDH8qaUqS7ZL%usG>v^RAz@Dk<}>Cp9Z6^Dg}~Dk<}>(kxX{=3Plytfb7l+)=Nj z%)2zPunC@S&%25+-TzL4jsMNpbE2k{>eevw3i&>>=j2oS{r@iGjmZpitX`((&EHvw@?Xul;{~zQ3&-JiC od8ZiF<*MCLe7u`xAZU6uP literal 0 HcmV?d00001 diff --git a/test/mocks.js b/test/mocks.js new file mode 100644 index 0000000..a124646 --- /dev/null +++ b/test/mocks.js @@ -0,0 +1,16 @@ +var express = require('express'); +var app = express(); + +var config = require('../src/config.js'); + +app.post('/ReportChannelWebhook', (req, res) => { + res.status(200); +}); + +app.post('/FirstTimeSubmissionsWebhook', (req, res) => { + res.status(200); +}); + +module.exports = function createMockServer(callback) { + return app.listen(config.mockPort, callback); +} \ No newline at end of file From e2f430dd0978f0f79ee26347f443b6c15be27d7a Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 21:08:48 +0100 Subject: [PATCH 08/11] removed db files from test --- .gitignore | 2 ++ test/databases/private.db | Bin 24576 -> 0 bytes test/databases/sponsorTimes.db | Bin 28672 -> 0 bytes 3 files changed, 2 insertions(+) delete mode 100644 test/databases/private.db delete mode 100644 test/databases/sponsorTimes.db diff --git a/.gitignore b/.gitignore index b1d8d3a..8fa7201 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,8 @@ typings/ databases/sponsorTimes.db databases/private.db databases/sponsorTimesReal.db +test/databases/sponsorTimes.db +test/databases/private.db # Config files config.json \ No newline at end of file diff --git a/test/databases/private.db b/test/databases/private.db deleted file mode 100644 index 086e4095af891d8eda34992bb79887196d0c6698..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI&Pj4GV6aa8g;J^pS$3O_FLPFwzcx%^{)1;R=mzKBI+T$7ToA-XRK8%ywH@6N3 zA&tG0Lo-Ne>E%+Al-^3yQmNF0a{01BW03ZMWATqw}0EHu^{$wvoAK74tMgQ>GW{gI~u=V-`#C*H1CwF4Y`&KE2B95_#~&{ym>yW4#Ayy+o3A$3V6q1TV?PViADV7g9?j9ceq*bZu1*iFrt8Z~ ztM?Cl=(RUim%6RJZo1upb9Za&4H%hBb-~a8YPx;T9v%z^!LKg0x4W%(TDKlwz1$pL zovSt)jpXZBM%j(bMo;Cl`HcF;i4=ktOf_DI^K+cd@W~8{(ZPe`aQ?dG=G~;)xONRz zX$-e-eD8}J=IAK+U0Cye|H+QIvnih&AE!Ub^901BW03ZMWA zJhQ-Xv%EO06_3TtY;9+6OBcF$%^6Q8p4Q2~&a_ zo*N^Si-K9rc%YVO&$-Vt??j|AMoOW@AU2^{mI?23<|L6q8%gsJA-ZRlOwRbmyT)&7e)?M2d$-45jsMYhU9`n zS(-!@nRQU>jHSFZSG`g!%2Kgu<-}W+NhS!>#wsq92iicj_uMflGeM;ZIz;PCE=?{% zFkW*Z3AGmJXU=9bc?7#;t+245Oc?7F2LLW9qeLnO(=w`guC>y6Mg*+9kdOiOa8C$k zG9!e_M9x%{o<;?W$wjo@P%br;6)D^*A&VUD07y7S0k?#q#xt%w&nVBB%<@@Fnf(2K z<)@X8S8Da2>v!u{YroZc&;0$t3sC?CPyhu`00mG01yBG5PyhuAf%g|HuMcY#f9KO) zy!qY-hkI_;4P5fM_~*Mnlfew5?v zDgHj7JDu`n2JAolwKIR20ej#4*_pf4fVI<3a)|-o{r9WW$^IX|o+y9BE%7Qm-H_I%0XCOB(0fgKqcVLp!f>iWpw&U1&s7K z_0i$*U^KkGyMHZ?uI}w^ZO#_d|9pAy#O|QKwX-?6(x0uVZtV{bP82>^w%>SZso5*m zvq^SV{*C<*hbL7uZ?EF`?lW4B%Qp@do4tz{v&q9_>t2aRqj-JCLN9J58pon{pZL@E zpucvje69arr(I>YHrLv_gDboJooC_P+unZUxS|wvs;aZ`_rXH5x4xeJWY1ACF`Bb;&pO<7>B3I{KRp!`GhN4%vL= z)8gN7dS|0sO1-E2sNVG(aGgD?3j9CO=@+hD@tvQEWsG}j zq>V&);gX;+q(UoMD5(UY$|b{@3DGG*t%f>DwM*0~Dzq$E;!MS4q_QE>2<TTw^30A z25R_AT8knGO{IuMVOcCFSZsm?n+uyvVuCuYbTDLY(+Z|cs4uy{(s*cPA)Xgn6^Z1(99YV3>P-R-I^-! zk>!ah*enH%$$|!2NELnLG6nAgrBq8#U7^yQFzsLOPA)7huVyEx)yn~4Ea>%KYo2&A zq);|lDK!s*0-8_|&AIR_!dL;5yx@dL7gRDH8qaUqS7ZL%usG>v^RAz@Dk<}>Cp9Z6^Dg}~Dk<}>(kxX{=3Plytfb7l+)=Nj z%)2zPunC@S&%25+-TzL4jsMNpbE2k{>eevw3i&>>=j2oS{r@iGjmZpitX`((&EHvw@?Xul;{~zQ3&-JiC od8ZiF<*MCLe7u`xAZU6uP From 30d24de4d3d28d201a76889cbc0a79fc7890f30f Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 21:29:13 +0100 Subject: [PATCH 09/11] added http tests --- src/routes/voteOnSponsorTime.js | 3 +-- test/cases/submitSponsorTimes.js | 26 ++++++++++++++++++++++++++ test/databases/_private.db.sql | 6 +++--- test/databases/_sponsorTimes.db.sql | 6 +++--- test/utils.js | 7 +++++++ 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/cases/submitSponsorTimes.js create mode 100644 test/utils.js diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index 1cd17a7..79c73d6 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -99,7 +99,6 @@ module.exports = async function voteOnSponsorTime(req, res) { err && console.log(err); return; } - console.log(config.test); request.post(config.discordReportChannelWebhookURL, { json: { @@ -120,7 +119,7 @@ module.exports = async function voteOnSponsorTime(req, res) { } }] } - }, () => {}); + }); }); } } diff --git a/test/cases/submitSponsorTimes.js b/test/cases/submitSponsorTimes.js new file mode 100644 index 0000000..181b550 --- /dev/null +++ b/test/cases/submitSponsorTimes.js @@ -0,0 +1,26 @@ +var assert = require('assert'); +var request = require('request'); + +var utils = require('../utils.js'); + +describe('postVideoSponsorTime', () => { + it('Should be able to create a time', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test", null, + (err, res, body) => { + if (err) done(false); + else if (res.statusCode === 200) done(); + else done(false); + }); + }); + + it('Should return 400 for missing params', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, + (err, res, body) => { + if (err) done(false); + if (res.statusCode === 400) done(); + else done(false); + }); + }); +}); \ No newline at end of file diff --git a/test/databases/_private.db.sql b/test/databases/_private.db.sql index 5179803..317603a 100644 --- a/test/databases/_private.db.sql +++ b/test/databases/_private.db.sql @@ -1,7 +1,7 @@ BEGIN TRANSACTION; -DROP TABLE "shadowBannedUsers"; -DROP TABLE "votes"; -DROP TABLE "sponsorTimes"; +DROP TABLE IF EXISTS "shadowBannedUsers"; +DROP TABLE IF EXISTS "votes"; +DROP TABLE IF EXISTS "sponsorTimes"; CREATE TABLE IF NOT EXISTS "shadowBannedUsers" ( "userID" TEXT NOT NULL diff --git a/test/databases/_sponsorTimes.db.sql b/test/databases/_sponsorTimes.db.sql index 9896332..bbf501e 100644 --- a/test/databases/_sponsorTimes.db.sql +++ b/test/databases/_sponsorTimes.db.sql @@ -1,7 +1,7 @@ BEGIN TRANSACTION; -DROP TABLE "vipUsers"; -DROP TABLE "sponsorTimes"; -DROP TABLE "userNames"; +DROP TABLE IF EXISTS "vipUsers"; +DROP TABLE IF EXISTS "sponsorTimes"; +DROP TABLE IF EXISTS "userNames"; CREATE TABLE IF NOT EXISTS "vipUsers" ( "userID" TEXT NOT NULL diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..1067f3f --- /dev/null +++ b/test/utils.js @@ -0,0 +1,7 @@ +var config = require('../src/config.js'); + +module.exports = { + getbaseURL: () => { + return "http://localhost:" + config.port; + } +}; \ No newline at end of file From 7bf43cfe9ade9a37fe4bad75a9a83db079e2ac60 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 22:02:59 +0100 Subject: [PATCH 10/11] You can insert into the test db in the test scripts --- test.json | 2 +- test/cases/getSponsorTime.js | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 test/cases/getSponsorTime.js diff --git a/test.json b/test.json index 9b57e70..f07fbf8 100644 --- a/test.json +++ b/test.json @@ -12,6 +12,6 @@ "createDatabaseIfNotExist": true, "dbSchema": "./test/databases/_sponsorTimes.db.sql", "privateDBSchema": "./test/databases/_private.db.sql", - "mode": "development", + "mode": "test", "readOnly": false } diff --git a/test/cases/getSponsorTime.js b/test/cases/getSponsorTime.js new file mode 100644 index 0000000..8c61504 --- /dev/null +++ b/test/cases/getSponsorTime.js @@ -0,0 +1,53 @@ +var request = require('request'); +var db = require('../../src/databases/databases.js').db; +var utils = require('../utils.js'); + + +/* + *CREATE TABLE IF NOT EXISTS "sponsorTimes" ( + "videoID" TEXT NOT NULL, + "startTime" REAL NOT NULL, + "endTime" REAL NOT NULL, + "votes" INTEGER NOT NULL, + "UUID" TEXT NOT NULL UNIQUE, + "userID" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL, + "views" INTEGER NOT NULL, + "shadowHidden" INTEGER NOT NULL +); + */ + +describe('getVideoSponsorTime', () => { + before(() => { + db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'abc123', 'testman', 0, 50, 0)"); + }); + + it('Should be able to get a time', (done) => { + request.get(utils.getbaseURL() + + "/api/getVideoSponsorTimes?videoID=testtesttest", null, + (err, res, body) => { + if (err) done(false); + else if (res.statusCode !== 200) done("non 200"); + else done(); + }); + }); + + it('Should be able to get the correct', (done) => { + request.get(utils.getbaseURL() + + "/api/getVideoSponsorTimes?videoID=testtesttest", null, + (err, res, body) => { + if (err) done(false); + else if (res.statusCode !== 200) done("non 200"); + else { + let parsedBody = JSON.parse(body); + if (parsedBody.sponsorTimes[0][0] === 1 + && parsedBody.sponsorTimes[0][1] === 11 + && parsedBody.UUIDs[0] === 'abc123') { + done(); + } else { + done("Wrong data was returned + " + parsedBody); + } + }; + }); + }); +}); \ No newline at end of file From 461483ea36ab6b2b529bb2709082b9aa31be3ad2 Mon Sep 17 00:00:00 2001 From: Joe-Dowd Date: Wed, 1 Apr 2020 22:12:32 +0100 Subject: [PATCH 11/11] changed a test message --- test/cases/getSponsorTime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/getSponsorTime.js b/test/cases/getSponsorTime.js index 8c61504..d0fc21e 100644 --- a/test/cases/getSponsorTime.js +++ b/test/cases/getSponsorTime.js @@ -32,7 +32,7 @@ describe('getVideoSponsorTime', () => { }); }); - it('Should be able to get the correct', (done) => { + it('Should be able to get the correct time', (done) => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest", null, (err, res, body) => {