This commit is contained in:
Ajay Ramachandran
2020-04-06 20:05:26 -04:00
parent 761fb7dafe
commit 9807d3e9c7
12 changed files with 529 additions and 126 deletions

View File

@@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS "sponsorTimes" (
"userID" TEXT NOT NULL, "userID" TEXT NOT NULL,
"timeSubmitted" INTEGER NOT NULL, "timeSubmitted" INTEGER NOT NULL,
"views" INTEGER NOT NULL, "views" INTEGER NOT NULL,
"category" TEXT NOT NULL; "category" TEXT NOT NULL,
"shadowHidden" INTEGER NOT NULL "shadowHidden" INTEGER NOT NULL
); );
CREATE TABLE IF NOT EXISTS "userNames" ( CREATE TABLE IF NOT EXISTS "userNames" (

View File

@@ -6,6 +6,7 @@
"scripts": { "scripts": {
"test": "node test.js", "test": "node test.js",
"dev": "nodemon -x \"(npm test || echo test failed) && npm start\"", "dev": "nodemon -x \"(npm test || echo test failed) && npm start\"",
"dev:bash": "nodemon -x 'npm test ; npm start'",
"start": "node index.js" "start": "node index.js"
}, },
"author": "Ajay Ramachandran", "author": "Ajay Ramachandran",
@@ -14,6 +15,7 @@
"better-sqlite3": "^5.4.3", "better-sqlite3": "^5.4.3",
"express": "^4.17.1", "express": "^4.17.1",
"http": "0.0.0", "http": "0.0.0",
"iso8601-duration": "^1.2.0",
"uuid": "^3.3.2", "uuid": "^3.3.2",
"youtube-api": "^2.0.10" "youtube-api": "^2.0.10"
}, },

View File

@@ -0,0 +1,273 @@
var fs = require('fs');
var config = require('../config.js');
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) {
const videoID = req.body.videoID || req.query.videoID;
// Default to sponsor
// If using params instead of JSON, only one category can be pulled
const categories = req.body.categories || [req.query.category] ["sponsor"];
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);
}
}

View File

@@ -4,6 +4,8 @@ var databases = require('../databases/databases.js');
var db = databases.db; var db = databases.db;
var privateDB = databases.privateDB; var privateDB = databases.privateDB;
var YouTubeAPI = require('../utils/youtubeAPI.js'); var YouTubeAPI = require('../utils/youtubeAPI.js');
var request = require('request');
var isoDurations = require('iso8601-duration');
var getHash = require('../utils/getHash.js'); var getHash = require('../utils/getHash.js');
var getIP = require('../utils/getIP.js'); var getIP = require('../utils/getIP.js');
@@ -34,7 +36,7 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID); let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID);
// If it is a first time submission // If it is a first time submission
if (userSubmissionCountRow.submissionCount === 0) { if (userSubmissionCountRow.submissionCount <= 1) {
YouTubeAPI.videos.list({ YouTubeAPI.videos.list({
part: "snippet", part: "snippet",
id: videoID id: videoID
@@ -78,6 +80,47 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
} }
} }
// submission: {videoID, startTime, endTime}
// callback: function(reject: "String containing reason the submission was rejected")
// returns: string when an error, false otherwise
async function autoModerateSubmission(submission, callback) {
// Get the video information from the youtube API
if (config.youtubeAPI !== null) {
let {err, data} = await new Promise((resolve, reject) => {
YouTubeAPI.videos.list({
part: "contentDetails",
id: submission.videoID
}, (err, data) => resolve({err, data}));
});
if (err) {
return "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) {
return "Sponsor segment is over 80% of the video.";
} else {
return false;
}
}
}
} else {
console.log("Skipped YouTube API");
// Can't moderate the submission without calling the youtube API
// so allow by default.
return;
}
}
module.exports = async function postSkipSegments(req, res) { module.exports = async function postSkipSegments(req, res) {
let videoID = req.query.videoID || req.body.videoID; let videoID = req.query.videoID || req.body.videoID;
let userID = req.query.userID || req.body.userID; let userID = req.query.userID || req.body.userID;
@@ -124,10 +167,17 @@ module.exports = async function postSkipSegments(req, res) {
} }
//check if this info has already been submitted before //check if this info has already been submitted before
let duplicateCheck2Row = let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get(startTime, endTime, videoID);
db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]); if (duplicateCheck2Row !== null) {
if (duplicateCheck2Row == null) { // console.log(duplicateCheck2Row)
res.sendStatus(409); // console.log(db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").all(1,10,"dQw4w9WgXcQ"))
// res.sendStatus(409);
// return;
}
let autoModerateResult = await autoModerateSubmission({videoID, startTime, endTime});
if (autoModerateResult) {
res.status(403).send("Request rejected by auto moderator: " + autoModerateResult);
return; return;
} }
} }
@@ -184,8 +234,10 @@ module.exports = async function postSkipSegments(req, res) {
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); segmentInfo.segment[1] + segmentInfo.category + userID, 1);
console.log(UUID)
try { 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); segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned);
//add to private db as well //add to private db as well
@@ -196,7 +248,7 @@ module.exports = async function postSkipSegments(req, res) {
//a DB change probably occurred //a DB change probably occurred
res.sendStatus(502); 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); segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
return; return;
} }
@@ -207,6 +259,6 @@ module.exports = async function postSkipSegments(req, res) {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
res.send(500); res.sendStatus(500);
} }
} }

