Merge branch 'master' into 147-add-cache-for-gettopusers

This commit is contained in:
Ajay Ramachandran
2020-10-14 18:35:15 -04:00
committed by GitHub
23 changed files with 941 additions and 21 deletions

View File

@@ -23,5 +23,20 @@
"readOnly": false,
"webhooks": [],
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], // List of supported categories any other category will be rejected
"getTopUsersCacheTimeMinutes": 5 // cacheTime for getTopUsers result in minutes
"getTopUsersCacheTimeMinutes": 5, // cacheTime for getTopUsers result in minutes
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
"hoursAfterWarningExpire": 24,
"rateLimit": {
"vote": {
"windowMs": 900000, // 15 minutes
"max": 20, // 20 requests in 15min time window
"message": "Too many votes, please try again later",
"statusCode": 429
},
"view": {
"windowMs": 900000, // 15 minutes
"max": 20, // 20 requests in 15min time window
"statusCode": 200
}
}
}

View File

@@ -0,0 +1,12 @@
BEGIN TRANSACTION;
/* Create warnings table */
CREATE TABLE "warnings" (
userID TEXT NOT NULL,
issueTime INTEGER NOT NULL,
issuerUserID TEXT NOT NULL
);
UPDATE config SET value = 4 WHERE key = "version";
COMMIT;

10
package-lock.json generated
View File

@@ -785,6 +785,11 @@
}
}
},
"express-rate-limit": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz",
"integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -1866,6 +1871,11 @@
"semver": "^5.7.0"
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-forge": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz",

View File

