mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2026-01-30 06:10:52 +03:00
Merge branch 'master' into feature/getuserinfo
This commit is contained in:
@@ -24,5 +24,18 @@
|
|||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], // List of supported categories any other category will be rejected
|
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], // List of supported categories any other category will be rejected
|
||||||
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
|
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
|
||||||
"hoursAfterWarningExpire": 24
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -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": {
|
"extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
|||||||
@@ -14,13 +14,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^5.4.3",
|
"better-sqlite3": "^5.4.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-rate-limit": "^5.1.3",
|
||||||
"http": "0.0.0",
|
"http": "0.0.0",
|
||||||
"iso8601-duration": "^1.2.0",
|
"iso8601-duration": "^1.2.0",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
||||||
"sync-mysql": "^3.0.1",
|
"sync-mysql": "^3.0.1",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^3.3.2",
|
||||||
"youtube-api": "^2.0.10",
|
"youtube-api": "^2.0.10"
|
||||||
"node-fetch": "^2.6.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "^7.1.1",
|
"mocha": "^7.1.1",
|
||||||
|
|||||||
24
src/app.js
24
src/app.js
@@ -3,8 +3,11 @@ var express = require('express');
|
|||||||
var app = express();
|
var app = express();
|
||||||
var config = require('./config.js');
|
var config = require('./config.js');
|
||||||
var redis = require('./utils/redis.js');
|
var redis = require('./utils/redis.js');
|
||||||
|
const getIP = require('./utils/getIP.js');
|
||||||
|
const getHash = require('./utils/getHash.js');
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
const rateLimitMiddleware = require('./middleware/requestRateLimit.js');
|
||||||
var corsMiddleware = require('./middleware/cors.js');
|
var corsMiddleware = require('./middleware/cors.js');
|
||||||
var loggerMiddleware = require('./middleware/logger.js');
|
var loggerMiddleware = require('./middleware/logger.js');
|
||||||
const userCounter = require('./middleware/userCounter.js');
|
const userCounter = require('./middleware/userCounter.js');
|
||||||
@@ -28,11 +31,20 @@ var getUserInfo = require('./routes/getUserInfo.js');
|
|||||||
var postNoSegments = require('./routes/postNoSegments.js');
|
var postNoSegments = require('./routes/postNoSegments.js');
|
||||||
var getIsUserVIP = require('./routes/getIsUserVIP.js');
|
var getIsUserVIP = require('./routes/getIsUserVIP.js');
|
||||||
var warnUser = require('./routes/postWarning.js');
|
var warnUser = require('./routes/postWarning.js');
|
||||||
|
var postSegmentShift = require('./routes/postSegmentShift.js');
|
||||||
|
|
||||||
// Old Routes
|
// Old Routes
|
||||||
var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js');
|
var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js');
|
||||||
var oldSubmitSponsorTimes = require('./routes/oldSubmitSponsorTimes.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
|
//setup CORS correctly
|
||||||
app.use(corsMiddleware);
|
app.use(corsMiddleware);
|
||||||
app.use(loggerMiddleware);
|
app.use(loggerMiddleware);
|
||||||
@@ -61,12 +73,12 @@ app.post('/api/skipSegments', postSkipSegments);
|
|||||||
app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash);
|
app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash);
|
||||||
|
|
||||||
//voting endpoint
|
//voting endpoint
|
||||||
app.get('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint);
|
app.get('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||||
app.post('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint);
|
app.post('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||||
|
|
||||||
//Endpoint when a sponsorTime is used up
|
//Endpoint when a submission is skipped
|
||||||
app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
|
app.get('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||||
app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
|
app.post('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||||
|
|
||||||
//To set your username for the stats view
|
//To set your username for the stats view
|
||||||
app.post('/api/setUsername', setUsername);
|
app.post('/api/setUsername', setUsername);
|
||||||
@@ -109,6 +121,8 @@ app.get('/api/isUserVIP', getIsUserVIP);
|
|||||||
//sent user a warning
|
//sent user a warning
|
||||||
app.post('/api/warnUser', warnUser);
|
app.post('/api/warnUser', warnUser);
|
||||||
|
|
||||||
|
//get if user is a vip
|
||||||
|
app.post('/api/segmentShift', postSegmentShift);
|
||||||
|
|
||||||
app.get('/database.db', function (req, res) {
|
app.get('/database.db', function (req, res) {
|
||||||
res.sendFile("./databases/sponsorTimes.db", { root: "./" });
|
res.sendFile("./databases/sponsorTimes.db", { root: "./" });
|
||||||
|
|||||||
18
src/middleware/requestRateLimit.js
Normal file
18
src/middleware/requestRateLimit.js
Normal 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';
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var request = require('request');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
const config = require('../config.js');
|
const config = require('../config.js');
|
||||||
const getIP = require('../utils/getIP.js');
|
const getIP = require('../utils/getIP.js');
|
||||||
@@ -6,11 +6,8 @@ const getHash = require('../utils/getHash.js');
|
|||||||
const logger = require('../utils/logger.js');
|
const logger = require('../utils/logger.js');
|
||||||
|
|
||||||
module.exports = function userCounter(req, res, next) {
|
module.exports = function userCounter(req, res, next) {
|
||||||
try {
|
fetch(config.userCounterURL + "/api/v1/addIP?hashedIP=" + getHash(getIP(req), 1), { method: "POST" })
|
||||||
request.post(config.userCounterURL + "/api/v1/addIP?hashedIP=" + getHash(getIP(req), 1));
|
.catch(() => logger.debug("Failing to connect to user counter at: " + config.userCounterURL))
|
||||||
} catch(e) {
|
|
||||||
logger.debug("Failing to connect to user counter at: " + config.userCounterURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,6 @@ module.exports = async function (req, res) {
|
|||||||
|
|
||||||
// Get all video id's that match hash prefix
|
// Get all video id's that match hash prefix
|
||||||
const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix+'%']);
|
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) => {
|
let segments = videoIds.map((video) => {
|
||||||
return {
|
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);
|
||||||
}
|
}
|
||||||
101
src/routes/postSegmentShift.js
Normal file
101
src/routes/postSegmentShift.js
Normal 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);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ const db = databases.db;
|
|||||||
const privateDB = databases.privateDB;
|
const privateDB = databases.privateDB;
|
||||||
const YouTubeAPI = require('../utils/youtubeAPI.js');
|
const YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||||
const logger = require('../utils/logger.js');
|
const logger = require('../utils/logger.js');
|
||||||
|
const getSubmissionUUID = require('../utils/getSubmissionUUID.js');
|
||||||
const request = require('request');
|
const request = require('request');
|
||||||
const isoDurations = require('iso8601-duration');
|
const isoDurations = require('iso8601-duration');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
@@ -201,8 +202,7 @@ async function autoModerateSubmission(submission) {
|
|||||||
startTime = parseFloat(segments[i].segment[0]);
|
startTime = parseFloat(segments[i].segment[0]);
|
||||||
endTime = parseFloat(segments[i].segment[1]);
|
endTime = parseFloat(segments[i].segment[1]);
|
||||||
|
|
||||||
let UUID = getHash("v2-categories" + submission.videoID + startTime +
|
const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime);
|
||||||
endTime + segments[i].category + submission.userID, 1);
|
|
||||||
// Send to Discord
|
// Send to Discord
|
||||||
// Note, if this is too spammy. Consider sending all the segments as one Webhook
|
// 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);
|
sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data);
|
||||||
@@ -301,12 +301,15 @@ module.exports = async function postSkipSegments(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reject segemnt if it's in the no segments list
|
// Reject segemnt if it's in the no segments list
|
||||||
if (noSegmentList.indexOf(segments[i].category) !== -1) {
|
if (!isVIP && noSegmentList.indexOf(segments[i].category) !== -1) {
|
||||||
// TODO: Do something about the fradulent submission
|
// TODO: Do something about the fradulent submission
|
||||||
logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'");
|
logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'");
|
||||||
res.status(403).send(
|
res.status(403).send(
|
||||||
"Request rejected by auto moderator: This video has been reported as not containing any segments with the category '"
|
"Request rejected by auto moderator: New submissions are not allowed for the following category: '"
|
||||||
+ segments[i].category + "'. If you believe this is incorrect, contact someone on Discord."
|
+ segments[i].category + "'. A moderator has decided that no new segments are needed and that all current segments of this category are timed perfectly.\n\n "
|
||||||
|
+ (segments[i].category === "sponsor" ? "Maybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " +
|
||||||
|
"Categories that aren't sponsor, such as self-promotion can be enabled in the options.\n\n " : "")
|
||||||
|
+ "If you believe this is incorrect, please contact someone on Discord."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -407,8 +410,7 @@ module.exports = async function postSkipSegments(req, res) {
|
|||||||
//this can just be a hash of the data
|
//this can just be a hash of the data
|
||||||
//it's better than generating an actual UUID like what was used before
|
//it's better than generating an actual UUID like what was used before
|
||||||
//also better for duplication checking
|
//also better for duplication checking
|
||||||
let UUID = getHash("v2-categories" + videoID + segmentInfo.segment[0] +
|
const UUID = getSubmissionUUID(videoID, segmentInfo.category, userID, segmentInfo.segment[0], segmentInfo.segment[1]);
|
||||||
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.prepare('run', "INSERT INTO sponsorTimes " +
|
db.prepare('run', "INSERT INTO sponsorTimes " +
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ var privateDB = databases.privateDB;
|
|||||||
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
const logger = require('../utils/logger.js');
|
const logger = require('../utils/logger.js');
|
||||||
|
const isUserVIP = require('../utils/isUserVIP.js');
|
||||||
|
|
||||||
const voteTypes = {
|
const voteTypes = {
|
||||||
normal: 0,
|
normal: 0,
|
||||||
@@ -134,7 +135,7 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
|
|||||||
// Check if they've already made a vote
|
// Check if they've already made a vote
|
||||||
let previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]);
|
let previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]);
|
||||||
|
|
||||||
if (previousVoteInfo > 0 && previousVoteInfo.category === category) {
|
if (previousVoteInfo !== undefined && previousVoteInfo.category === category) {
|
||||||
// Double vote, ignore
|
// Double vote, ignore
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
return;
|
return;
|
||||||
@@ -166,23 +167,34 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the info into the private db
|
// Add the info into the private db
|
||||||
if (previousVoteInfo > 0) {
|
if (previousVoteInfo !== undefined) {
|
||||||
// Reverse the previous vote
|
// Reverse the previous vote
|
||||||
db.prepare('run', "update categoryVotes set votes -= 1 where UUID = ? and category = ?", [UUID, previousVoteInfo.category]);
|
db.prepare('run', "update categoryVotes set votes = votes - ? where UUID = ? and category = ?", [voteAmount, UUID, previousVoteInfo.category]);
|
||||||
|
|
||||||
privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?", [category, timeSubmitted, hashedIP]);
|
privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ? where userID = ?", [category, timeSubmitted, hashedIP, userID]);
|
||||||
} else {
|
} else {
|
||||||
privateDB.prepare('run', "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, userID, hashedIP, category, timeSubmitted]);
|
privateDB.prepare('run', "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, userID, hashedIP, category, timeSubmitted]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if the submissions category is ready to change
|
// See if the submissions category is ready to change
|
||||||
let currentCategoryInfo = db.prepare('get', "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]);
|
let currentCategoryInfo = db.prepare("get", "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]);
|
||||||
|
|
||||||
|
let submissionInfo = db.prepare("get", "SELECT userID, timeSubmitted FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||||
|
let isSubmissionVIP = submissionInfo && isUserVIP(submissionInfo.userID);
|
||||||
|
let startingVotes = isSubmissionVIP ? 10000 : 1;
|
||||||
|
|
||||||
// Change this value from 1 in the future to make it harder to change categories
|
// Change this value from 1 in the future to make it harder to change categories
|
||||||
// Done this way without ORs incase the value is zero
|
// Done this way without ORs incase the value is zero
|
||||||
let currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? 1 : currentCategoryInfo.votes;
|
let currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? startingVotes : currentCategoryInfo.votes;
|
||||||
|
|
||||||
let nextCategoryCount = (previousVoteInfo.votes || 0) + 1;
|
// Add submission as vote
|
||||||
|
if (!currentCategoryInfo && submissionInfo) {
|
||||||
|
db.prepare("run", "insert into categoryVotes (UUID, category, votes) values (?, ?, ?)", [UUID, currentCategory.category, currentCategoryCount]);
|
||||||
|
|
||||||
|
privateDB.prepare("run", "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, submissionInfo.userID, "unknown", currentCategory.category, submissionInfo.timeSubmitted]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextCategoryCount = (previousVoteInfo.votes || 0) + voteAmount;
|
||||||
|
|
||||||
//TODO: In the future, raise this number from zero to make it harder to change categories
|
//TODO: In the future, raise this number from zero to make it harder to change categories
|
||||||
// VIPs change it every time
|
// VIPs change it every time
|
||||||
@@ -223,7 +235,7 @@ async function voteOnSponsorTime(req, res) {
|
|||||||
let isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined;
|
let isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined;
|
||||||
|
|
||||||
if (type === undefined && category !== undefined) {
|
if (type === undefined && category !== undefined) {
|
||||||
return categoryVote(UUID, userID, isVIP, category, hashedIP, res);
|
return categoryVote(UUID, nonAnonUserID, isVIP, category, hashedIP, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == 1 && !isVIP && !isOwnSubmission) {
|
if (type == 1 && !isVIP && !isOwnSubmission) {
|
||||||
@@ -365,17 +377,19 @@ async function voteOnSponsorTime(req, res) {
|
|||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
|
|
||||||
sendWebhooks({
|
if (incrementAmount - oldIncrementAmount !== 0) {
|
||||||
UUID,
|
sendWebhooks({
|
||||||
nonAnonUserID,
|
UUID,
|
||||||
voteTypeEnum,
|
nonAnonUserID,
|
||||||
isVIP,
|
voteTypeEnum,
|
||||||
isOwnSubmission,
|
isVIP,
|
||||||
row,
|
isOwnSubmission,
|
||||||
category,
|
row,
|
||||||
incrementAmount,
|
category,
|
||||||
oldIncrementAmount
|
incrementAmount,
|
||||||
});
|
oldIncrementAmount
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
|
||||||
|
|||||||
7
src/utils/getSubmissionUUID.js
Normal file
7
src/utils/getSubmissionUUID.js
Normal 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);
|
||||||
|
};
|
||||||
@@ -19,6 +19,11 @@ if (config.mode === "test") {
|
|||||||
|
|
||||||
// YouTubeAPI.videos.list wrapper with cacheing
|
// YouTubeAPI.videos.list wrapper with cacheing
|
||||||
exportObject.listVideos = (videoID, part, callback) => {
|
exportObject.listVideos = (videoID, part, callback) => {
|
||||||
|
if (videoID.length !== 11 || videoID.includes(".")) {
|
||||||
|
callback("Invalid video ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let redisKey = "youtube.video." + videoID + "." + part;
|
let redisKey = "youtube.video." + videoID + "." + part;
|
||||||
redis.get(redisKey, (getErr, result) => {
|
redis.get(redisKey, (getErr, result) => {
|
||||||
if (getErr || !result) {
|
if (getErr || !result) {
|
||||||
|
|||||||
15
test.json
15
test.json
@@ -51,5 +51,18 @@
|
|||||||
],
|
],
|
||||||
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"],
|
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"],
|
||||||
"maxNumberOfActiveWarnings": 3,
|
"maxNumberOfActiveWarnings": 3,
|
||||||
"hoursAfterWarningExpires": 24
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
request.get(utils.getbaseURL()
|
||||||
+ '/api/skipSegments/11111?categories=["shilling"]', null,
|
+ '/api/skipSegments/11111?categories=["shilling"]', null,
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done("Couldn't call endpoint");
|
if (err) done("Couldn't call endpoint");
|
||||||
else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode);
|
else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode);
|
||||||
else {
|
else {
|
||||||
done(); // pass
|
if (JSON.parse(body).length === 0 && body === '[]') done(); // pass
|
||||||
|
else done("non empty array returned");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
8
test/cases/getSubmissionUUID.js
Normal file
8
test/cases/getSubmissionUUID.js
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
273
test/cases/segmentShift.js
Normal file
273
test/cases/segmentShift.js
Normal 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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user