View File

@@ -2,8 +2,18 @@ var config = require('../config.js');
// YouTube API // YouTube API
const YouTubeAPI = require("youtube-api"); const YouTubeAPI = require("youtube-api");
YouTubeAPI.authenticate({
type: "key", var exportObject;
key: config.youtubeAPIKey // If in test mode, return a mocked youtube object
}); // otherwise return an authenticated youtube api
module.exports = YouTubeAPI; if (config.mode === "test") {
exportObject = require("../../test/youtubeMock.js");
} else {
YouTubeAPI.authenticate({
type: "key",
key: config.youtubeAPIKey
});
exportObject = YouTubeAPI;
}
module.exports = exportObject;

View File

@@ -5,16 +5,16 @@ var getHash = require('../../src/utils/getHash.js');
describe('getSavedTimeForUser', () => { describe('getSavedTimeForUser', () => {
before(() => { before(() => {
db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '"+getHash("testman")+"', 0, 50, 0)"); db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)");
}); });
it('Should be able to get a 200', (done) => { it('Should be able to get a 200', (done) => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/getSavedTimeForUser?userID=testman", null, + "/api/getSavedTimeForUser?userID=testman", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done("couldn't call endpoint");
else if (res.statusCode !== 200) done("non 200"); else if (res.statusCode !== 200) done("non 200");
else done(); else done(); // pass
}); });
}); });
}); });

View File

