diff --git a/package.json b/package.json index e7408a0..e162a6b 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "test": "node test.js", "dev": "nodemon -x \"(npm test || echo test failed) && npm start\"", + "dev:bash": "nodemon -x 'npm test ; npm start'", "start": "node index.js" }, "author": "Ajay Ramachandran", @@ -14,6 +15,7 @@ "better-sqlite3": "^5.4.3", "express": "^4.17.1", "http": "0.0.0", + "iso8601-duration": "^1.2.0", "uuid": "^3.3.2", "youtube-api": "^2.0.10" }, diff --git a/src/routes/submitSponsorTimes.js b/src/routes/submitSponsorTimes.js index a550f00..8221fa1 100644 --- a/src/routes/submitSponsorTimes.js +++ b/src/routes/submitSponsorTimes.js @@ -4,6 +4,8 @@ var databases = require('../databases/databases.js'); var db = databases.db; var privateDB = databases.privateDB; var YouTubeAPI = require('../utils/youtubeAPI.js'); +var request = require('request'); +var isoDurations = require('iso8601-duration'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); @@ -116,32 +118,77 @@ module.exports = async function submitSponsorTimes(req, res) { } 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); + //not a duplicate + + autoModerateSubmission({videoID, startTime, endTime}, (reject) => { + if (!reject) { + try { + db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, startTime, endTime, startingVotes, UUID, userID, timeSubmitted, 0, shadowBanned); - 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; - } + //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 + console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + startTime + ", " + "endTime" + ", " + userID); + res.sendStatus(502); + } + } else { + res.status(403).send("Request rejected by auto moderator: " + reject); + } + }); } else { res.sendStatus(409); } + } + } + } catch (err) { + console.error(err); + res.send(500); + } +} +// submission: {videoID, startTime, endTime} +// callback: function(reject: "String containing reason the submission was rejected") +const autoModerateSubmission = (submission, callback) => { + // Get the video information from the youtube API + if (config.youtubeAPI !== null) { + YouTubeAPI.videos.list({ + part: "contentDetails", + id: submission.videoID + }, (err, data) => { + if (err) callback("Couldn't get video information."); + else { + // Check to see if video exists + if (data.pageInfo.totalResults === 0) { + callback("No video exists with id " + submission.videoID); + } else { + let duration = data.items[0].contentDetails.duration; + duration = isoDurations.toSeconds(isoDurations.parse(duration)); + + // Reject submission if over 80% of the video + if ((submission.endTime - submission.startTime) > (duration/100)*80) { + callback ("Sponsor segment is over 80% of the video."); + } else { + callback(); + } + } + } + }); + } else { + console.log("skip youtube api"); + // Can't moderate the submission without calling the youtube API + // so allow by default. + callback(); + } +} +/* //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) { + if (userSubmissionCountRow.submissionCount <= 1) { YouTubeAPI.videos.list({ part: "snippet", id: videoID @@ -150,6 +197,8 @@ module.exports = async function submitSponsorTimes(req, res) { err && console.log(err); return; } + + console.log(JSON.stringify(data)); request.post(config.discordFirstTimeSubmissionsWebhookURL, { json: { @@ -181,12 +230,4 @@ module.exports = async function submitSponsorTimes(req, res) { }); }); } - } - } - } - } catch (err) { - console.error(err); - - res.send(500); - } -} + }*/ \ No newline at end of file diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js index 7449815..c28f882 100644 --- a/src/utils/youtubeAPI.js +++ b/src/utils/youtubeAPI.js @@ -2,8 +2,18 @@ var config = require('../config.js'); // YouTube API const YouTubeAPI = require("youtube-api"); -YouTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey -}); -module.exports = YouTubeAPI; \ No newline at end of file + +var exportObject; +// If in test mode, return a mocked youtube object +// otherwise return an authenticated youtube api +if (config.mode === "test") { + exportObject = require("../../test/youtubeMock.js"); +} else { + YouTubeAPI.authenticate({ + type: "key", + key: config.youtubeAPIKey + }); + exportObject = YouTubeAPI; +} + +module.exports = exportObject; \ No newline at end of file diff --git a/test/cases/getSavedTimeForUser.js b/test/cases/getSavedTimeForUser.js index 5488c67..184b017 100644 --- a/test/cases/getSavedTimeForUser.js +++ b/test/cases/getSavedTimeForUser.js @@ -12,9 +12,9 @@ describe('getSavedTimeForUser', () => { request.get(utils.getbaseURL() + "/api/getSavedTimeForUser?userID=testman", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); }); \ No newline at end of file diff --git a/test/cases/getSponsorTime.js b/test/cases/getSponsorTime.js index 0565d28..be33143 100644 --- a/test/cases/getSponsorTime.js +++ b/test/cases/getSponsorTime.js @@ -27,9 +27,9 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest", null, (err, res, body) => { - if (err) done(false); + if (err) done("Couldn't call endpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); @@ -37,9 +37,9 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=notarealvideo", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); - else done(); + else done(); // pass }); }); @@ -48,9 +48,9 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest&fakeparam=hello", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't callendpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); @@ -58,9 +58,10 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest,test", null, (err, res, body) => { - if (err) done(false); + if (err) done("couln't call endpoint"); else if (res.statusCode !== 200) done("non 200 response: " + res.statusCode); - else (JSON.parse(body).UUIDs[0] === 'uuid-1') && done(); + else if (JSON.parse(body).UUIDs[0] === 'uuid-1') done(); // pass + else done("couldn't parse response"); }); }); @@ -68,14 +69,14 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); 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] === 'uuid-0') { - done(); + done(); // pass } else { done("Wrong data was returned + " + parsedBody); } diff --git a/test/cases/submitSponsorTimes.js b/test/cases/submitSponsorTimes.js index 181b550..ce7b90e 100644 --- a/test/cases/submitSponsorTimes.js +++ b/test/cases/submitSponsorTimes.js @@ -6,11 +6,11 @@ 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, + + "/api/postVideoSponsorTimes?videoID=fWvKvOViM3g&startTime=1&endTime=10&userID=test", null, (err, res, body) => { - if (err) done(false); + if (err) done("Couldn't call endpoint"); else if (res.statusCode === 200) done(); - else done(false); + else done("non 200 status code: " + res.statusCode + " ("+body+")"); }); }); @@ -18,9 +18,30 @@ describe('postVideoSponsorTime', () => { 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); + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 400) done(); // pass + else done("non 400 status code: " + res.statusCode + " ("+body+")"); + }); + }); + + + it('Should be rejected if over 80% of the video', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=1&endTime=1000000&userID=test", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " ("+body+")"); + }); + }); + + it('Should be rejected if not a valid videoID', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=1&endTime=1000000&userID=test", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " ("+body+")"); }); }); }); \ No newline at end of file diff --git a/test/youtubeMock.js b/test/youtubeMock.js new file mode 100644 index 0000000..56300fd --- /dev/null +++ b/test/youtubeMock.js @@ -0,0 +1,38 @@ +/* +YouTubeAPI.videos.list({ + part: "snippet", + id: videoID +}, function (err, data) {}); + */ + + // https://developers.google.com/youtube/v3/docs/videos + +const YouTubeAPI = { + videos: { + list: (obj, callback) => { + if (obj.videoID === "knownWrongID") { + callback(undefined, { + pageInfo: { + totalResults: 0 + }, + items: [] + }); + } else { + callback(undefined, { + pageInfo: { + totalResults: 1 + }, + items: [ + { + contentDetails: { + duration: "PT1H23M30S" + } + } + ] + }); + } + } + } +}; + +module.exports = YouTubeAPI; \ No newline at end of file