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,
"timeSubmitted" INTEGER NOT NULL,
"views" INTEGER NOT NULL,
"category" TEXT NOT NULL;
"category" TEXT NOT NULL,
"shadowHidden" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "userNames" (

View File

@@ -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"
},

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 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');
@@ -34,7 +36,7 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
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
@@ -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) {
let videoID = req.query.videoID || req.body.videoID;
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
let duplicateCheck2Row =
db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]);
if (duplicateCheck2Row == null) {
res.sendStatus(409);
let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get(startTime, endTime, videoID);
if (duplicateCheck2Row !== null) {
// console.log(duplicateCheck2Row)
// 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;
}
}
@@ -184,8 +234,10 @@ module.exports = async function postSkipSegments(req, res) {
let UUID = getHash("v2-categories" + videoID + segmentInfo.segment[0] +
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
console.log(UUID)
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
@@ -196,7 +248,7 @@ module.exports = async function postSkipSegments(req, res) {
//a DB change probably occurred
res.sendStatus(502);
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;
}
@@ -207,6 +259,6 @@ module.exports = async function postSkipSegments(req, res) {
} catch (err) {
console.error(err);
res.send(500);
res.sendStatus(500);
}
}

View File

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

View File

@@ -5,16 +5,16 @@ var getHash = require('../../src/utils/getHash.js');
describe('getSavedTimeForUser', () => {
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) => {
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
});
});
});

View File

@@ -19,17 +19,17 @@ var utils = require('../utils.js');
describe('getVideoSponsorTime', () => {
before(() => {
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 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', 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, 'sponsor', 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);
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);
}

View File

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

View File

@@ -1,8 +1,4 @@
BEGIN TRANSACTION;
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
);

View File

@@ -1,8 +1,4 @@
BEGIN TRANSACTION;
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
);
@@ -15,6 +11,7 @@ CREATE TABLE IF NOT EXISTS "sponsorTimes" (
"userID" TEXT NOT NULL,
"timeSubmitted" INTEGER NOT NULL,
"views" INTEGER NOT NULL,
"category" TEXT NOT NULL,
"shadowHidden" INTEGER NOT NULL
);
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;