@@ -19,17 +19,17 @@ var utils = require('../utils.js');
describe('getVideoSponsorTime', () => { describe('getVideoSponsorTime', () => {
before(() => { before(() => {
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 0)"); db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)");
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 0)"); db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)");
}); });
it('Should be able to get a time', (done) => { it('Should be able to get a time', (done) => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/getVideoSponsorTimes?videoID=testtesttest", null, + "/api/getVideoSponsorTimes?videoID=testtesttest", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done("Couldn't call endpoint");
else if (res.statusCode !== 200) done("non 200"); else if (res.statusCode !== 200) done("non 200");
else done(); else done(); // pass
}); });
}); });
@@ -37,9 +37,9 @@ describe('getVideoSponsorTime', () => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/getVideoSponsorTimes?videoID=notarealvideo", null, + "/api/getVideoSponsorTimes?videoID=notarealvideo", null,
(err, res, body) => { (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 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() request.get(utils.getbaseURL()
+ "/api/getVideoSponsorTimes?videoID=testtesttest&fakeparam=hello", null, + "/api/getVideoSponsorTimes?videoID=testtesttest&fakeparam=hello", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done("couldn't callendpoint");
else if (res.statusCode !== 200) done("non 200"); else if (res.statusCode !== 200) done("non 200");
else done(); else done(); // pass
}); });
}); });
@@ -58,9 +58,10 @@ describe('getVideoSponsorTime', () => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/getVideoSponsorTimes?videoID=testtesttest,test", null, + "/api/getVideoSponsorTimes?videoID=testtesttest,test", null,
(err, res, body) => { (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 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() request.get(utils.getbaseURL()
+ "/api/getVideoSponsorTimes?videoID=testtesttest", null, + "/api/getVideoSponsorTimes?videoID=testtesttest", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done("couldn't call endpoint");
else if (res.statusCode !== 200) done("non 200"); else if (res.statusCode !== 200) done("non 200");
else { else {
let parsedBody = JSON.parse(body); let parsedBody = JSON.parse(body);
if (parsedBody.sponsorTimes[0][0] === 1 if (parsedBody.sponsorTimes[0][0] === 1
&& parsedBody.sponsorTimes[0][1] === 11 && parsedBody.sponsorTimes[0][1] === 11
&& parsedBody.UUIDs[0] === 'uuid-0') { && parsedBody.UUIDs[0] === 'uuid-0') {
done(); done(); // pass
} else { } else {
done("Wrong data was returned + " + parsedBody); done("Wrong data was returned + " + parsedBody);
} }

View File

@@ -9,35 +9,37 @@ var db = databases.db;
describe('postVideoSponsorTime (Old submission method)', () => { describe('postVideoSponsorTime (Old submission method)', () => {
it('Should be able to submit a time (GET)', (done) => { it('Should be able to submit a time (GET)', (done) => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test", null, + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcQ&startTime=1&endTime=10&userID=test", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
else if (res.statusCode === 200) { else if (res.statusCode === 200) {
let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcQ");
if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") {
done() done()
return; } else {
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
} }
} else {
done("Status code was " + res.statusCode);
} }
done(false);
}); });
}); });
it('Should be able to submit a time (POST)', (done) => { it('Should be able to submit a time (POST)', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test", null, + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcE&startTime=1&endTime=11&userID=test", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
else if (res.statusCode === 200) { else if (res.statusCode === 200) {
let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcE");
if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") {
done() done()
return; } else {
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
} }
} else {
done("Status code was " + res.statusCode);
} }
done(false);
}); });
}); });
@@ -45,9 +47,9 @@ describe('postVideoSponsorTime (Old submission method)', () => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
if (res.statusCode === 400) done(); if (res.statusCode === 400) done();
else done(false); else done("Status code was: " + res.statusCode);
}); });
}); });
}); });

View File

