diff --git a/package.json b/package.json index e162a6b..b8f62a8 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "http": "0.0.0", "iso8601-duration": "^1.2.0", "uuid": "^3.3.2", - "youtube-api": "^2.0.10" + "youtube-api": "^2.0.10", + "node-fetch": "^2.6.0" }, "devDependencies": { "mocha": "^7.1.1", diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.js index d8fe2a3..93ac5a6 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.js @@ -10,6 +10,7 @@ var isoDurations = require('iso8601-duration'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); var getFormattedTime = require('../utils/getFormattedTime.js'); +const fetch = require('node-fetch'); // TODO: might need to be a util //returns true if the user is considered trustworthy @@ -21,8 +22,8 @@ async function isUserTrustworthy(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 || + + return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 || (totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions); } @@ -48,14 +49,14 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) { let startTime = parseFloat(segmentInfo.segment[0]); let endTime = parseFloat(segmentInfo.segment[1]); - + 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: " + + "\n\nTimestamp: " + getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + "\n\nCategory: " + segmentInfo.category, "color": 10813440, @@ -110,11 +111,30 @@ async function autoModerateSubmission(submission, callback) { if ((submission.endTime - submission.startTime) > (duration/100)*80) { return "Sponsor segment is over 80% of the video."; } else { - return false; + let overlap = false; + nb_predictions = fetch("https://ai.neuralblock.app/api/getSponsorSegments?vid=" + submission.videoID).then().then(); + for (nb_seg in nb_predictions.sponsorSegments){ + let head = 0; + let tail = 0; + // If there's an overlap, find the percentage of overlap. + if (submission.startTime <= nb_seg[1] && nb_seg[0] <= submission.endTime){ + head = Math.max(submission.startTime, nb_seg[0]); + tail = Math.min(submission.endTime, nb_seg[1]); + } + if ((tail-head)/(nb_seg[1]-nb_seg[0]) > 0.65){ + overlap = true; + break; + } + } + if (overlap){ + return "Sponsor segment has passed checks."; + } else{ + return "Sponsor segment doesn't have at least 65% match."; + } } } } - + } else { console.log("Skipped YouTube API"); @@ -195,7 +215,7 @@ module.exports = async function postSkipSegments(req, res) { //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); @@ -205,7 +225,7 @@ module.exports = async function postSkipSegments(req, res) { //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); @@ -233,24 +253,24 @@ module.exports = async function postSkipSegments(req, res) { //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("v2-categories" + videoID + segmentInfo.segment[0] + + let UUID = getHash("v2-categories" + videoID + segmentInfo.segment[0] + segmentInfo.segment[1] + segmentInfo.category + userID, 1); try { - db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0], + db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned); - + //add to private db as well privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted); } catch (err) { //a DB change probably occurred res.sendStatus(502); - console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " + + console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " + segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err); - + return; } - + // Discord notification sendDiscordNotification(userID, videoID, UUID, segmentInfo); } diff --git a/test/cases/postSkipSegments.js b/test/cases/postSkipSegments.js index 05c7374..c8f3c39 100644 --- a/test/cases/postSkipSegments.js +++ b/test/cases/postSkipSegments.js @@ -8,8 +8,8 @@ var db = databases.db; describe('postSkipSegments', () => { it('Should be able to submit a single time (Params method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, (err, res, body) => { if (err) done(err); else if (res.statusCode === 200) { @@ -26,7 +26,7 @@ describe('postSkipSegments', () => { }); it('Should be able to submit a single time (JSON method)', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", @@ -36,7 +36,7 @@ describe('postSkipSegments', () => { category: "sponsor" }] } - }, + }, (err, res, body) => { if (err) done(err); else if (res.statusCode === 200) { @@ -53,7 +53,7 @@ describe('postSkipSegments', () => { }); it('Should be able to submit multiple times (JSON method)', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", @@ -66,7 +66,7 @@ describe('postSkipSegments', () => { category: "intro" }] } - }, + }, (err, res, body) => { if (err) done(err); else if (res.statusCode === 200) { @@ -91,18 +91,28 @@ describe('postSkipSegments', () => { }); it('Should be rejected if over 80% of the video', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null, + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", 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 there\'s not at least 65% overlap with NB' , (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=LevkAjUE6d4&startTime=40&endTime=60&userID=testing", 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=30&endTime=1000000&userID=testing", null, + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=30&endTime=1000000&userID=testing", null, (err, res, body) => { if (err) done("Couldn't call endpoint"); else if (res.statusCode === 403) done(); // pass @@ -111,8 +121,8 @@ describe('postSkipSegments', () => { }); it('Should return 400 for missing params (Params method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null, + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null, (err, res, body) => { if (err) done(true); if (res.statusCode === 400) done(); @@ -121,7 +131,7 @@ describe('postSkipSegments', () => { }); it('Should return 400 for missing params (JSON method) 1', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", @@ -133,7 +143,7 @@ describe('postSkipSegments', () => { category: "intro" }] } - }, + }, (err, res, body) => { if (err) done(true); else if (res.statusCode === 400) done(); @@ -141,13 +151,13 @@ describe('postSkipSegments', () => { }); }); it('Should return 400 for missing params (JSON method) 2', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", videoID: "dQw4w9WgXcQ" } - }, + }, (err, res, body) => { if (err) done(true); else if (res.statusCode === 400) done(); @@ -155,7 +165,7 @@ describe('postSkipSegments', () => { }); }); it('Should return 400 for missing params (JSON method) 3', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", @@ -168,7 +178,7 @@ describe('postSkipSegments', () => { category: "intro" }] } - }, + }, (err, res, body) => { if (err) done(true); else if (res.statusCode === 400) done(); @@ -176,7 +186,7 @@ describe('postSkipSegments', () => { }); }); it('Should return 400 for missing params (JSON method) 4', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", @@ -188,7 +198,7 @@ describe('postSkipSegments', () => { category: "intro" }] } - }, + }, (err, res, body) => { if (err) done(true); else if (res.statusCode === 400) done(); @@ -196,17 +206,17 @@ describe('postSkipSegments', () => { }); }); it('Should return 400 for missing params (JSON method) 5', (done) => { - request.post(utils.getbaseURL() + request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes", { json: { userID: "test", videoID: "dQw4w9WgXcQ" } - }, + }, (err, res, body) => { if (err) done(true); else if (res.statusCode === 400) done(); else done(true); }); }); -}); \ No newline at end of file +});