mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-13 02:14:32 +01:00
Merge pull request #71 from ajayyy/categories
Categories + Github Actions and Tests Fixes
This commit is contained in:
commit
b2147d42a8
20 changed files with 978 additions and 369 deletions
18
.github/workflows/ci.yml
vendored
Normal file
18
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Initialization
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
|
||||
- name: Run Tests
|
||||
run: npm test
|
|
@ -11,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" (
|
||||
|
|
24
src/app.js
24
src/app.js
|
@ -8,8 +8,8 @@ var corsMiddleware = require('./middleware/cors.js');
|
|||
var loggerMiddleware = require('./middleware/logger.js');
|
||||
|
||||
// Routes
|
||||
var getVideoSponsorTimes = require('./routes/getVideoSponsorTimes.js');
|
||||
var submitSponsorTimes = require('./routes/submitSponsorTimes.js');
|
||||
var getSkipSegments = require('./routes/getSkipSegments.js').endpoint;
|
||||
var postSkipSegments = require('./routes/postSkipSegments.js');
|
||||
var voteOnSponsorTime = require('./routes/voteOnSponsorTime.js');
|
||||
var viewedVideoSponsorTime = require('./routes/viewedVideoSponsorTime.js');
|
||||
var setUsername = require('./routes/setUsername.js');
|
||||
|
@ -22,17 +22,29 @@ var getTopUsers = require('./routes/getTopUsers.js');
|
|||
var getTotalStats = require('./routes/getTotalStats.js');
|
||||
var getDaysSavedFormatted = require('./routes/getDaysSavedFormatted.js');
|
||||
|
||||
// Old Routes
|
||||
var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js');
|
||||
var oldSubmitSponsorTimes = require('./routes/oldSubmitSponsorTimes.js');
|
||||
|
||||
|
||||
//setup CORS correctly
|
||||
app.use(corsMiddleware);
|
||||
app.use(loggerMiddleware);
|
||||
app.use(express.json())
|
||||
|
||||
// Setup pretty JSON
|
||||
if (config.mode === "development") app.set('json spaces', 2);
|
||||
|
||||
//add the get function
|
||||
app.get('/api/getVideoSponsorTimes', getVideoSponsorTimes);
|
||||
app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes);
|
||||
|
||||
//add the post function
|
||||
app.get('/api/postVideoSponsorTimes', submitSponsorTimes);
|
||||
app.post('/api/postVideoSponsorTimes', submitSponsorTimes);
|
||||
//add the oldpost function
|
||||
app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
|
||||
//add the skip segments functions
|
||||
app.get('/api/skipSegments', getSkipSegments);
|
||||
app.post('/api/skipSegments', postSkipSegments);
|
||||
|
||||
//voting endpoint
|
||||
app.get('/api/voteOnSponsorTime', voteOnSponsorTime);
|
||||
|
|
|
@ -200,71 +200,109 @@ function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns what would be sent to the client.
|
||||
* Will resond with errors if required. Returns false if it errors.
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
function handleGetSegments(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 ? [req.query.category] : ["sponsor"]);
|
||||
|
||||
/**
|
||||
* @type {Array<{
|
||||
* segment: number[],
|
||||
* category: string,
|
||||
* UUID: string
|
||||
* }>
|
||||
* }
|
||||
*/
|
||||
let segments = [];
|
||||
|
||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
|
||||
try {
|
||||
for (const category of categories) {
|
||||
let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime")
|
||||
.all(videoID, category);
|
||||
|
||||
let sponsorTimes = [];
|
||||
let votes = []
|
||||
let UUIDs = [];
|
||||
|
||||
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([rows[i].startTime, rows[i].endTime]);
|
||||
votes.push(rows[i].votes);
|
||||
UUIDs.push(rows[i].UUID);
|
||||
}
|
||||
|
||||
if (sponsorTimes.length == 0) {
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
||||
organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs);
|
||||
sponsorTimes = organisedData.sponsorTimes;
|
||||
UUIDs = organisedData.UUIDs;
|
||||
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
segments.push({
|
||||
segment: sponsorTimes[i],
|
||||
category: category,
|
||||
UUID: UUIDs[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
res.send(500);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segments.length == 0) {
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
|
||||
module.exports = function (req, res) {
|
||||
let videoID = req.query.videoID;
|
||||
module.exports = {
|
||||
handleGetSegments,
|
||||
endpoint: function (req, res) {
|
||||
let segments = handleGetSegments(req, res);
|
||||
|
||||
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);
|
||||
}
|
||||
if (segments) {
|
||||
//send result
|
||||
res.send(segments)
|
||||
}
|
||||
}
|
||||
}
|
26
src/routes/oldGetVideoSponsorTimes.js
Normal file
26
src/routes/oldGetVideoSponsorTimes.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
var getSkipSegments = require("./getSkipSegments.js")
|
||||
|
||||
|
||||
module.exports = function (req, res) {
|
||||
let videoID = req.query.videoID;
|
||||
|
||||
let segments = getSkipSegments.handleGetSegments(req, res);
|
||||
|
||||
if (segments) {
|
||||
// Convert to old outputs
|
||||
let sponsorTimes = [];
|
||||
let UUIDs = [];
|
||||
|
||||
for (const segment of segments) {
|
||||
sponsorTimes.push(segment.segment);
|
||||
UUIDs.push(segment.UUID);
|
||||
}
|
||||
|
||||
res.send({
|
||||
sponsorTimes,
|
||||
UUIDs
|
||||
})
|
||||
}
|
||||
|
||||
// Error has already been handled in the other method
|
||||
}
|
9
src/routes/oldSubmitSponsorTimes.js
Normal file
9
src/routes/oldSubmitSponsorTimes.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
var config = require('../config.js');
|
||||
|
||||
var postSkipSegments = require('./postSkipSegments.js');
|
||||
|
||||
module.exports = async function submitSponsorTimes(req, res) {
|
||||
req.query.category = "sponsor";
|
||||
|
||||
return postSkipSegments(req, res);
|
||||
}
|
266
src/routes/postSkipSegments.js
Normal file
266
src/routes/postSkipSegments.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
var config = require('../config.js');
|
||||
|
||||
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');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||
|
||||
// TODO: might need to be a util
|
||||
//returns true if the user is considered trustworthy
|
||||
//this happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
async function isUserTrustworthy(userID) {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
let totalSubmissionsRow = db.prepare("SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
let downvotedSubmissionsRow = db.prepare("SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)").get(userID);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||
//check if they are a first time user
|
||||
//if so, send a notification to discord
|
||||
if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null) {
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
|
||||
// If it is a first time submission
|
||||
if (userSubmissionCountRow.submissionCount <= 1) {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "snippet",
|
||||
id: videoID
|
||||
}, function (err, data) {
|
||||
if (err || data.items.length === 0) {
|
||||
err && console.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
let startTime = parseFloat(segmentInfo.segment[0]);
|
||||
let endTime = parseFloat(segmentInfo.segment[1]);
|
||||
|
||||
request.post(config.discordFirstTimeSubmissionsWebhookURL, {
|
||||
json: {
|
||||
"embeds": [{
|
||||
"title": data.items[0].snippet.title,
|
||||
"url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2),
|
||||
"description": "Submission ID: " + UUID +
|
||||
"\n\nTimestamp: " +
|
||||
getFormattedTime(startTime) + " to " + getFormattedTime(endTime) +
|
||||
"\n\nCategory: " + segmentInfo.category,
|
||||
"color": 10813440,
|
||||
"author": {
|
||||
"name": userID
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||
}
|
||||
}]
|
||||
}
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
console.log("Failed to send first time submission Discord hook.");
|
||||
console.log(JSON.stringify(err));
|
||||
console.log("\n");
|
||||
} else if (res && res.statusCode >= 400) {
|
||||
console.log("Error sending first time submission Discord hook");
|
||||
console.log(JSON.stringify(res));
|
||||
console.log("\n");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
let segments = req.body.segments;
|
||||
|
||||
if (segments === undefined) {
|
||||
// Use query instead
|
||||
segments = [{
|
||||
segment: [req.query.startTime, req.query.endTime],
|
||||
category: req.query.category
|
||||
}];
|
||||
}
|
||||
|
||||
//check if all correct inputs are here and the length is 1 second or more
|
||||
if (videoID == undefined || userID == undefined || segments == undefined || segments.length < 1) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
|
||||
//hash the ip 5000 times so no one can get it from the database
|
||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
|
||||
// Check if all submissions are correct
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
if (segments[i] === undefined || segments[i].segment === undefined || segments[i].category === undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
let startTime = parseFloat(segments[i].segment[0]);
|
||||
let endTime = parseFloat(segments[i].segment[1]);
|
||||
|
||||
if (Math.abs(startTime - endTime) < 1 || isNaN(startTime) || isNaN(endTime)
|
||||
|| startTime === Infinity || endTime === Infinity || startTime > endTime) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//check if this info has already been submitted before
|
||||
let duplicateCheck2Row = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
||||
"and endTime = ? and category = ? and videoID = ?").get(startTime, endTime, segments[i].category, videoID);
|
||||
if (duplicateCheck2Row.count > 0) {
|
||||
res.sendStatus(409);
|
||||
return;
|
||||
}
|
||||
|
||||
let autoModerateResult = await autoModerateSubmission({videoID, startTime, endTime});
|
||||
if (autoModerateResult) {
|
||||
res.status(403).send("Request rejected by auto moderator: " + autoModerateResult);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//check if this user is on the vip list
|
||||
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID);
|
||||
|
||||
//get current time
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let yesterday = timeSubmitted - 86400000;
|
||||
|
||||
//check to see if this ip has submitted too many sponsors today
|
||||
let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]);
|
||||
|
||||
if (rateLimitCheckRow.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
res.sendStatus(429);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if the user has already submitted sponsors for this video
|
||||
let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]);
|
||||
|
||||
if (duplicateCheckRow.count >= 8) {
|
||||
//too many sponsors for the same video from the same user
|
||||
res.sendStatus(429);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if this user is shadowbanned
|
||||
let shadowBanRow = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
|
||||
let shadowBanned = shadowBanRow.userCount;
|
||||
|
||||
if (!(await isUserTrustworthy(userID))) {
|
||||
//hide this submission as this user is untrustworthy
|
||||
shadowBanned = 1;
|
||||
}
|
||||
|
||||
let startingVotes = 0;
|
||||
if (vipRow.userCount > 0) {
|
||||
//this user is a vip, start them at a higher approval rating
|
||||
startingVotes = 10;
|
||||
}
|
||||
|
||||
for (const segmentInfo of segments) {
|
||||
//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);
|
||||
|
||||
try {
|
||||
db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0],
|
||||
segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned);
|
||||
|
||||
//add to private db as well
|
||||
privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted);
|
||||
} catch (err) {
|
||||
//a DB change probably occurred
|
||||
res.sendStatus(502);
|
||||
console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
|
||||
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Discord notification
|
||||
sendDiscordNotification(userID, videoID, UUID, segmentInfo);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
var config = require('../config.js');
|
||||
|
||||
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');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||
|
||||
// TODO: might need to be a util
|
||||
//returns true if the user is considered trustworthy
|
||||
//this happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
async function isUserTrustworthy(userID) {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
let totalSubmissionsRow = db.prepare("SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
let downvotedSubmissionsRow = db.prepare("SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)").get(userID);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = async function submitSponsorTimes(req, res) {
|
||||
let videoID = req.query.videoID;
|
||||
let startTime = req.query.startTime;
|
||||
let endTime = req.query.endTime;
|
||||
let userID = req.query.userID;
|
||||
|
||||
//check if all correct inputs are here and the length is 1 second or more
|
||||
if (videoID == undefined || startTime == undefined || endTime == undefined || userID == undefined
|
||||
|| Math.abs(startTime - endTime) < 1) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
|
||||
//hash the ip 5000 times so no one can get it from the database
|
||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
|
||||
startTime = parseFloat(startTime);
|
||||
endTime = parseFloat(endTime);
|
||||
|
||||
if (isNaN(startTime) || isNaN(endTime)) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (startTime === Infinity || endTime === Infinity) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (startTime > endTime) {
|
||||
//time can't go backwards
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//check if this user is on the vip list
|
||||
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID);
|
||||
|
||||
//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(videoID + startTime + endTime + userID, 1);
|
||||
|
||||
//get current time
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let yesterday = timeSubmitted - 86400000;
|
||||
|
||||
//check to see if this ip has submitted too many sponsors today
|
||||
let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]);
|
||||
|
||||
if (rateLimitCheckRow.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
res.sendStatus(429);
|
||||
} else {
|
||||
//check to see if the user has already submitted sponsors for this video
|
||||
let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]);
|
||||
|
||||
if (duplicateCheckRow.count >= 8) {
|
||||
//too many sponsors for the same video from the same user
|
||||
res.sendStatus(429);
|
||||
} else {
|
||||
//check if this info has already been submitted first
|
||||
let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]);
|
||||
|
||||
//check to see if this user is shadowbanned
|
||||
let shadowBanRow = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
|
||||
let shadowBanned = shadowBanRow.userCount;
|
||||
|
||||
if (!(await isUserTrustworthy(userID))) {
|
||||
//hide this submission as this user is untrustworthy
|
||||
shadowBanned = 1;
|
||||
}
|
||||
|
||||
let startingVotes = 0;
|
||||
if (vipRow.userCount > 0) {
|
||||
//this user is a vip, start them at a higher approval rating
|
||||
startingVotes = 10;
|
||||
}
|
||||
|
||||
if (duplicateCheck2Row == null) {
|
||||
//not a duplicate
|
||||
|
||||
autoModerateSubmission({videoID, startTime, endTime}, (reject) => {
|
||||
if (!reject) {
|
||||
try {
|
||||
db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, startTime, endTime, startingVotes, UUID, userID, timeSubmitted, 0, shadowBanned);
|
||||
|
||||
//add to private db as well
|
||||
privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted);
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
//a DB change probably occurred
|
||||
console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + startTime + ", " + "endTime" + ", " + userID);
|
||||
res.sendStatus(502);
|
||||
}
|
||||
} else {
|
||||
res.status(403).send("Request rejected by auto moderator: " + reject);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(409);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.send(500);
|
||||
}
|
||||
}
|
||||
|
||||
// submission: {videoID, startTime, endTime}
|
||||
// callback: function(reject: "String containing reason the submission was rejected")
|
||||
const autoModerateSubmission = (submission, callback) => {
|
||||
// Get the video information from the youtube API
|
||||
if (config.youtubeAPI !== null) {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "contentDetails",
|
||||
id: submission.videoID
|
||||
}, (err, data) => {
|
||||
if (err) callback("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) {
|
||||
callback ("Sponsor segment is over 80% of the video.");
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("skip youtube api");
|
||||
// Can't moderate the submission without calling the youtube API
|
||||
// so allow by default.
|
||||
callback();
|
||||
}
|
||||
}
|
||||
/*
|
||||
//check if they are a first time user
|
||||
//if so, send a notification to discord
|
||||
if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null && duplicateCheck2Row == null) {
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
// If it is a first time submission
|
||||
if (userSubmissionCountRow.submissionCount <= 1) {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "snippet",
|
||||
id: videoID
|
||||
}, function (err, data) {
|
||||
if (err || data.items.length === 0) {
|
||||
err && console.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(data));
|
||||
|
||||
request.post(config.discordFirstTimeSubmissionsWebhookURL, {
|
||||
json: {
|
||||
"embeds": [{
|
||||
"title": data.items[0].snippet.title,
|
||||
"url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2),
|
||||
"description": "Submission ID: " + UUID +
|
||||
"\n\nTimestamp: " +
|
||||
getFormattedTime(startTime) + " to " + getFormattedTime(endTime),
|
||||
"color": 10813440,
|
||||
"author": {
|
||||
"name": userID
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||
}
|
||||
}]
|
||||
}
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
console.log("Failed to send first time submission Discord hook.");
|
||||
console.log(JSON.stringify(err));
|
||||
console.log("\n");
|
||||
} else if (res && res.statusCode >= 400) {
|
||||
console.log("Error sending first time submission Discord hook");
|
||||
console.log(JSON.stringify(res));
|
||||
console.log("\n");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}*/
|
|
@ -1,5 +1,5 @@
|
|||
var fs = require('fs');
|
||||
var config = JSON.parse(fs.readFileSync('config.json'));
|
||||
var config = require('../config.js');
|
||||
|
||||
module.exports = function getIP(req) {
|
||||
return config.behindProxy ? req.headers['x-forwarded-for'] : req.connection.remoteAddress;
|
||||
|
|
5
test.js
5
test.js
|
@ -2,6 +2,11 @@ var Mocha = require('mocha'),
|
|||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
var config = require('./src/config.js');
|
||||
// delete old test database
|
||||
if (fs.existsSync(config.db)) fs.unlinkSync(config.db);
|
||||
if (fs.existsSync(config.privateDB)) fs.unlinkSync(config.privateDB);
|
||||
|
||||
var createServer = require('./src/app.js');
|
||||
var createMockServer = require('./test/mocks.js');
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ 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) => {
|
||||
|
|
248
test/cases/getSkipSegments.js
Normal file
248
test/cases/getSkipSegments.js
Normal file
|
@ -0,0 +1,248 @@
|
|||
var request = require('request');
|
||||
var db = require('../../src/databases/databases.js').db;
|
||||
var utils = require('../utils.js');
|
||||
|
||||
/*
|
||||
*CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"shadowHidden" INTEGER NOT NULL
|
||||
);
|
||||
*/
|
||||
|
||||
describe('getSkipSegments', () => {
|
||||
before(() => {
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0)");
|
||||
});
|
||||
|
||||
|
||||
it('Should be able to get a time by category (Query Method) 1', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&category=sponsor", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = JSON.parse(res.body);
|
||||
if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11
|
||||
&& data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get a time by category (Query Method) 2', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&category=intro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = JSON.parse(res.body);
|
||||
if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33
|
||||
&& data[0].category === "intro" && data[0].UUID === "1-uuid-2") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get a time by category (JSON Method) 1', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments", {
|
||||
json: {
|
||||
videoID: "testtesttest",
|
||||
categories: ["sponsor"]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = res.body;
|
||||
if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11
|
||||
&& data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get a time by category (JSON Method) 2', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments", {
|
||||
json: {
|
||||
videoID: "testtesttest",
|
||||
categories: ["intro"]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = res.body;
|
||||
if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33
|
||||
&& data[0].category === "intro" && data[0].UUID === "1-uuid-2") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get multiple times by category (JSON Method) 1', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments", {
|
||||
json: {
|
||||
videoID: "multiple",
|
||||
categories: ["intro"]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = res.body;
|
||||
if (data.length === 2) {
|
||||
|
||||
let success = true;
|
||||
for (const segment of data) {
|
||||
if ((segment.segment[0] !== 20 || segment.segment[1] !== 33
|
||||
|| segment.category !== "intro" || segment.UUID !== "1-uuid-7") &&
|
||||
(segment.segment[0] !== 1 || segment.segment[1] !== 11
|
||||
|| segment.category !== "intro" || segment.UUID !== "1-uuid-6")) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) done();
|
||||
else done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
} else {
|
||||
done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get multiple times by multiple categories (JSON Method)', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments", {
|
||||
json: {
|
||||
videoID: "testtesttest",
|
||||
categories: ["sponsor", "intro"]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = res.body;
|
||||
if (data.length === 2) {
|
||||
|
||||
let success = true;
|
||||
for (const segment of data) {
|
||||
if ((segment.segment[0] !== 20 || segment.segment[1] !== 33
|
||||
|| segment.category !== "intro" || segment.UUID !== "1-uuid-2") &&
|
||||
(segment.segment[0] !== 1 || segment.segment[1] !== 11
|
||||
|| segment.category !== "sponsor" || segment.UUID !== "1-uuid-0")) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) done();
|
||||
else done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
} else {
|
||||
done("Received incorrect body: " + JSON.stringify(res.body));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be possible to send unexpected query parameters', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&fakeparam=hello&category=sponsor", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = JSON.parse(res.body);
|
||||
if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11
|
||||
&& data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Low voted submissions should be hidden', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=test3&category=sponsor", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = JSON.parse(res.body);
|
||||
if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11
|
||||
&& data[0].category === "sponsor" && data[0].UUID === "1-uuid-4") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return 404 if no segment found', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=notarealvideo", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couldn't call endpoint");
|
||||
else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode);
|
||||
else done(); // pass
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Should be able send a comma in a query param', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest,test&category=sponsor", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("Status code was: " + res.statusCode);
|
||||
else {
|
||||
let data = JSON.parse(res.body);
|
||||
if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11
|
||||
&& data[0].category === "sponsor" && data[0].UUID === "1-uuid-1") {
|
||||
done();
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -17,15 +17,15 @@ var utils = require('../utils.js');
|
|||
);
|
||||
*/
|
||||
|
||||
describe('getVideoSponsorTime', () => {
|
||||
describe('getVideoSponsorTime (Old get method)', () => {
|
||||
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 ('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('old-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,
|
||||
+ "/api/getVideoSponsorTimes?videoID=old-testtesttest", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
||||
|
@ -46,7 +46,7 @@ describe('getVideoSponsorTime', () => {
|
|||
|
||||
it('Should be possible to send unexpected query parameters', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getVideoSponsorTimes?videoID=testtesttest&fakeparam=hello", null,
|
||||
+ "/api/getVideoSponsorTimes?videoID=old-testtesttest&fakeparam=hello", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couldn't callendpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
||||
|
@ -56,7 +56,7 @@ describe('getVideoSponsorTime', () => {
|
|||
|
||||
it('Should be able send a comma in a query param', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getVideoSponsorTimes?videoID=testtesttest,test", null,
|
||||
+ "/api/getVideoSponsorTimes?videoID=old-testtesttest,test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couln't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200 response: " + res.statusCode);
|
||||
|
@ -67,7 +67,7 @@ describe('getVideoSponsorTime', () => {
|
|||
|
||||
it('Should be able to get the correct time', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getVideoSponsorTimes?videoID=testtesttest", null,
|
||||
+ "/api/getVideoSponsorTimes?videoID=old-testtesttest", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
55
test/cases/oldSubmitSponsorTimes.js
Normal file
55
test/cases/oldSubmitSponsorTimes.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
var assert = require('assert');
|
||||
var request = require('request');
|
||||
|
||||
var utils = require('../utils.js');
|
||||
|
||||
var databases = require('../../src/databases/databases.js');
|
||||
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=dQw4w9WgXcQ&startTime=1&endTime=10&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
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()
|
||||
} else {
|
||||
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to submit a time (POST)', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcE&startTime=1&endTime=11&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
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()
|
||||
} else {
|
||||
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return 400 for missing params', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
if (res.statusCode === 400) done();
|
||||
else done("Status code was: " + res.statusCode);
|
||||
});
|
||||
});
|
||||
});
|
212
test/cases/postSkipSegments.js
Normal file
212
test/cases/postSkipSegments.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
var assert = require('assert');
|
||||
var request = require('request');
|
||||
|
||||
var utils = require('../utils.js');
|
||||
|
||||
var databases = require('../../src/databases/databases.js');
|
||||
var db = databases.db;
|
||||
|
||||
describe('postSkipSegments', () => {
|
||||
it('Should be able to submit a single time (Params method)', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
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()
|
||||
} else {
|
||||
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to submit a single time (JSON method)', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes", {
|
||||
json: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcF",
|
||||
segments: [{
|
||||
segment: [0, 10],
|
||||
category: "sponsor"
|
||||
}]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
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()
|
||||
} else {
|
||||
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to submit multiple times (JSON method)', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes", {
|
||||
json: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcQ",
|
||||
segments: [{
|
||||
segment: [3, 10],
|
||||
category: "sponsor"
|
||||
}, {
|
||||
segment: [30, 60],
|
||||
category: "intro"
|
||||
}]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (success) done();
|
||||
else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(row));
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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=9&endTime=10&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(true);
|
||||
if (res.statusCode === 400) done();
|
||||
else done(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return 400 for missing params (JSON method) 1', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes", {
|
||||
json: {
|
||||
userID: "test",
|
||||
segments: [{
|
||||
segment: [9, 10],
|
||||
category: "sponsor"
|
||||
}, {
|
||||
segment: [31, 60],
|
||||
category: "intro"
|
||||
}]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
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: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcQ"
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
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: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcQ",
|
||||
segments: [{
|
||||
segment: [0],
|
||||
category: "sponsor"
|
||||
}, {
|
||||
segment: [31, 60],
|
||||
category: "intro"
|
||||
}]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
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: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcQ",
|
||||
segments: [{
|
||||
segment: [9, 10]
|
||||
}, {
|
||||
segment: [31, 60],
|
||||
category: "intro"
|
||||
}]
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
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: {
|
||||
userID: "test",
|
||||
videoID: "dQw4w9WgXcQ"
|
||||
}
|
||||
},
|
||||
(err, res, body) => {
|
||||
if (err) done(true);
|
||||
else if (res.statusCode === 400) done();
|
||||
else done(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
var assert = require('assert');
|
||||
var request = require('request');
|
||||
|
||||
var utils = require('../utils.js');
|
||||
|
||||
describe('postVideoSponsorTime', () => {
|
||||
it('Should be able to create a time', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?videoID=fWvKvOViM3g&startTime=1&endTime=10&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode === 200) done();
|
||||
else done("non 200 status code: " + res.statusCode + " ("+body+")");
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return 400 for missing params', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode === 400) done(); // pass
|
||||
else done("non 400 status code: " + res.statusCode + " ("+body+")");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Should be rejected if over 80% of the video', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=1&endTime=1000000&userID=test", 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=1&endTime=1000000&userID=test", 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+")");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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" (
|
||||
|
|
|
@ -4,12 +4,10 @@ var app = express();
|
|||
var config = require('../src/config.js');
|
||||
|
||||
app.post('/ReportChannelWebhook', (req, res) => {
|
||||
console.log("report mock hit");
|
||||
res.status(200);
|
||||
});
|
||||
|
||||
app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
|
||||
console.log("first time submisson mock hit");
|
||||
res.status(200);
|
||||
});
|
||||
|
||||
|
|
|
@ -26,6 +26,14 @@ const YouTubeAPI = {
|
|||
{
|
||||
contentDetails: {
|
||||
duration: "PT1H23M30S"
|
||||
},
|
||||
snippet: {
|
||||
title: "Example Title",
|
||||
thumbnails: {
|
||||
maxres: {
|
||||
url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue