mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-06 11:36:58 +03:00
Merge pull request #67 from ajayyy/testing
Merge the refactor into master
This commit is contained in:
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
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -89,8 +89,14 @@ typings/
|
||||
|
||||
# Databases
|
||||
databases/sponsorTimes.db
|
||||
databases/sponsorTimes.db-shm
|
||||
databases/sponsorTimes.db-wal
|
||||
databases/private.db
|
||||
databases/sponsorTimesReal.db
|
||||
test/databases/sponsorTimes.db
|
||||
test/databases/sponsorTimes.db-shm
|
||||
test/databases/sponsorTimes.db-wal
|
||||
test/databases/private.db
|
||||
|
||||
# Config files
|
||||
config.json
|
||||
@@ -20,12 +20,18 @@ The client web browser extension is available here: https://github.com/ajayyy/Sp
|
||||
|
||||
This is a node.js server, so clone this repo and run `npm install` to install all dependencies.
|
||||
|
||||
Make sure to create the databases in the `databases` folder out of the database schemas.
|
||||
Make sure to put the database files in the `./databases` folder if you want to use a pre-existing database. Otherwise, a fresh database will be created.
|
||||
|
||||
Rename `config.json.example` to `config.json` and fill the parameters inside. Make sure to remove the comments as comments are not supported in JSON.
|
||||
|
||||
Ensure all the tests pass with `npm test`
|
||||
|
||||
Run the server with `npm start`.
|
||||
|
||||
# Developing
|
||||
|
||||
If you want to make changes, run `npm run dev` to automatically reload the server and run tests whenever a file is saved.
|
||||
|
||||
# API Docs
|
||||
|
||||
Available [here](https://github.com/ajayyy/SponsorBlock/wiki/API-Docs)
|
||||
|
||||
@@ -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" (
|
||||
|
||||
2166
package-lock.json
generated
2166
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,9 @@
|
||||
"description": "Server that holds the SponsorBlock database",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "node test.js",
|
||||
"dev": "nodemon -x \"(npm test || echo test failed) && npm start\"",
|
||||
"dev:bash": "nodemon -x 'npm test ; npm start'",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"author": "Ajay Ramachandran",
|
||||
@@ -13,7 +15,12 @@
|
||||
"better-sqlite3": "^5.4.3",
|
||||
"express": "^4.17.1",
|
||||
"http": "0.0.0",
|
||||
"iso8601-duration": "^1.2.0",
|
||||
"uuid": "^3.3.2",
|
||||
"youtube-api": "^2.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^7.1.1",
|
||||
"nodemon": "^2.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
94
src/app.js
Normal file
94
src/app.js
Normal file
@@ -0,0 +1,94 @@
|
||||
var express = require('express');
|
||||
// Create a service (the app object is just a callback).
|
||||
var app = express();
|
||||
var config = require('./config.js');
|
||||
|
||||
// Middleware
|
||||
var corsMiddleware = require('./middleware/cors.js');
|
||||
var loggerMiddleware = require('./middleware/logger.js');
|
||||
|
||||
// Routes
|
||||
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');
|
||||
var getUsername = require('./routes/getUsername.js');
|
||||
var shadowBanUser = require('./routes/shadowBanUser.js');
|
||||
var addUserAsVIP = require('./routes/addUserAsVIP.js');
|
||||
var getSavedTimeForUser = require('./routes/getSavedTimeForUser.js');
|
||||
var getViewsForUser = require('./routes/getViewsForUser.js');
|
||||
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', oldGetVideoSponsorTimes);
|
||||
|
||||
//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);
|
||||
app.post('/api/voteOnSponsorTime', voteOnSponsorTime);
|
||||
|
||||
//Endpoint when a sponsorTime is used up
|
||||
app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
|
||||
app.post('/api/viewedVideoSponsorTime', viewedVideoSponsorTime);
|
||||
|
||||
//To set your username for the stats view
|
||||
app.post('/api/setUsername', setUsername);
|
||||
|
||||
//get what username this user has
|
||||
app.get('/api/getUsername', getUsername);
|
||||
|
||||
//Endpoint used to hide a certain user's data
|
||||
app.post('/api/shadowBanUser', shadowBanUser);
|
||||
|
||||
//Endpoint used to make a user a VIP user with special privileges
|
||||
app.post('/api/addUserAsVIP', addUserAsVIP);
|
||||
|
||||
//Gets all the views added up for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
app.get('/api/getViewsForUser', getViewsForUser);
|
||||
|
||||
//Gets all the saved time added up (views * sponsor length) for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
//In minutes
|
||||
app.get('/api/getSavedTimeForUser', getSavedTimeForUser);
|
||||
|
||||
app.get('/api/getTopUsers', getTopUsers);
|
||||
|
||||
//send out totals
|
||||
//send the total submissions, total views and total minutes saved
|
||||
app.get('/api/getTotalStats', getTotalStats);
|
||||
|
||||
//send out a formatted time saved total
|
||||
app.get('/api/getdayssavedformatted', getDaysSavedFormatted);
|
||||
|
||||
app.get('/database.db', function (req, res) {
|
||||
res.sendfile("./databases/sponsortimes.db", { root: __dirname });
|
||||
});
|
||||
|
||||
// Create an HTTP service.
|
||||
module.exports = function createServer (callback) {
|
||||
return app.listen(config.port, callback);
|
||||
}
|
||||
12
src/config.js
Normal file
12
src/config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
var fs = require('fs');
|
||||
var config = undefined;
|
||||
|
||||
// Check to see if launched in test mode
|
||||
if (process.env.npm_lifecycle_script === 'node test.js') {
|
||||
config = JSON.parse(fs.readFileSync('test.json'));
|
||||
} else {
|
||||
config = JSON.parse(fs.readFileSync('config.json'));
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
31
src/databases/databases.js
Normal file
31
src/databases/databases.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var config = require('../config.js');
|
||||
var Sqlite3 = require('better-sqlite3');
|
||||
var fs = require('fs');
|
||||
|
||||
let options = {
|
||||
readonly: config.readOnly,
|
||||
fileMustExist: !config.createDatabaseIfNotExist
|
||||
};
|
||||
|
||||
var db = new Sqlite3(config.db, options);
|
||||
var privateDB = new Sqlite3(config.privateDB, options);
|
||||
|
||||
if (config.createDatabaseIfNotExist && !config.readOnly) {
|
||||
if (fs.existsSync(config.dbSchema)) db.exec(fs.readFileSync(config.dbSchema).toString());
|
||||
if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString());
|
||||
}
|
||||
|
||||
// Enable WAL mode checkpoint number
|
||||
if (!config.readOnly && config.mode === "production") {
|
||||
db.exec("PRAGMA journal_mode=WAL;");
|
||||
db.exec("PRAGMA wal_autocheckpoint=1;");
|
||||
}
|
||||
|
||||
// Enable Memory-Mapped IO
|
||||
db.exec("pragma mmap_size= 500000000;");
|
||||
privateDB.exec("pragma mmap_size= 500000000;");
|
||||
|
||||
module.exports = {
|
||||
db: db,
|
||||
privateDB: privateDB
|
||||
};
|
||||
5
src/middleware/cors.js
Normal file
5
src/middleware/cors.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = function corsMiddleware(req, res, next) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
next();
|
||||
}
|
||||
7
src/middleware/logger.js
Normal file
7
src/middleware/logger.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var fs = require('fs');
|
||||
var config = require('../config.js');
|
||||
|
||||
module.exports = function logger (req, res, next) {
|
||||
(config.mode === "development") && console.log('Request recieved: ' + req.url);
|
||||
next();
|
||||
}
|
||||
46
src/routes/addUserAsVIP.js
Normal file
46
src/routes/addUserAsVIP.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var fs = require('fs');
|
||||
var config = require('../config.js');
|
||||
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
|
||||
module.exports = async function addUserAsVIP (req, res) {
|
||||
let userID = req.query.userID;
|
||||
let adminUserIDInput = req.query.adminUserID;
|
||||
|
||||
let enabled = req.query.enabled;
|
||||
if (enabled === undefined){
|
||||
enabled = true;
|
||||
} else {
|
||||
enabled = enabled === "true";
|
||||
}
|
||||
|
||||
if (userID == undefined || adminUserIDInput == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput !== adminUserID) {
|
||||
//not authorized
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if this user is already a vip
|
||||
let row = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID);
|
||||
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the vip list
|
||||
db.prepare("INSERT INTO vipUsers VALUES(?)").run(userID);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
db.prepare("DELETE FROM vipUsers WHERE userID = ?").run(userID);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
12
src/routes/getDaysSavedFormatted.js
Normal file
12
src/routes/getDaysSavedFormatted.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var db = require('../databases/databases.js');
|
||||
|
||||
module.exports = function getDaysSavedFormatted (req, res) {
|
||||
let row = db.prepare("select sum((endtime - starttime) / 60 / 60 / 24 * views) as dayssaved from sponsortimes where shadowhidden != 1").get();
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
res.send({
|
||||
dayssaved: row.dayssaved.tofixed(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
32
src/routes/getSavedTimeForUser.js
Normal file
32
src/routes/getSavedTimeForUser.js
Normal file
@@ -0,0 +1,32 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
module.exports = function getSavedTimeForUser (req, res) {
|
||||
let userID = req.query.userID;
|
||||
|
||||
if (userID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ").get(userID);
|
||||
|
||||
if (row.minutesSaved != null) {
|
||||
res.send({
|
||||
timeSaved: row.minutesSaved
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
304
src/routes/getSkipSegments.js
Normal file
304
src/routes/getSkipSegments.js
Normal file
@@ -0,0 +1,304 @@
|
||||
var fs = require('fs');
|
||||
var config = require('../config.js');
|
||||
|
||||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
var privateDB = databases.privateDB;
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
|
||||
|
||||
//gets the getWeightedRandomChoice for each group in an array of groups
|
||||
function getWeightedRandomChoiceForArray(choiceGroups, weights) {
|
||||
let finalChoices = [];
|
||||
//the indexes either chosen to be added to final indexes or chosen not to be added
|
||||
let choicesDealtWith = [];
|
||||
//for each choice group, what are the sums of the weights
|
||||
let weightSums = [];
|
||||
|
||||
for (let i = 0; i < choiceGroups.length; i++) {
|
||||
//find weight sums for this group
|
||||
weightSums.push(0);
|
||||
for (let j = 0; j < choiceGroups[i].length; j++) {
|
||||
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||
if (weights[choiceGroups[i][j]] > 0) {
|
||||
weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]];
|
||||
}
|
||||
}
|
||||
|
||||
//create a random choice for this group
|
||||
let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1)
|
||||
finalChoices.push(randomChoice.finalChoices);
|
||||
|
||||
for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) {
|
||||
choicesDealtWith.push(randomChoice.choicesDealtWith[j])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith,
|
||||
weightSums: weightSums
|
||||
};
|
||||
}
|
||||
|
||||
//gets a weighted random choice from the indexes array based on the weights.
|
||||
//amountOfChoices speicifies the amount of choices to return, 1 or more.
|
||||
//choices are unique
|
||||
function getWeightedRandomChoice(choices, weights, amountOfChoices) {
|
||||
if (amountOfChoices > choices.length) {
|
||||
//not possible, since all choices must be unique
|
||||
return null;
|
||||
}
|
||||
|
||||
let finalChoices = [];
|
||||
let choicesDealtWith = [];
|
||||
|
||||
let sqrtWeightsList = [];
|
||||
//the total of all the weights run through the cutom sqrt function
|
||||
let totalSqrtWeights = 0;
|
||||
for (let j = 0; j < choices.length; j++) {
|
||||
//multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3)
|
||||
//The 3 makes -2 the minimum votes before being ignored completely
|
||||
//https://www.desmos.com/calculator/ljftxolg9j
|
||||
//this can be changed if this system increases in popularity.
|
||||
let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10);
|
||||
sqrtWeightsList.push(sqrtVote)
|
||||
totalSqrtWeights += sqrtVote;
|
||||
|
||||
//this index has now been deat with
|
||||
choicesDealtWith.push(choices[j]);
|
||||
}
|
||||
|
||||
//iterate and find amountOfChoices choices
|
||||
let randomNumber = Math.random();
|
||||
|
||||
//this array will keep adding to this variable each time one sqrt vote has been dealt with
|
||||
//this is the sum of all the sqrtVotes under this index
|
||||
let currentVoteNumber = 0;
|
||||
for (let j = 0; j < sqrtWeightsList.length; j++) {
|
||||
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) {
|
||||
//this one was randomly generated
|
||||
finalChoices.push(choices[j]);
|
||||
//remove that from original array, for next recursion pass if it happens
|
||||
choices.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
//add on to the count
|
||||
currentVoteNumber += sqrtWeightsList[j];
|
||||
}
|
||||
|
||||
//add on the other choices as well using recursion
|
||||
if (amountOfChoices > 1) {
|
||||
let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices;
|
||||
//add all these choices to the finalChoices array being returned
|
||||
for (let i = 0; i < otherChoices.length; i++) {
|
||||
finalChoices.push(otherChoices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//This function will find sponsor times that are contained inside of eachother, called similar sponsor times
|
||||
//Only one similar time will be returned, randomly generated based on the sqrt of votes.
|
||||
//This allows new less voted items to still sometimes appear to give them a chance at getting votes.
|
||||
//Sponsor times with less than -1 votes are already ignored before this function is called
|
||||
function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) {
|
||||
//list of sponsors that are contained inside eachother
|
||||
let similarSponsors = [];
|
||||
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
//see if the start time is located between the start and end time of the other sponsor time.
|
||||
for (let j = i + 1; j < sponsorTimes.length; j++) {
|
||||
if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) {
|
||||
//sponsor j is contained in sponsor i
|
||||
similarSponsors.push([i, j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let similarSponsorsGroups = [];
|
||||
//once they have been added to a group, they don't need to be dealt with anymore
|
||||
let dealtWithSimilarSponsors = [];
|
||||
|
||||
//create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3)
|
||||
for (let i = 0; i < similarSponsors.length; i++) {
|
||||
if (dealtWithSimilarSponsors.includes(i)) {
|
||||
//dealt with already
|
||||
continue;
|
||||
}
|
||||
|
||||
//this is the group of indexes that are similar
|
||||
let group = similarSponsors[i];
|
||||
for (let j = 0; j < similarSponsors.length; j++) {
|
||||
if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) {
|
||||
//this is a similar group
|
||||
group.push(similarSponsors[j][0]);
|
||||
group.push(similarSponsors[j][1]);
|
||||
dealtWithSimilarSponsors.push(j);
|
||||
}
|
||||
}
|
||||
similarSponsorsGroups.push(group);
|
||||
}
|
||||
|
||||
//remove duplicate indexes in group arrays
|
||||
for (let i = 0; i < similarSponsorsGroups.length; i++) {
|
||||
uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) {
|
||||
return self.indexOf(item) == pos;
|
||||
});
|
||||
|
||||
similarSponsorsGroups[i] = uniqueArray;
|
||||
}
|
||||
|
||||
let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes);
|
||||
|
||||
let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices;
|
||||
//the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added
|
||||
let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith;
|
||||
|
||||
let voteSums = weightedRandomIndexes.weightSums;
|
||||
//convert these into the votes
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
//it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor.
|
||||
votes[finalSponsorTimeIndexes[i]] = voteSums[i];
|
||||
}
|
||||
|
||||
//find the indexes never dealt with and add them
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (!finalSponsorTimeIndexesDealtWith.includes(i)) {
|
||||
finalSponsorTimeIndexes.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
//if there are too many indexes, find the best 4
|
||||
if (finalSponsorTimeIndexes.length > 8) {
|
||||
finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices;
|
||||
}
|
||||
|
||||
//convert this to a final array to return
|
||||
let finalSponsorTimes = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
//convert this to a final array of UUIDs as well
|
||||
let finalUUIDs = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
return {
|
||||
sponsorTimes: finalSponsorTimes,
|
||||
UUIDs: finalUUIDs
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 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.query.videoID;
|
||||
// Default to sponsor
|
||||
// If using params instead of JSON, only one category can be pulled
|
||||
const categories = req.query.categories ? JSON.parse(req.query.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);
|
||||
}
|
||||
|
||||
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 = {
|
||||
handleGetSegments,
|
||||
endpoint: function (req, res) {
|
||||
let segments = handleGetSegments(req, res);
|
||||
|
||||
if (segments) {
|
||||
//send result
|
||||
res.send(segments)
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/routes/getTopUsers.js
Normal file
51
src/routes/getTopUsers.js
Normal file
@@ -0,0 +1,51 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
|
||||
module.exports = function getTopUsers (req, res) {
|
||||
let sortType = req.query.sortType;
|
||||
|
||||
if (sortType == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//setup which sort type to use
|
||||
let sortBy = "";
|
||||
if (sortType == 0) {
|
||||
sortBy = "minutesSaved";
|
||||
} else if (sortType == 1) {
|
||||
sortBy = "viewCount";
|
||||
} else if (sortType == 2) {
|
||||
sortBy = "totalSubmissions";
|
||||
} else {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
let userNames = [];
|
||||
let viewCounts = [];
|
||||
let totalSubmissions = [];
|
||||
let minutesSaved = [];
|
||||
|
||||
let rows = db.prepare("SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
|
||||
"SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " +
|
||||
"IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " +
|
||||
"WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 GROUP BY IFNULL(userName, sponsorTimes.userID) ORDER BY " + sortBy + " DESC LIMIT 100").all();
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
userNames[i] = rows[i].userName;
|
||||
|
||||
viewCounts[i] = rows[i].viewCount;
|
||||
totalSubmissions[i] = rows[i].totalSubmissions;
|
||||
minutesSaved[i] = rows[i].minutesSaved;
|
||||
}
|
||||
|
||||
//send this result
|
||||
res.send({
|
||||
userNames: userNames,
|
||||
viewCounts: viewCounts,
|
||||
totalSubmissions: totalSubmissions,
|
||||
minutesSaved: minutesSaved
|
||||
});
|
||||
}
|
||||
53
src/routes/getTotalStats.js
Normal file
53
src/routes/getTotalStats.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
var request = require('request');
|
||||
|
||||
// A cache of the number of chrome web store users
|
||||
var chromeUsersCache = null;
|
||||
var firefoxUsersCache = null;
|
||||
var lastUserCountCheck = 0;
|
||||
|
||||
|
||||
module.exports = function getTotalStats (req, res) {
|
||||
let row = db.prepare("SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " +
|
||||
"SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1").get();
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
res.send({
|
||||
userCount: row.userCount,
|
||||
activeUsers: chromeUsersCache + firefoxUsersCache,
|
||||
viewCount: row.viewCount,
|
||||
totalSubmissions: row.totalSubmissions,
|
||||
minutesSaved: row.minutesSaved
|
||||
});
|
||||
|
||||
// Check if the cache should be updated (every ~14 hours)
|
||||
let now = Date.now();
|
||||
if (now - lastUserCountCheck > 5000000) {
|
||||
lastUserCountCheck = now;
|
||||
|
||||
// Get total users
|
||||
request.get("https://addons.mozilla.org/api/v3/addons/addon/sponsorblock/", function (err, firefoxResponse, body) {
|
||||
try {
|
||||
firefoxUsersCache = parseInt(JSON.parse(body).average_daily_users);
|
||||
|
||||
request.get("https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone", function(err, chromeResponse, body) {
|
||||
if (body !== undefined) {
|
||||
try {
|
||||
chromeUsersCache = parseInt(body.match(/(?<=\<span class=\"e-f-ih\" title=\").*?(?= users\">)/)[0].replace(",", ""));
|
||||
} catch (error) {
|
||||
// Re-check later
|
||||
lastUserCountCheck = 0;
|
||||
}
|
||||
} else {
|
||||
lastUserCountCheck = 0;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Re-check later
|
||||
lastUserCountCheck = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/routes/getUsername.js
Normal file
36
src/routes/getUsername.js
Normal file
@@ -0,0 +1,36 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
module.exports = function getUsername (req, res) {
|
||||
let userID = req.query.userID;
|
||||
|
||||
if (userID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT userName FROM userNames WHERE userID = ?").get(userID);
|
||||
|
||||
if (row !== undefined) {
|
||||
res.send({
|
||||
userName: row.userName
|
||||
});
|
||||
} else {
|
||||
//no username yet, just send back the userID
|
||||
res.send({
|
||||
userName: userID
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
33
src/routes/getViewsForUser.js
Normal file
33
src/routes/getViewsForUser.js
Normal file
@@ -0,0 +1,33 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
module.exports = function getViewsForUser(req, res) {
|
||||
let userID = req.query.userID;
|
||||
|
||||
if (userID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
|
||||
//increase the view count by one
|
||||
if (row.viewCount != null) {
|
||||
res.send({
|
||||
viewCount: row.viewCount
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
53
src/routes/setUsername.js
Normal file
53
src/routes/setUsername.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
var config = require('../config.js');
|
||||
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
|
||||
module.exports = function setUsername(req, res) {
|
||||
let userID = req.query.userID;
|
||||
let userName = req.query.username;
|
||||
|
||||
let adminUserIDInput = req.query.adminUserID;
|
||||
|
||||
if (userID == undefined || userName == undefined || userID === "undefined") {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (adminUserIDInput != undefined) {
|
||||
//this is the admin controlling the other users account, don't hash the controling account's ID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput != config.adminUserID) {
|
||||
//they aren't the admin
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
//hash the userID
|
||||
userID = getHash(userID);
|
||||
}
|
||||
|
||||
try {
|
||||
//check if username is already set
|
||||
let row = db.prepare("SELECT count(*) as count FROM userNames WHERE userID = ?").get(userID);
|
||||
|
||||
if (row.count > 0) {
|
||||
//already exists, update this row
|
||||
db.prepare("UPDATE userNames SET userName = ? WHERE userID = ?").run(userName, userID);
|
||||
} else {
|
||||
//add to the db
|
||||
db.prepare("INSERT INTO userNames VALUES(?, ?)").run(userID, userName);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
65
src/routes/shadowBanUser.js
Normal file
65
src/routes/shadowBanUser.js
Normal file
@@ -0,0 +1,65 @@
|
||||
var config = require('../config.js');
|
||||
|
||||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
var privateDB = databases.privateDB;
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
module.exports = async function shadowBanUser(req, res) {
|
||||
let userID = req.query.userID;
|
||||
let adminUserIDInput = req.query.adminUserID;
|
||||
|
||||
let enabled = req.query.enabled;
|
||||
if (enabled === undefined){
|
||||
enabled = true;
|
||||
} else {
|
||||
enabled = enabled === "true";
|
||||
}
|
||||
|
||||
//if enabled is false and the old submissions should be made visible again
|
||||
let unHideOldSubmissions = req.query.unHideOldSubmissions;
|
||||
if (enabled === undefined){
|
||||
unHideOldSubmissions = true;
|
||||
} else {
|
||||
unHideOldSubmissions = unHideOldSubmissions === "true";
|
||||
}
|
||||
|
||||
if (adminUserIDInput == undefined || userID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput !== config.adminUserID) {
|
||||
//not authorized
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if this user is already shadowbanned
|
||||
let row = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the shadow ban list
|
||||
|
||||
//add it to the table
|
||||
privateDB.prepare("INSERT INTO shadowBannedUsers VALUES(?)").run(userID);
|
||||
|
||||
//find all previous submissions and hide them
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?").run(userID);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
privateDB.prepare("DELETE FROM shadowBannedUsers WHERE userID = ?").run(userID);
|
||||
|
||||
//find all previous submissions and unhide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?").run(userID);
|
||||
}
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
16
src/routes/viewedVideoSponsorTime.js
Normal file
16
src/routes/viewedVideoSponsorTime.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
|
||||
module.exports = function viewedVideoSponsorTime(req, res) {
|
||||
let UUID = req.query.UUID;
|
||||
|
||||
if (UUID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//up the view count by one
|
||||
db.prepare("UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?").run(UUID);
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
171
src/routes/voteOnSponsorTime.js
Normal file
171
src/routes/voteOnSponsorTime.js
Normal file
@@ -0,0 +1,171 @@
|
||||
var fs = require('fs');
|
||||
var config = require('../config.js');
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.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');
|
||||
|
||||
module.exports = async function voteOnSponsorTime(req, res) {
|
||||
let UUID = req.query.UUID;
|
||||
let userID = req.query.userID;
|
||||
let type = req.query.type;
|
||||
|
||||
if (UUID == undefined || userID == undefined || type == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
let nonAnonUserID = getHash(userID);
|
||||
userID = getHash(userID + UUID);
|
||||
|
||||
//x-forwarded-for if this server is behind a proxy
|
||||
let ip = getIP(req);
|
||||
|
||||
//hash the ip 5000 times so no one can get it from the database
|
||||
let hashedIP = getHash(ip + config.globalSalt);
|
||||
|
||||
try {
|
||||
//check if vote has already happened
|
||||
let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID);
|
||||
|
||||
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future
|
||||
let incrementAmount = 0;
|
||||
let oldIncrementAmount = 0;
|
||||
|
||||
if (type == 1) {
|
||||
//upvote
|
||||
incrementAmount = 1;
|
||||
} else if (type == 0) {
|
||||
//downvote
|
||||
incrementAmount = -1;
|
||||
} else {
|
||||
//unrecongnised type of vote
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
if (votesRow != undefined) {
|
||||
if (votesRow.type == 1) {
|
||||
//upvote
|
||||
oldIncrementAmount = 1;
|
||||
} else if (votesRow.type == 0) {
|
||||
//downvote
|
||||
oldIncrementAmount = -1;
|
||||
} else if (votesRow.type == 2) {
|
||||
//extra downvote
|
||||
oldIncrementAmount = -4;
|
||||
} else if (votesRow.type < 0) {
|
||||
//vip downvote
|
||||
oldIncrementAmount = votesRow.type;
|
||||
}
|
||||
}
|
||||
|
||||
//check if this user is on the vip list
|
||||
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID);
|
||||
|
||||
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
|
||||
let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID);
|
||||
|
||||
if (vipRow.userCount != 0 && incrementAmount < 0) {
|
||||
//this user is a vip and a downvote
|
||||
incrementAmount = - (row.votes + 2 - oldIncrementAmount);
|
||||
type = incrementAmount;
|
||||
} else if (row !== undefined && (row.votes > 8 || row.views > 15) && incrementAmount < 0) {
|
||||
//increase the power of this downvote
|
||||
incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount));
|
||||
type = incrementAmount;
|
||||
}
|
||||
|
||||
// Send discord message
|
||||
if (type != 1) {
|
||||
// Get video ID
|
||||
let submissionInfoRow = db.prepare("SELECT videoID, userID, startTime, endTime FROM sponsorTimes WHERE UUID = ?").get(UUID);
|
||||
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID);
|
||||
|
||||
if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "snippet",
|
||||
id: submissionInfoRow.videoID
|
||||
}, function (err, data) {
|
||||
if (err || data.items.length === 0) {
|
||||
err && console.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
request.post(config.discordReportChannelWebhookURL, {
|
||||
json: {
|
||||
"embeds": [{
|
||||
"title": data.items[0].snippet.title,
|
||||
"url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID +
|
||||
"&t=" + (submissionInfoRow.startTime.toFixed(0) - 2),
|
||||
"description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views +
|
||||
" Views**\n\nSubmission ID: " + UUID +
|
||||
"\n\nSubmitted by: " + submissionInfoRow.userID + "\n\nTimestamp: " +
|
||||
getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime),
|
||||
"color": 10813440,
|
||||
"author": {
|
||||
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "")
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||
}
|
||||
}]
|
||||
}
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
console.log("Failed to send reported submission Discord hook.");
|
||||
console.log(JSON.stringify(err));
|
||||
console.log("\n");
|
||||
} else if (res && res.statusCode >= 400) {
|
||||
console.log("Error sending reported submission Discord hook");
|
||||
console.log(JSON.stringify(res));
|
||||
console.log("\n");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//update the votes table
|
||||
if (votesRow != undefined) {
|
||||
privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
|
||||
} else {
|
||||
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type);
|
||||
}
|
||||
|
||||
//update the vote count on this sponsorTime
|
||||
//oldIncrementAmount will be zero is row is null
|
||||
db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID);
|
||||
|
||||
//for each positive vote, see if a hidden submission can be shown again
|
||||
if (incrementAmount > 0) {
|
||||
//find the UUID that submitted the submission that was voted on
|
||||
let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID;
|
||||
|
||||
//check if any submissions are hidden
|
||||
let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID);
|
||||
|
||||
if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
|
||||
//see if some of this users submissions should be visible again
|
||||
|
||||
if (await isUserTrustworthy(submissionUserID)) {
|
||||
//they are trustworthy again, show 2 of their submissions again, if there are two to show
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//added to db
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
13
src/utils/getFormattedTime.js
Normal file
13
src/utils/getFormattedTime.js
Normal file
@@ -0,0 +1,13 @@
|
||||
//converts time in seconds to minutes:seconds
|
||||
module.exports = function getFormattedTime(seconds) {
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
let secondsDisplay = Math.round(seconds - minutes * 60);
|
||||
if (secondsDisplay < 10) {
|
||||
//add a zero
|
||||
secondsDisplay = "0" + secondsDisplay;
|
||||
}
|
||||
|
||||
let formatted = minutes+ ":" + secondsDisplay;
|
||||
|
||||
return formatted;
|
||||
}
|
||||
12
src/utils/getHash.js
Normal file
12
src/utils/getHash.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
module.exports = function (value, times=5000) {
|
||||
if (times <= 0) return "";
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
let hashCreator = crypto.createHash('sha256');
|
||||
value = hashCreator.update(value).digest('hex');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
6
src/utils/getIP.js
Normal file
6
src/utils/getIP.js
Normal file
@@ -0,0 +1,6 @@
|
||||
var fs = require('fs');
|
||||
var config = require('../config.js');
|
||||
|
||||
module.exports = function getIP(req) {
|
||||
return config.behindProxy ? req.headers['x-forwarded-for'] : req.connection.remoteAddress;
|
||||
}
|
||||
19
src/utils/youtubeAPI.js
Normal file
19
src/utils/youtubeAPI.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var config = require('../config.js');
|
||||
|
||||
// YouTube API
|
||||
const YouTubeAPI = require("youtube-api");
|
||||
|
||||
var exportObject;
|
||||
// If in test mode, return a mocked youtube object
|
||||
// otherwise return an authenticated youtube api
|
||||
if (config.mode === "test") {
|
||||
exportObject = require("../../test/youtubeMock.js");
|
||||
} else {
|
||||
YouTubeAPI.authenticate({
|
||||
type: "key",
|
||||
key: config.youtubeAPIKey
|
||||
});
|
||||
exportObject = YouTubeAPI;
|
||||
}
|
||||
|
||||
module.exports = exportObject;
|
||||
40
test.js
Normal file
40
test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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');
|
||||
|
||||
// Instantiate a Mocha instance.
|
||||
var mocha = new Mocha();
|
||||
|
||||
var testDir = './test/cases'
|
||||
|
||||
// Add each .js file to the mocha instance
|
||||
fs.readdirSync(testDir).filter(function(file) {
|
||||
// Only keep the .js files
|
||||
return file.substr(-3) === '.js';
|
||||
|
||||
}).forEach(function(file) {
|
||||
mocha.addFile(
|
||||
path.join(testDir, file)
|
||||
);
|
||||
});
|
||||
|
||||
var mockServer = createMockServer(() => {
|
||||
console.log("Started mock HTTP Server");
|
||||
var server = createServer(() => {
|
||||
console.log("Started main HTTP server");
|
||||
// Run the tests.
|
||||
mocha.run(function(failures) {
|
||||
mockServer.close();
|
||||
server.close();
|
||||
process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures
|
||||
});
|
||||
});
|
||||
});
|
||||
17
test.json
Normal file
17
test.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"port": 8080,
|
||||
"mockPort": 8081,
|
||||
"globalSalt": "testSalt",
|
||||
"adminUserID": "testUserId",
|
||||
"youtubeAPIKey": "",
|
||||
"discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook",
|
||||
"discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook",
|
||||
"behindProxy": true,
|
||||
"db": "./test/databases/sponsorTimes.db",
|
||||
"privateDB": "./test/databases/private.db",
|
||||
"createDatabaseIfNotExist": true,
|
||||
"dbSchema": "./test/databases/_sponsorTimes.db.sql",
|
||||
"privateDBSchema": "./test/databases/_private.db.sql",
|
||||
"mode": "test",
|
||||
"readOnly": false
|
||||
}
|
||||
29
test/cases/getHash.js
Normal file
29
test/cases/getHash.js
Normal file
@@ -0,0 +1,29 @@
|
||||
var getHash = require('../../src/utils/getHash.js');
|
||||
|
||||
var assert = require('assert');
|
||||
describe('getHash', () => {
|
||||
it('Should not output the input string', () => {
|
||||
assert(getHash("test") !== "test");
|
||||
assert(getHash("test", -1) !== "test");
|
||||
assert(getHash("test", 0) !== "test");
|
||||
assert(getHash("test", null) !== "test");
|
||||
});
|
||||
|
||||
it('Should return a hashed value', () => {
|
||||
assert.equal(getHash("test"), "2f327ef967ade1ebf4319163f7debbda9cc17bb0c8c834b00b30ca1cf1c256ee");
|
||||
});
|
||||
|
||||
it ('Should take a variable number of passes', () => {
|
||||
assert.equal(getHash("test", 1), "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
|
||||
assert.equal(getHash("test", 2), "7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c");
|
||||
assert.equal(getHash("test", 3), "5b24f7aa99f1e1da5698a4f91ae0f4b45651a1b625c61ed669dd25ff5b937972");
|
||||
});
|
||||
|
||||
it ('Should default to 5000 passes', () => {
|
||||
assert.equal(getHash("test"), getHash("test", 5000));
|
||||
});
|
||||
|
||||
it ('Should not take a negative number of passes', () => {
|
||||
assert.equal(getHash("test", -1), "");
|
||||
});
|
||||
});
|
||||
20
test/cases/getSavedTimeForUser.js
Normal file
20
test/cases/getSavedTimeForUser.js
Normal file
@@ -0,0 +1,20 @@
|
||||
var request = require('request');
|
||||
var utils = require('../utils.js');
|
||||
var db = require('../../src/databases/databases.js').db;
|
||||
var getHash = require('../../src/utils/getHash.js');
|
||||
|
||||
describe('getSavedTimeForUser', () => {
|
||||
before(() => {
|
||||
db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)");
|
||||
});
|
||||
|
||||
it('Should be able to get a 200', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getSavedTimeForUser?userID=testman", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
||||
else done(); // pass
|
||||
});
|
||||
});
|
||||
});
|
||||
228
test/cases/getSkipSegments.js
Normal file
228
test/cases/getSkipSegments.js
Normal file
@@ -0,0 +1,228 @@
|
||||
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 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 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 categories array', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&categories=[\"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 categories array 2', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&categories=[\"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 multiple times by category', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=multiple&categories=[\"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 === 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: " + res.body);
|
||||
} else {
|
||||
done("Received incorrect body: " + res.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get multiple times by multiple categories', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=testtesttest&categories=[\"sponsor\", \"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 === 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: " + res.body);
|
||||
} else {
|
||||
done("Received incorrect body: " + 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
86
test/cases/oldGetSponsorTime.js
Normal file
86
test/cases/oldGetSponsorTime.js
Normal file
@@ -0,0 +1,86 @@
|
||||
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('getVideoSponsorTime (Old get method)', () => {
|
||||
before(() => {
|
||||
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=old-testtesttest", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
||||
else done(); // pass
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return 404 if no segment found', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getVideoSponsorTimes?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 possible to send unexpected query parameters', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/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");
|
||||
else done(); // pass
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able send a comma in a query param', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/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);
|
||||
else if (JSON.parse(body).UUIDs[0] === 'uuid-1') done(); // pass
|
||||
else done("couldn't parse response");
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get the correct time', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/getVideoSponsorTimes?videoID=old-testtesttest", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("couldn't call endpoint");
|
||||
else if (res.statusCode !== 200) done("non 200");
|
||||
else {
|
||||
let parsedBody = JSON.parse(body);
|
||||
if (parsedBody.sponsorTimes[0][0] === 1
|
||||
&& parsedBody.sponsorTimes[0][1] === 11
|
||||
&& parsedBody.UUIDs[0] === 'uuid-0') {
|
||||
done(); // pass
|
||||
} else {
|
||||
done("Wrong data was returned + " + parsedBody);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
18
test/databases/_private.db.sql
Normal file
18
test/databases/_private.db.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE IF NOT EXISTS "shadowBannedUsers" (
|
||||
"userID" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "votes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" INTEGER NOT NULL,
|
||||
"hashedIP" INTEGER NOT NULL,
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_hashedIP on sponsorTimes(hashedIP);
|
||||
CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID);
|
||||
COMMIT;
|
||||
23
test/databases/_sponsorTimes.db.sql
Normal file
23
test/databases/_sponsorTimes.db.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE IF NOT EXISTS "vipUsers" (
|
||||
"userID" TEXT NOT NULL
|
||||
);
|
||||
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,
|
||||
"category" TEXT NOT NULL,
|
||||
"shadowHidden" INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "userNames" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_videoID on sponsorTimes(videoID);
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID);
|
||||
COMMIT;
|
||||
16
test/mocks.js
Normal file
16
test/mocks.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var config = require('../src/config.js');
|
||||
|
||||
app.post('/ReportChannelWebhook', (req, res) => {
|
||||
res.status(200);
|
||||
});
|
||||
|
||||
app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
|
||||
res.status(200);
|
||||
});
|
||||
|
||||
module.exports = function createMockServer(callback) {
|
||||
return app.listen(config.mockPort, callback);
|
||||
}
|
||||
7
test/utils.js
Normal file
7
test/utils.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var config = require('../src/config.js');
|
||||
|
||||
module.exports = {
|
||||
getbaseURL: () => {
|
||||
return "http://localhost:" + config.port;
|
||||
}
|
||||
};
|
||||
46
test/youtubeMock.js
Normal file
46
test/youtubeMock.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
YouTubeAPI.videos.list({
|
||||
part: "snippet",
|
||||
id: videoID
|
||||
}, function (err, data) {});
|
||||
*/
|
||||
|
||||
// https://developers.google.com/youtube/v3/docs/videos
|
||||
|
||||
const YouTubeAPI = {
|
||||
videos: {
|
||||
list: (obj, callback) => {
|
||||
if (obj.videoID === "knownWrongID") {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
totalResults: 0
|
||||
},
|
||||
items: []
|
||||
});
|
||||
} else {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
totalResults: 1
|
||||
},
|
||||
items: [
|
||||
{
|
||||
contentDetails: {
|
||||
duration: "PT1H23M30S"
|
||||
},
|
||||
snippet: {
|
||||
title: "Example Title",
|
||||
thumbnails: {
|
||||
maxres: {
|
||||
url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = YouTubeAPI;
|
||||
Reference in New Issue
Block a user