@@ -14,13 +14,14 @@
"dependencies": {
"better-sqlite3": "^5.4.3",
"express": "^4.17.1",
"express-rate-limit": "^5.1.3",
"http": "0.0.0",
"iso8601-duration": "^1.2.0",
"node-fetch": "^2.6.0",
"redis": "^3.0.2",
"sync-mysql": "^3.0.1",
"uuid": "^3.3.2",
"youtube-api": "^2.0.10",
"node-fetch": "^2.6.0"
"youtube-api": "^2.0.10"
},
"devDependencies": {
"mocha": "^7.1.1",

View File

@@ -3,8 +3,11 @@ var express = require('express');
var app = express();
var config = require('./config.js');
var redis = require('./utils/redis.js');
const getIP = require('./utils/getIP.js');
const getHash = require('./utils/getHash.js');
// Middleware
const rateLimitMiddleware = require('./middleware/requestRateLimit.js');
var corsMiddleware = require('./middleware/cors.js');
var loggerMiddleware = require('./middleware/logger.js');
const userCounter = require('./middleware/userCounter.js');
@@ -24,13 +27,24 @@ var getViewsForUser = require('./routes/getViewsForUser.js');
var getTopUsers = require('./routes/getTopUsers.js');
var getTotalStats = require('./routes/getTotalStats.js');
var getDaysSavedFormatted = require('./routes/getDaysSavedFormatted.js');
var getUserInfo = require('./routes/getUserInfo.js');
var postNoSegments = require('./routes/postNoSegments.js');
var getIsUserVIP = require('./routes/getIsUserVIP.js');
var warnUser = require('./routes/postWarning.js');
var postSegmentShift = require('./routes/postSegmentShift.js');
// Old Routes
var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js');
var oldSubmitSponsorTimes = require('./routes/oldSubmitSponsorTimes.js');
// Rate limit endpoint lists
let voteEndpoints = [voteOnSponsorTime.endpoint];
let viewEndpoints = [viewedVideoSponsorTime];
if (config.rateLimit) {
if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote));
if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view));
}
//setup CORS correctly
app.use(corsMiddleware);
app.use(loggerMiddleware);
@@ -59,12 +73,12 @@ app.post('/api/skipSegments', postSkipSegments);
app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash);
//voting endpoint
app.get('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint);
app.post('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint);
app.get('/api/voteOnSponsorTime', ...voteEndpoints);
app.post('/api/voteOnSponsorTime', ...voteEndpoints);
//Endpoint when a sponsorTime is used up
app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
//Endpoint when a submission is skipped
app.get('/api/viewedVideoSponsorTime', ...viewEndpoints);
app.post('/api/viewedVideoSponsorTime', ...viewEndpoints);
//To set your username for the stats view
app.post('/api/setUsername', setUsername);
@@ -93,6 +107,8 @@ app.get('/api/getTopUsers', getTopUsers);
//send the total submissions, total views and total minutes saved
app.get('/api/getTotalStats', getTotalStats);
app.get('/api/getUserInfo', getUserInfo);
//send out a formatted time saved total
app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted);
@@ -102,6 +118,11 @@ app.post('/api/noSegments', postNoSegments);
//get if user is a vip
app.get('/api/isUserVIP', getIsUserVIP);
//sent user a warning
app.post('/api/warnUser', warnUser);
//get if user is a vip
app.post('/api/segmentShift', postSegmentShift);
app.get('/database.db', function (req, res) {
res.sendFile("./databases/sponsorTimes.db", { root: "./" });

View File

@@ -20,7 +20,9 @@ addDefaults(config, {
"privateDBSchema": "./databases/_private.db.sql",
"readOnly": false,
"webhooks": [],
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"]
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"],
"maxNumberOfActiveWarnings": 3,
"hoursAfterWarningExpires": 24
})
module.exports = config;

View File

@@ -0,0 +1,18 @@
const getIP = require('../utils/getIP.js');
const getHash = require('../utils/getHash.js');
const rateLimit = require('express-rate-limit');
module.exports = (limitConfig) => rateLimit({
windowMs: limitConfig.windowMs,
max: limitConfig.max,
message: limitConfig.message,
statusCode: limitConfig.statusCode,
headers: false,
keyGenerator: (req /*, res*/) => {
return getHash(getIP(req), 1);
},
skip: (/*req, res*/) => {
// skip rate limit if running in test mode
return process.env.npm_lifecycle_script === 'node test.js';
}
});

View File

@@ -20,10 +20,6 @@ module.exports = async function (req, res) {
// Get all video id's that match hash prefix
const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix+'%']);
if (videoIds.length === 0) {
res.sendStatus(404);
return;
}
let segments = videoIds.map((video) => {
return {
@@ -33,5 +29,5 @@ module.exports = async function (req, res) {
};
});
res.status(200).json(segments);
res.status((segments.length === 0) ? 404 : 200).json(segments);
}

82
src/routes/getUserInfo.js Normal file
View File

@@ -0,0 +1,82 @@
const db = require('../databases/databases.js').db;
const getHash = require('../utils/getHash.js');
function dbGetSubmittedSegmentSummary (userID) {
try {
let row = db.prepare("get", "SELECT SUM(((endTime - startTime) / 60) * views) as minutesSaved, count(*) as segmentCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]);
if (row.minutesSaved != null) {
return {
minutesSaved: row.minutesSaved,
segmentCount: row.segmentCount,
};
} else {
return {
minutesSaved: 0,
segmentCount: 0,
};
}
} catch (err) {
return false;
}
}
function dbGetUsername (userID) {
try {
let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]);
if (row !== undefined) {
return row.userName;
} else {
//no username yet, just send back the userID
return userID;
}
} catch (err) {
return false;
}
}
function dbGetViewsForUser (userID) {
try {
let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]);
//increase the view count by one
if (row.viewCount != null) {
return row.viewCount;
} else {
return 0;
}
} catch (err) {
return false;
}
}
function dbGetWarningsForUser (userID) {
try {
let rows = db.prepare('all', "SELECT * FROM warnings WHERE userID = ?", [userID]);
return rows.length;
} catch (err) {
logger.error('Couldn\'t get warnings for user ' + userID + '. returning 0') ;
return 0;
}
}
module.exports = function getUserInfo (req, res) {
let userID = req.query.userID;
if (userID == undefined) {
//invalid request
res.status(400).send('Parameters are not valid');
return;
}
//hash the userID
userID = getHash(userID);
const segmentsSummary = dbGetSubmittedSegmentSummary(userID);
res.send({
userID,
userName: dbGetUsername(userID),
minutesSaved: segmentsSummary.minutesSaved,
segmentCount: segmentsSummary.segmentCount,
viewCount: dbGetViewsForUser(userID),
warnings: dbGetWarningsForUser(userID)
});
}