@@ -9,172 +9,204 @@ var db = databases.db;
describe('postSkipSegments', () => { describe('postSkipSegments', () => {
it('Should be able to submit a single time (Params method)', (done) => { it('Should be able to submit a single time (Params method)', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test&category=sponsor", null, + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
else if (res.statusCode === 200) { else if (res.statusCode === 200) {
let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcR");
if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") {
done() done()
return; } else {
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
} }
} else {
done("Status code was " + res.statusCode);
} }
done(false);
}); });
}); });
it('Should be able to submit a single time (JSON method)', (done) => { it('Should be able to submit a single time (JSON method)', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc", userID: "test",
videoID: "dQw4w9WgXcF",
segments: [{ segments: [{
segment: [0, 10], segment: [0, 10],
category: "sponsor" category: "sponsor"
}] }]
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
else if (res.statusCode === 200) { else if (res.statusCode === 200) {
let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcF");
if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") { if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") {
done() done()
return; } else {
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
} }
} else {
done("Status code was " + res.statusCode);
} }
done(false);
}); });
}); });
it('Should be able to submit multiple times (JSON method)', (done) => { it('Should be able to submit multiple times (JSON method)', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc", userID: "test",
videoID: "dQw4w9WgXcQ",
segments: [{ segments: [{
segment: [0, 10], segment: [3, 10],
category: "sponsor" category: "sponsor"
}, { }, {
segment: [30, 60], segment: [30, 60],
category: "intro" category: "intro"
}] }]
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(err);
else if (res.statusCode === 200) { else if (res.statusCode === 200) {
let rows = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").all(videoID); let rows = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").all("dQw4w9WgXcR");
let success = true;
if (rows.length !== 2) done(false); if (rows.length === 2) {
for (const row of rows) { for (const row of rows) {
if (row.startTime !== 1 || row.endTime !== 10 || row.category !== "sponsor") { if ((row.startTime !== 3 || row.endTime !== 10 || row.category !== "sponsor") &&
done(false) (row.startTime !== 30 || row.endTime !== 60 || row.category !== "intro")) {
return; success = false;
break;
}
} }
} }
done() if (success) done();
else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(row));
} else {
done("Status code was " + res.statusCode);
} }
});
done(false); });
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,
(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,
(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 return 400 for missing params (Params method)', (done) => { it('Should return 400 for missing params (Params method)', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null,
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
if (res.statusCode === 400) done(); if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
it('Should return 400 for missing params (JSON method) 1', (done) => { it('Should return 400 for missing params (JSON method) 1', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
userID: "test",
segments: [{ segments: [{
segment: [0, 10], segment: [9, 10],
category: "sponsor" category: "sponsor"
}, { }, {
segment: [30, 60], segment: [31, 60],
category: "intro" category: "intro"
}] }]
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
else if (res.statusCode === 200) done(); else if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
it('Should return 400 for missing params (JSON method) 2', (done) => { it('Should return 400 for missing params (JSON method) 2', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc" userID: "test",
videoID: "dQw4w9WgXcQ"
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
else if (res.statusCode === 200) done(); else if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
it('Should return 400 for missing params (JSON method) 3', (done) => { it('Should return 400 for missing params (JSON method) 3', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc", userID: "test",
videoID: "dQw4w9WgXcQ",
segments: [{ segments: [{
segment: [0], segment: [0],
category: "sponsor" category: "sponsor"
}, { }, {
segment: [30, 60], segment: [31, 60],
category: "intro" category: "intro"
}] }]
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
else if (res.statusCode === 200) done(); else if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
it('Should return 400 for missing params (JSON method) 4', (done) => { it('Should return 400 for missing params (JSON method) 4', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc", userID: "test",
videoID: "dQw4w9WgXcQ",
segments: [{ segments: [{
segment: [0, 10] segment: [9, 10]
}, { }, {
segment: [30, 60], segment: [31, 60],
category: "intro" category: "intro"
}] }]
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
else if (res.statusCode === 200) done(); else if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
it('Should return 400 for missing params (JSON method) 5', (done) => { it('Should return 400 for missing params (JSON method) 5', (done) => {
request.post(utils.getbaseURL() request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", JSON.stringify({ + "/api/postVideoSponsorTimes", {
body: { json: {
videoID: "djgofQKWmXc" userID: "test",
videoID: "dQw4w9WgXcQ"
} }
}), },
(err, res, body) => { (err, res, body) => {
if (err) done(false); if (err) done(true);
else if (res.statusCode === 200) done(); else if (res.statusCode === 400) done();
else done(false); else done(true);
}); });
}); });
}); });

View File

@@ -1,8 +1,4 @@
BEGIN TRANSACTION; BEGIN TRANSACTION;
DROP TABLE IF EXISTS "shadowBannedUsers";
DROP TABLE IF EXISTS "votes";
DROP TABLE IF EXISTS "sponsorTimes";
CREATE TABLE IF NOT EXISTS "shadowBannedUsers" ( CREATE TABLE IF NOT EXISTS "shadowBannedUsers" (
"userID" TEXT NOT NULL "userID" TEXT NOT NULL
); );

View File

@@ -1,8 +1,4 @@
BEGIN TRANSACTION; BEGIN TRANSACTION;
DROP TABLE IF EXISTS "vipUsers";
DROP TABLE IF EXISTS "sponsorTimes";
DROP TABLE IF EXISTS "userNames";
CREATE TABLE IF NOT EXISTS "vipUsers" ( CREATE TABLE IF NOT EXISTS "vipUsers" (
"userID" TEXT NOT NULL "userID" TEXT NOT NULL
); );
@@ -15,6 +11,7 @@ CREATE TABLE IF NOT EXISTS "sponsorTimes" (
"userID" TEXT NOT NULL, "userID" TEXT NOT NULL,
"timeSubmitted" INTEGER NOT NULL, "timeSubmitted" INTEGER NOT NULL,
"views" INTEGER NOT NULL, "views" INTEGER NOT NULL,
"category" TEXT NOT NULL,
"shadowHidden" INTEGER NOT NULL "shadowHidden" INTEGER NOT NULL
); );
CREATE TABLE IF NOT EXISTS "userNames" ( CREATE TABLE IF NOT EXISTS "userNames" (

38
test/youtubeMock.js Normal file
View File

@@ -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;