View File

@@ -0,0 +1,101 @@
const db = require('../databases/databases.js').db;
const getHash = require('../utils/getHash.js');
const isUserVIP = require('../utils/isUserVIP.js');
const logger = require('../utils/logger.js');
const ACTION_NONE = Symbol('none');
const ACTION_UPDATE = Symbol('update');
const ACTION_REMOVE = Symbol('remove');
function shiftSegment(segment, shift) {
if (segment.startTime >= segment.endTime) return {action: ACTION_NONE, segment};
if (shift.startTime >= shift.endTime) return {action: ACTION_NONE, segment};
const duration = shift.endTime - shift.startTime;
if (shift.endTime < segment.startTime) {
// Scenario #1 cut before segment
segment.startTime -= duration;
segment.endTime -= duration;
return {action: ACTION_UPDATE, segment};
}
if (shift.startTime > segment.endTime) {
// Scenario #2 cut after segment
return {action: ACTION_NONE, segment};
}
if (segment.startTime < shift.startTime && segment.endTime > shift.endTime) {
// Scenario #3 cut inside segment
segment.endTime -= duration;
return {action: ACTION_UPDATE, segment};
}
if (segment.startTime >= shift.startTime && segment.endTime > shift.endTime) {
// Scenario #4 cut overlap startTime
segment.startTime = shift.startTime;
segment.endTime -= duration;
return {action: ACTION_UPDATE, segment};
}
if (segment.startTime < shift.startTime && segment.endTime <= shift.endTime) {
// Scenario #5 cut overlap endTime
segment.endTime = shift.startTime;
return {action: ACTION_UPDATE, segment};
}
if (segment.startTime >= shift.startTime && segment.endTime <= shift.endTime) {
// Scenario #6 cut overlap startTime and endTime
return {action: ACTION_REMOVE, segment};
}
return {action: ACTION_NONE, segment};
}
module.exports = (req, res) => {
// Collect user input data
const videoID = req.body.videoID;
const startTime = req.body.startTime;
const endTime = req.body.endTime;
let userID = req.body.userID;
// Check input data is valid
if (!videoID
|| !userID
|| !startTime
|| !endTime
) {
res.status(400).json({
message: 'Bad Format'
});
return;
}
// Check if user is VIP
userID = getHash(userID);
const userIsVIP = isUserVIP(userID);
if (!userIsVIP) {
res.status(403).json({
message: 'Must be a VIP to perform this action.'
});
return;
}
try {
const segments = db.prepare('all', 'SELECT startTime, endTime, UUID FROM sponsorTimes WHERE videoID = ?', [videoID]);
const shift = {
startTime,
endTime,
};
segments.forEach(segment => {
const result = shiftSegment(segment, shift);
switch (result.action) {
case ACTION_UPDATE:
db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
case ACTION_REMOVE:
db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ?, votes = -2 WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
}
});
}
catch(err) {
logger.error(err);
res.sendStatus(500);
}
res.sendStatus(200);
};

View File

@@ -5,6 +5,7 @@ const db = databases.db;
const privateDB = databases.privateDB;
const YouTubeAPI = require('../utils/youtubeAPI.js');
const logger = require('../utils/logger.js');
const getSubmissionUUID = require('../utils/getSubmissionUUID.js');
const request = require('request');
const isoDurations = require('iso8601-duration');
const fetch = require('node-fetch');
@@ -201,8 +202,7 @@ async function autoModerateSubmission(submission) {
startTime = parseFloat(segments[i].segment[0]);
endTime = parseFloat(segments[i].segment[1]);
let UUID = getHash("v2-categories" + submission.videoID + startTime +
endTime + segments[i].category + submission.userID, 1);
const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime);
// Send to Discord
// Note, if this is too spammy. Consider sending all the segments as one Webhook
sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data);
@@ -269,6 +269,16 @@ module.exports = async function postSkipSegments(req, res) {
//hash the ip 5000 times so no one can get it from the database
let hashedIP = getHash(getIP(req) + config.globalSalt);
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?",
[userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))]
).count;
if (warningsCount >= config.maxNumberOfActiveWarnings) {
return res.status(403).send('Submission blocked. Too many active warnings!');
}
let noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]).map((list) => { return list.category });
@@ -400,8 +410,7 @@ 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] +
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
const UUID = getSubmissionUUID(videoID, segmentInfo.category, userID, segmentInfo.segment[0], segmentInfo.segment[1]);
try {
db.prepare('run', "INSERT INTO sponsorTimes " +

24
src/routes/postWarning.js Normal file
View File

@@ -0,0 +1,24 @@
const db = require('../databases/databases.js').db;
const getHash = require('../utils/getHash.js');
const isUserVIP = require('../utils/isUserVIP.js');
const logger = require('../utils/logger.js');
module.exports = (req, res) => {
// Collect user input data
let issuerUserID = getHash(req.body.issuerUserID);
let userID = getHash(req.body.userID);
let issueTime = new Date().getTime();
// Ensure user is a VIP
if (!isUserVIP(issuerUserID)) {
logger.debug("Permission violation: User " + issuerUserID + " attempted to warn user " + userID + "."); // maybe warn?
res.status(403).json({"message": "Not a VIP"});
return;
}
db.prepare('run', 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES (?, ?, ?)', [userID, issueTime, issuerUserID]);
res.status(200).json({
message: "Warning issued to user '" + userID + "'."
});
};

View File

@@ -247,6 +247,16 @@ async function voteOnSponsorTime(req, res) {
return;
}
}
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?",
[nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))]
).count;
if (warningsCount >= config.maxNumberOfActiveWarnings) {
return res.status(403).send('Vote blocked. Too many active warnings!');
}
let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect;

View File

@@ -0,0 +1,7 @@
const getHash = require('./getHash.js');
module.exports = function getSubmissionUUID(videoID, category, userID,
startTime, endTime) {
return getHash('v2-categories' + videoID + startTime + endTime + category +
userID, 1);
};

View File

@@ -19,6 +19,11 @@ if (config.mode === "test") {
// YouTubeAPI.videos.list wrapper with cacheing
exportObject.listVideos = (videoID, part, callback) => {
if (videoID.length !== 11 || videoID.includes(".")) {
callback("Invalid video ID");
return;
}
let redisKey = "youtube.video." + videoID + "." + part;
redis.get(redisKey, (getErr, result) => {
if (getErr || !result) {

View File

@@ -49,5 +49,20 @@
]
}
],
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"]
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"],
"maxNumberOfActiveWarnings": 3,
"hoursAfterWarningExpires": 24,
"rateLimit": {
"vote": {
"windowMs": 900000,
"max": 20,
"message": "Too many votes, please try again later",
"statusCode": 429
},
"view": {
"windowMs": 900000,
"max": 20,
"statusCode": 200
}
}
}

View File

@@ -40,14 +40,15 @@ describe('getSegmentsByHash', () => {
});
});
it('Should be able to get a 404 if no videos', (done) => {
it('Should be able to get an empty array if no videos', (done) => {
request.get(utils.getbaseURL()
+ '/api/skipSegments/11111?categories=["shilling"]', null,
(err, res, body) => {
if (err) done("Couldn't call endpoint");
else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode);
else {
done(); // pass
if (JSON.parse(body).length === 0 && body === '[]') done(); // pass
else done("non empty array returned");
}
});
});

View File

@@ -0,0 +1,8 @@
const getSubmissionUUID = require('../../src/utils/getSubmissionUUID.js');
const assert = require('assert');
describe('getSubmissionUUID', () => {
it('Should return the hashed value', () => {
assert.equal(getSubmissionUUID('video001', 'sponsor', 'testuser001', 13.33337, 42.000001), '1d33d7016aa6482849019bd906d75c08fe6b815e64e823146df35f66c35612dd');
});
});

167
test/cases/getUserInfo.js Normal file
View File

@@ -0,0 +1,167 @@
var request = require('request');
var utils = require('../utils.js');
var db = require('../../src/databases/databases.js').db;
var getHash = require('../../src/utils/getHash.js');
describe('getUserInfo', () => {
before(() => {
let startOfUserNamesQuery = "INSERT INTO userNames (userID, userName) VALUES";
db.exec(startOfUserNamesQuery + "('" + getHash("getuserinfo_user_01") + "', 'Username user 01')");
let startOfSponsorTimesQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000001', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)");
db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000002', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)");
db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -1, 'uuid000003', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)");
db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -2, 'uuid000004', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)");
db.exec(startOfSponsorTimesQuery + "('xzzzxxyyy', 1, 11, -5, 'uuid000005', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)");
db.exec(startOfSponsorTimesQuery + "('zzzxxxyyy', 1, 11, 2, 'uuid000006', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 0)");
db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000007', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)");
db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000008', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)");
db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_0') + "', 10, 'getuserinfo_vip')");
db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')");
db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')");
});
it('Should be able to get a 200', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_user_01', null,
(err, res, body) => {
if (err) {
done('couldn\'t call endpoint');
} else {
if (res.statusCode !== 200) {
done('non 200 (' + res.statusCode + ')');
} else {
done(); // pass
}
}
});
});
it('Should be able to get a 400 (No userID parameter)', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo', null,
(err, res, body) => {
if (err) {
done('couldn\'t call endpoint');
} else {
if (res.statusCode !== 400) {
done('non 400');
} else {
done(); // pass
}
}
});
});
it('Should return info', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_user_01', null,
(err, res, body) => {
if (err) {
done("couldn't call endpoint");
} else {
if (res.statusCode !== 200) {
done("non 200");
} else {
const data = JSON.parse(body);
if (data.userName !== 'Username user 01') {
done('Returned incorrect userName "' + data.userName + '"');
} else if (data.minutesSaved !== 5) {
done('Returned incorrect minutesSaved "' + data.minutesSaved + '"');
} else if (data.viewCount !== 30) {
done('Returned incorrect viewCount "' + data.viewCount + '"');
} else if (data.segmentCount !== 3) {
done('Returned incorrect segmentCount "' + data.segmentCount + '"');
} else {
done(); // pass
}
}
}
});
});
it('Should get warning data', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_warning_0', null,
(err, res, body) => {
if (err) {
done("couldn't call endpoint");
} else {
if (res.statusCode !== 200) {
done("non 200");
} else {
const data = JSON.parse(body);
if (data.warnings !== 1) {
done('wrong number of warnings: ' + data.warnings + ', not ' + 1);
} else {
done(); // pass
}
}
}
});
});
it('Should get multiple warnings', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_warning_1', null,
(err, res, body) => {
if (err) {
done("couldn't call endpoint");
} else {
if (res.statusCode !== 200) {
done("non 200");
} else {
const data = JSON.parse(body);
if (data.warnings !== 2) {
done('wrong number of warnings: ' + data.warnings + ', not ' + 2);
} else {
done(); // pass
}
}
}
});
});
it('Should not get warnings if noe', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_warning_2', null,
(err, res, body) => {
if (err) {
done("couldn't call endpoint");
} else {
if (res.statusCode !== 200) {
done("non 200");
} else {
const data = JSON.parse(body);
if (data.warnings !== 0) {
done('wrong number of warnings: ' + data.warnings + ', not ' + 0);
} else {
done(); // pass
}
}
}
});
});
it('Should return userID for userName (No userName set)', (done) => {
request.get(utils.getbaseURL()
+ '/api/getUserInfo?userID=getuserinfo_user_02', null,
(err, res, body) => {
if (err) {
done('couldn\'t call endpoint');
} else {
if (res.statusCode !== 200) {
done('non 200');
} else {
const data = JSON.parse(body);
if (data.userName !== 'c2a28fd225e88f74945794ae85aef96001d4a1aaa1022c656f0dd48ac0a3ea0f') {
return done('Did not return userID for userName');
}
done(); // pass
}
}
});
});
});

View File

@@ -1,5 +1,7 @@
var assert = require('assert');
var request = require('request');
var config = require('../../src/config.js');
var getHash = require('../../src/utils/getHash.js');
var utils = require('../utils.js');
@@ -7,6 +9,24 @@ var databases = require('../../src/databases/databases.js');
var db = databases.db;
describe('postSkipSegments', () => {
before(() => {
const now = Date.now();
const warnVip01Hash = getHash("warn-vip01");
const warnUser01Hash = getHash("warn-user01");
const warnUser02Hash = getHash("warn-user02");
const MILLISECONDS_IN_HOUR = 3600000;
const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires;
const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES';
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')");
});
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,
@@ -130,6 +150,50 @@ describe('postSkipSegments', () => {
});
});
it('Should be rejected if user has to many active warnings', (done) => {
request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", {
json: {
userID: "warn-user01",
videoID: "dQw4w9WgXcF",
segments: [{
segment: [0, 10],
category: "sponsor"
}]
}
},
(err, res, body) => {
if (err) done(err);
else if (res.statusCode === 403) {
done(); // success
} else {
done("Status code was " + res.statusCode);
}
});
});
it('Should be accepted if user has some active warnings', (done) => {
request.post(utils.getbaseURL()
+ "/api/postVideoSponsorTimes", {
json: {
userID: "warn-user02",
videoID: "dQw4w9WgXcF",
segments: [{
segment: [50, 60],
category: "sponsor"
}]
}
},
(err, res, body) => {
if (err) done(err);
else if (res.statusCode === 200) {
done(); // success
} else {
done("Status code was " + res.statusCode + " " + body);
}
});
});
it('Should be allowed if youtube thinks duration is 0', (done) => {
request.get(utils.getbaseURL()
+ "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null,

47
test/cases/postWarning.js Normal file
View File

@@ -0,0 +1,47 @@
var request = require('request');
var utils = require('../utils.js');
var db = require('../../src/databases/databases.js').db;
var getHash = require('../../src/utils/getHash.js');
describe('postWarning', () => {
before(() => {
db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("warning-vip") + "')");
});
it('Should be able to create warning if vip (exp 200)', (done) => {
let json = {
issuerUserID: 'warning-vip',
userID: 'warning-0'
};
request.post(utils.getbaseURL()
+ "/api/warnUser", {json},
(err, res, body) => {
if (err) done(err);
else if (res.statusCode === 200) {
done();
} else {
console.log(body);
done("Status code was " + res.statusCode);
}
});
});
it('Should not be able to create warning if vip (exp 403)', (done) => {
let json = {
issuerUserID: 'warning-not-vip',
userID: 'warning-1'
};
request.post(utils.getbaseURL()
+ "/api/warnUser", {json},
(err, res, body) => {
if (err) done(err);
else if (res.statusCode === 403) {
done();
} else {
console.log(body);
done("Status code was " + res.statusCode);
}
});
});
});

273
test/cases/segmentShift.js Normal file
View File

@@ -0,0 +1,273 @@
const request = require('request');
const utils = require('../utils.js');
const { db } = require('../../src/databases/databases.js');
const getHash = require('../../src/utils/getHash.js');
function dbSponsorTimesAdd(db, videoID, startTime, endTime, UUID, category) {
const votes = 0,
userID = 0,
timeSubmitted = 0,
views = 0,
shadowHidden = 0,
hashedVideoID = `hash_${UUID}`;
db.exec(`INSERT INTO
sponsorTimes (videoID, startTime, endTime, votes, UUID,
userID, timeSubmitted, views, category, shadowHidden, hashedVideoID)
VALUES
('${videoID}', ${startTime}, ${endTime}, ${votes}, '${UUID}',
'${userID}', ${timeSubmitted}, ${views}, '${category}', ${shadowHidden}, '${hashedVideoID}')
`);
}
function dbSponsorTimesSetByUUID(db, UUID, startTime, endTime) {
db.prepare('run', `UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?`, [startTime, endTime, UUID]);
}
function dbSponsorTimesCompareExpect(db, expect) {
for (let i=0, len=expect.length; i<len; i++) {
const expectSeg = expect[i];
let seg = db.prepare('get', "SELECT startTime, endTime FROM sponsorTimes WHERE UUID = ?", [expectSeg.UUID]);
if ('removed' in expect) {
if (expect.removed === true && seg.votes === -2) {
return;
}
else {
return `${expectSeg.UUID} doesnt got removed`;
}
}
if (seg.startTime !== expectSeg.startTime) {
return `${expectSeg.UUID} startTime is incorrect. seg.startTime is ${seg.startTime} expected ${expectSeg.startTime}`;
}
if (seg.endTime !== expectSeg.endTime) {
return `${expectSeg.UUID} endTime is incorrect. seg.endTime is ${seg.endTime} expected ${expectSeg.endTime}`;
}
}
return;
}
describe('segmentShift', function() {
const privateVipUserID = 'VIPUser-segmentShift';
const vipUserID = getHash(privateVipUserID);
const baseURL = utils.getbaseURL();
before(function(done) {
// startTime and endTime get set in beforeEach for consistency
dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid01', 'intro');
dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid02', 'sponsor');
dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid03', 'interaction');
dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid04', 'outro');
db.exec(`INSERT INTO vipUsers (userID) VALUES ('${vipUserID}')`);
done();
});
beforeEach(function(done) {
// resetting startTime and endTime to reuse them
dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid01', 0, 10);
dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid02', 60, 90);
dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid03', 40, 45);
dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid04', 120, 140);
done();
});
it('Reject none VIP user', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: 'segshift_randomuser001',
startTime: 20,
endTime: 30,
}
}, (err, res, body) => {
if (err) return done(err);
return done(res.statusCode === 403 ? undefined : res.statusCode);
});
});
it('Shift is outside segments', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: privateVipUserID,
startTime: 20,
endTime: 30,
}
}, (err, res, body) => {
if (err) return done(err);
if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`);
const expect = [
{
UUID: 'vsegshifttest01uuid01',
startTime: 0,
endTime: 10,
},
{
UUID: 'vsegshifttest01uuid02',
startTime: 50,
endTime: 80,
},
{
UUID: 'vsegshifttest01uuid03',
startTime: 30,
endTime: 35,
},
{
UUID: 'vsegshifttest01uuid04',
startTime: 110,
endTime: 130,
},
];
done(dbSponsorTimesCompareExpect(db, expect));
});
});
it('Shift is inside segment', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: privateVipUserID,
startTime: 65,
endTime: 75,
}
}, (err, res, body) => {
if (err) return done(err);
if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`);
const expect = [
{
UUID: 'vsegshifttest01uuid01',
startTime: 0,
endTime: 10,
},
{
UUID: 'vsegshifttest01uuid02',
startTime: 60,
endTime: 80,
},
{
UUID: 'vsegshifttest01uuid03',
startTime: 40,
endTime: 45,
},
{
UUID: 'vsegshifttest01uuid04',
startTime: 110,
endTime: 130,
},
];
done(dbSponsorTimesCompareExpect(db, expect));
});
});
it('Shift is overlaping startTime of segment', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: privateVipUserID,
startTime: 32,
endTime: 42,
}
}, (err, res, body) => {
if (err) return done(err);
if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`);
const expect = [
{
UUID: 'vsegshifttest01uuid01',
startTime: 0,
endTime: 10,
},
{
UUID: 'vsegshifttest01uuid02',
startTime: 50,
endTime: 80,
},
{
UUID: 'vsegshifttest01uuid03',
startTime: 32,
endTime: 35,
},
{
UUID: 'vsegshifttest01uuid04',
startTime: 110,
endTime: 130,
},
];
done(dbSponsorTimesCompareExpect(db, expect));
});
});
it('Shift is overlaping endTime of segment', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: privateVipUserID,
startTime: 85,
endTime: 95,
}
}, (err, res, body) => {
if (err) return done(err);
if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`);
const expect = [
{
UUID: 'vsegshifttest01uuid01',
startTime: 0,
endTime: 10,
},
{
UUID: 'vsegshifttest01uuid02',
startTime: 60,
endTime: 85,
},
{
UUID: 'vsegshifttest01uuid03',
startTime: 40,
endTime: 45,
},
{
UUID: 'vsegshifttest01uuid04',
startTime: 110,
endTime: 130,
},
];
done(dbSponsorTimesCompareExpect(db, expect));
});
});
it('Shift is overlaping segment', function(done) {
request.post(`${baseURL}/api/segmentShift`, {
json: {
videoID: 'vsegshift01',
userID: privateVipUserID,
startTime: 35,
endTime: 55,
}
}, (err, res, body) => {
if (err) return done(err);
if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`);
const expect = [
{
UUID: 'vsegshifttest01uuid01',
startTime: 0,
endTime: 10,
},
{
UUID: 'vsegshifttest01uuid02',
startTime: 40,
endTime: 70,
},
{
UUID: 'vsegshifttest01uuid03',
startTime: 40,
endTime: 45,
removed: true,
},
{
UUID: 'vsegshifttest01uuid04',
startTime: 100,
endTime: 120,
},
];
done(dbSponsorTimesCompareExpect(db, expect));
});
});
});

View File

@@ -1,11 +1,19 @@
const request = require('request');
const config = require('../../src/config.js');
const { db, privateDB } = require('../../src/databases/databases.js');
const utils = require('../utils.js');
const getHash = require('../../src/utils/getHash.js');
describe('voteOnSponsorTime', () => {
before(() => {
const now = Date.now();
const warnVip01Hash = getHash("warn-vip01");
const warnUser01Hash = getHash("warn-voteuser01");
const warnUser02Hash = getHash("warn-voteuser02");
const MILLISECONDS_IN_HOUR = 3600000;
const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires;
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES";
const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES';
db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')");
db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest2', 1) + "')");
@@ -25,6 +33,17 @@ describe('voteOnSponsorTime', () => {
db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('not-own-submission-video', 1) + "')");
db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')");
db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')");
db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'warnvote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')");
db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')");
db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')");
privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')");
@@ -332,4 +351,17 @@ describe('voteOnSponsorTime', () => {
});
});
it('Should not be able to upvote a segment (Too many warning)', (done) => {
request.get(utils.getbaseURL()
+ "/api/voteOnSponsorTime?userID=warn-voteuser01&UUID=warnvote-uuid-0&type=1", null,
(err, res, body) => {
if (err) done(err);
else if (res.statusCode === 403) {
done(); // success
} else {
done("Status code was " + res.statusCode);
}
});
});
});