mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 01:02:30 +01:00
commit
220f562242
36 changed files with 939 additions and 548 deletions
|
@ -7,10 +7,12 @@
|
|||
"youtubeAPIKey": null, //get this from Google Cloud Platform [optional]
|
||||
"discordReportChannelWebhookURL": null, //URL from discord if you would like notifications when someone makes a report [optional]
|
||||
"discordFirstTimeSubmissionsWebhookURL": null, //URL from discord if you would like notifications when someone makes a first time submission [optional]
|
||||
"behindProxy": true,
|
||||
"discordCompletelyIncorrectReportWebhookURL": null, //URL from discord if you would like notifications when someone reports a submission as completely incorrect [optional]
|
||||
"behindProxy": "X-Forwarded-For", //Options: "X-Forwarded-For", "Cloudflare", "X-Real-IP", anything else will mean it is not behind a proxy. True defaults to "X-Forwarded-For"
|
||||
"db": "./databases/sponsorTimes.db",
|
||||
"privateDB": "./databases/private.db",
|
||||
"createDatabaseIfNotExist": true, //This will run on startup every time (unless readOnly is true) - so ensure "create table if not exists" is used in the schema
|
||||
"schemaFolder": "./databases",
|
||||
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||
"privateDBSchema": "./databases/_private.db.sql",
|
||||
"mode": "development",
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
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,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "categoryVotes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "config" (
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_hashedIP on sponsorTimes(hashedIP);
|
||||
CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID);
|
||||
|
||||
COMMIT;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "vipUsers" (
|
||||
"userID" TEXT NOT NULL
|
||||
);
|
||||
|
@ -18,6 +19,18 @@ CREATE TABLE IF NOT EXISTS "userNames" (
|
|||
"userID" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "categoryVotes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"votes" INTEGER NOT NULL default '0'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "config" (
|
||||
"key" TEXT NOT NULL UNIQUE,
|
||||
"value" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_videoID on sponsorTimes(videoID);
|
||||
CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID);
|
||||
|
||||
COMMIT;
|
25
databases/_upgrade_sponsorTimes_1.sql
Normal file
25
databases/_upgrade_sponsorTimes_1.sql
Normal file
|
@ -0,0 +1,25 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add incorrectVotes field */
|
||||
CREATE TABLE "sqlb_temp_table_1" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT "sponsor",
|
||||
"shadowHidden" INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO sqlb_temp_table_1 SELECT videoID,startTime,endTime,votes,"1",UUID,userID,timeSubmitted,views,category,shadowHidden FROM sponsorTimes;
|
||||
|
||||
DROP TABLE sponsorTimes;
|
||||
ALTER TABLE sqlb_temp_table_1 RENAME TO "sponsorTimes";
|
||||
|
||||
/* Add version to config */
|
||||
INSERT INTO config (key, value) VALUES("version", 1);
|
||||
|
||||
COMMIT;
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -1398,6 +1398,11 @@
|
|||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"iso8601-duration": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz",
|
||||
"integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg=="
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
"express": "^4.17.1",
|
||||
"http": "0.0.0",
|
||||
"iso8601-duration": "^1.2.0",
|
||||
"mysql": "^2.18.1",
|
||||
"sync-mysql": "^3.0.1",
|
||||
"uuid": "^3.3.2",
|
||||
"youtube-api": "^2.0.10"
|
||||
},
|
||||
|
|
27
src/databases/Mysql.js
Normal file
27
src/databases/Mysql.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
var MysqlInterface = require('sync-mysql');
|
||||
|
||||
class Mysql {
|
||||
constructor(config) {
|
||||
this.connection = new MysqlInterface(config);
|
||||
}
|
||||
|
||||
exec(query) {
|
||||
this.prepare('run', query, []);
|
||||
}
|
||||
|
||||
prepare (type, query, params) {
|
||||
console.log("prepare (mysql): type: " + type + ", query: " + query + ", params: " + params);
|
||||
if (type === 'get') {
|
||||
return this.connection.query(query, params)[0];
|
||||
} else if (type === 'run') {
|
||||
this.connection.query(query, params);
|
||||
} else if (type === 'all') {
|
||||
return this.connection.query(query, params);
|
||||
} else {
|
||||
console.log('returning undefined...')
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Mysql;
|
32
src/databases/Sqlite.js
Normal file
32
src/databases/Sqlite.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const { db } = require("./databases");
|
||||
|
||||
class Sqlite {
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
getConnection() {
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
prepare(type, query, params) {
|
||||
//console.log("prepare: type: " + type + ", query: " + query + ", params: " + params);
|
||||
if (type === 'get') {
|
||||
return this.connection.prepare(query).get(...params);
|
||||
} else if (type === 'run') {
|
||||
this.connection.prepare(query).run(...params);
|
||||
} else if (type === 'all') {
|
||||
return this.connection.prepare(query).all(...params);
|
||||
} else {
|
||||
console.log('returning undefined...')
|
||||
console.log("prepare: type: " + type + ", query: " + query + ", params: " + params);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
exec(query) {
|
||||
return this.connection.exec(query);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sqlite;
|
|
@ -1,12 +1,29 @@
|
|||
var config = require('../config.js');
|
||||
var Sqlite3 = require('better-sqlite3');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Sqlite = require('./Sqlite.js')
|
||||
var Mysql = require('./Mysql.js')
|
||||
|
||||
let options = {
|
||||
readonly: config.readOnly,
|
||||
fileMustExist: !config.createDatabaseIfNotExist
|
||||
};
|
||||
|
||||
if (config.mysql) {
|
||||
module.exports = {
|
||||
db: new Mysql(config.mysql),
|
||||
privateDB: new Mysql(config.privateMysql)
|
||||
};
|
||||
} else {
|
||||
// Make dirs if required
|
||||
if (!fs.existsSync(path.join(config.db, "../"))) {
|
||||
fs.mkdirSync(path.join(config.db, "../"));
|
||||
}
|
||||
if (!fs.existsSync(path.join(config.privateDB, "../"))) {
|
||||
fs.mkdirSync(path.join(config.privateDB, "../"));
|
||||
}
|
||||
|
||||
var db = new Sqlite3(config.db, options);
|
||||
var privateDB = new Sqlite3(config.privateDB, options);
|
||||
|
||||
|
@ -15,6 +32,12 @@ if (config.createDatabaseIfNotExist && !config.readOnly) {
|
|||
if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString());
|
||||
}
|
||||
|
||||
// Upgrade database if required
|
||||
if (!config.readOnly) {
|
||||
ugradeDB(db, "sponsorTimes");
|
||||
ugradeDB(privateDB, "private")
|
||||
}
|
||||
|
||||
// Enable WAL mode checkpoint number
|
||||
if (!config.readOnly && config.mode === "production") {
|
||||
db.exec("PRAGMA journal_mode=WAL;");
|
||||
|
@ -26,6 +49,20 @@ db.exec("pragma mmap_size= 500000000;");
|
|||
privateDB.exec("pragma mmap_size= 500000000;");
|
||||
|
||||
module.exports = {
|
||||
db: db,
|
||||
privateDB: privateDB
|
||||
db: new Sqlite(db),
|
||||
privateDB: new Sqlite(privateDB)
|
||||
};
|
||||
|
||||
function ugradeDB(db, prefix) {
|
||||
let versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version");
|
||||
let versionCode = versionCodeInfo ? versionCodeInfo.value : 0;
|
||||
|
||||
let path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (versionCode + 1) + ".sql";
|
||||
while (fs.existsSync(path)) {
|
||||
db.exec(fs.readFileSync(path).toString());
|
||||
|
||||
versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value;
|
||||
path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (versionCode + 1) + ".sql";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ 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;
|
||||
|
@ -25,21 +24,21 @@ module.exports = async function addUserAsVIP (req, res) {
|
|||
//hash the userID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput !== adminUserID) {
|
||||
if (adminUserIDInput !== config.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);
|
||||
let row = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]);
|
||||
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the vip list
|
||||
db.prepare("INSERT INTO vipUsers VALUES(?)").run(userID);
|
||||
db.prepare('run', "INSERT INTO vipUsers VALUES(?)", [userID]);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
db.prepare("DELETE FROM vipUsers WHERE userID = ?").run(userID);
|
||||
db.prepare('run', "DELETE FROM vipUsers WHERE userID = ?", [userID]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var db = require('../databases/databases.js').db;
|
||||
|
||||
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();
|
||||
let row = db.prepare('get', "SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1", []);
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function getSavedTimeForUser (req, res) {
|
|||
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);
|
||||
let row = db.prepare("get", "SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ", [userID]);
|
||||
|
||||
if (row.minutesSaved != null) {
|
||||
res.send({
|
||||
|
|
|
@ -8,202 +8,86 @@ 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.
|
||||
//gets a weighted random choice from the choices array based on their `votes` property.
|
||||
//amountOfChoices specifies the maximum 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;
|
||||
function getWeightedRandomChoice(choices, amountOfChoices) {
|
||||
//trivial case: no need to go through the whole process
|
||||
if (amountOfChoices >= choices.length) {
|
||||
return choices;
|
||||
}
|
||||
|
||||
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++) {
|
||||
//assign a weight to each choice
|
||||
let totalWeight = 0;
|
||||
choices = choices.map(choice => {
|
||||
//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;
|
||||
const weight = Math.sqrt((choice.votes + 3) * 10);
|
||||
totalWeight += weight;
|
||||
|
||||
//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;
|
||||
return { ...choice, weight };
|
||||
});
|
||||
|
||||
similarSponsorsGroups[i] = uniqueArray;
|
||||
//iterate and find amountOfChoices choices
|
||||
const chosen = [];
|
||||
while (amountOfChoices-- > 0) {
|
||||
//weighted random draw of one element of choices
|
||||
const randomNumber = Math.random() * totalWeight;
|
||||
let stackWeight = choices[0].weight;
|
||||
let i = 0;
|
||||
while (stackWeight < randomNumber) {
|
||||
stackWeight += choices[++i].weight;
|
||||
}
|
||||
|
||||
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];
|
||||
//add it to the chosen ones and remove it from the choices before the next iteration
|
||||
chosen.push(choices[i]);
|
||||
choices.splice(i, 1);
|
||||
}
|
||||
|
||||
//find the indexes never dealt with and add them
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (!finalSponsorTimeIndexesDealtWith.includes(i)) {
|
||||
finalSponsorTimeIndexes.push(i)
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
//if there are too many indexes, find the best 4
|
||||
if (finalSponsorTimeIndexes.length > 8) {
|
||||
finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices;
|
||||
//This function will find segments that are contained inside of eachother, called similar segments
|
||||
//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.
|
||||
//Segments with less than -1 votes are already ignored before this function is called
|
||||
function chooseSegments(segments) {
|
||||
//Create groups of segments that are similar to eachother
|
||||
//Segments must be sorted by their startTime so that we can build groups chronologically:
|
||||
//1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group
|
||||
//2. If a segment starts after the end of the currentGroup (> cursor), no other segment will ever fall
|
||||
// inside that group (because they're sorted) so we can create a new one
|
||||
const similarSegmentsGroups = [];
|
||||
let currentGroup;
|
||||
let cursor = -1; //-1 to make sure that, even if the 1st segment starts at 0, a new group is created
|
||||
segments.forEach(segment => {
|
||||
if (segment.startTime > cursor) {
|
||||
currentGroup = { segments: [], votes: 0 };
|
||||
similarSegmentsGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
//convert this to a final array to return
|
||||
let finalSponsorTimes = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]);
|
||||
currentGroup.segments.push(segment);
|
||||
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||
if (segment.votes > 0) {
|
||||
currentGroup.votes += segment.votes;
|
||||
}
|
||||
|
||||
//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]]);
|
||||
}
|
||||
cursor = Math.max(cursor, segment.endTime);
|
||||
});
|
||||
|
||||
return {
|
||||
sponsorTimes: finalSponsorTimes,
|
||||
UUIDs: finalUUIDs
|
||||
};
|
||||
//if there are too many groups, find the best 8
|
||||
return getWeightedRandomChoice(similarSegmentsGroups, 8).map(
|
||||
//randomly choose 1 good segment per group and return them
|
||||
group => getWeightedRandomChoice(group.segments, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns what would be sent to the client.
|
||||
* Will resond with errors if required. Returns false if it errors.
|
||||
* Will respond with errors if required. Returns false if it errors.
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
|
@ -214,8 +98,11 @@ 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"]);
|
||||
const categories = req.query.categories
|
||||
? JSON.parse(req.query.categories)
|
||||
: req.query.category
|
||||
? [req.query.category]
|
||||
: ['sponsor'];
|
||||
|
||||
/**
|
||||
* @type {Array<{
|
||||
|
@ -225,61 +112,50 @@ function handleGetSegments(req, res) {
|
|||
* }>
|
||||
* }
|
||||
*/
|
||||
let segments = [];
|
||||
const segments = [];
|
||||
|
||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
let userHashedIP, shadowHiddenSegments;
|
||||
|
||||
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;
|
||||
const categorySegments = db
|
||||
.prepare(
|
||||
'all',
|
||||
'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime',
|
||||
[videoID, category]
|
||||
)
|
||||
.filter(segment => {
|
||||
if (segment.votes < -1) {
|
||||
return false; //too untrustworthy, just ignore it
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
if (segment.shadowHidden != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sponsorTimes.push([rows[i].startTime, rows[i].endTime]);
|
||||
votes.push(rows[i].votes);
|
||||
UUIDs.push(rows[i].UUID);
|
||||
if (shadowHiddenSegments === undefined) {
|
||||
shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
|
||||
}
|
||||
|
||||
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]
|
||||
//if this isn't their ip, don't send it to them
|
||||
return shadowHiddenSegments.some(shadowHiddenSegment => {
|
||||
if (userHashedIP === undefined) {
|
||||
//hash the IP only if it's strictly necessary
|
||||
userHashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
}
|
||||
return shadowHiddenSegment.hashedIP === userHashedIP;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
res.send(500);
|
||||
|
||||
return false;
|
||||
chooseSegments(categorySegments).forEach(chosenSegment => {
|
||||
segments.push({
|
||||
category,
|
||||
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||
UUID: chosenSegment.UUID,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (segments.length == 0) {
|
||||
|
@ -288,8 +164,13 @@ function handleGetSegments(req, res) {
|
|||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.sendStatus(500);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handleGetSegments,
|
||||
|
@ -298,7 +179,7 @@ module.exports = {
|
|||
|
||||
if (segments) {
|
||||
//send result
|
||||
res.send(segments)
|
||||
}
|
||||
}
|
||||
res.send(segments);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -28,10 +28,10 @@ module.exports = function getTopUsers (req, res) {
|
|||
let totalSubmissions = [];
|
||||
let minutesSaved = [];
|
||||
|
||||
let rows = db.prepare("SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
|
||||
let rows = db.prepare('all', "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();
|
||||
"WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 GROUP BY IFNULL(userName, sponsorTimes.userID) ORDER BY " + sortBy + " DESC LIMIT 100", []);
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
userNames[i] = rows[i].userName;
|
||||
|
|
|
@ -8,8 +8,8 @@ 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();
|
||||
let row = db.prepare('get', "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", []);
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = function getUsername (req, res) {
|
|||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT userName FROM userNames WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]);
|
||||
|
||||
if (row !== undefined) {
|
||||
res.send({
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function getViewsForUser(req, res) {
|
|||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
//increase the view count by one
|
||||
if (row.viewCount != null) {
|
||||
|
|
|
@ -10,30 +10,13 @@ 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;
|
||||
}
|
||||
var isUserTrustworthy = require('../utils/isUserTrustworthy.js')
|
||||
|
||||
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);
|
||||
let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
// If it is a first time submission
|
||||
if (userSubmissionCountRow.submissionCount <= 1) {
|
||||
|
@ -101,13 +84,15 @@ async function autoModerateSubmission(submission, callback) {
|
|||
} else {
|
||||
// Check to see if video exists
|
||||
if (data.pageInfo.totalResults === 0) {
|
||||
callback("No video exists with id " + submission.videoID);
|
||||
return "No video exists with id " + submission.videoID;
|
||||
} else {
|
||||
let duration = data.items[0].contentDetails.duration;
|
||||
duration = isoDurations.toSeconds(isoDurations.parse(duration));
|
||||
|
||||
if (duration == 0) {
|
||||
// Allow submission if the duration is 0 (bug in youtube api)
|
||||
return false;
|
||||
} else if ((submission.endTime - submission.startTime) > (duration/100)*80) {
|
||||
// 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;
|
||||
|
@ -162,16 +147,22 @@ module.exports = async function postSkipSegments(req, res) {
|
|||
let startTime = parseFloat(segments[i].segment[0]);
|
||||
let endTime = parseFloat(segments[i].segment[1]);
|
||||
|
||||
if (Math.abs(startTime - endTime) < 1 || isNaN(startTime) || isNaN(endTime)
|
||||
if (isNaN(startTime) || isNaN(endTime)
|
||||
|| startTime === Infinity || endTime === Infinity || startTime > endTime) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (segments[i].category === "sponsor" && Math.abs(startTime - endTime) < 1) {
|
||||
// Too short
|
||||
res.status(400).send("Sponsors must be longer than 1 second long");
|
||||
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);
|
||||
let duplicateCheck2Row = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
||||
"and endTime = ? and category = ? and videoID = ?", [startTime, endTime, segments[i].category, videoID]);
|
||||
if (duplicateCheck2Row.count > 0) {
|
||||
res.sendStatus(409);
|
||||
return;
|
||||
|
@ -186,15 +177,17 @@ module.exports = async function postSkipSegments(req, res) {
|
|||
|
||||
try {
|
||||
//check if this user is on the vip list
|
||||
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID);
|
||||
let vipRow = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]);
|
||||
|
||||
//get current time
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let yesterday = timeSubmitted - 86400000;
|
||||
|
||||
// Disable IP ratelimiting for now
|
||||
if (false) {
|
||||
//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]);
|
||||
let rateLimitCheckRow = privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]);
|
||||
|
||||
if (rateLimitCheckRow.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
|
@ -202,19 +195,23 @@ module.exports = async function postSkipSegments(req, res) {
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable max submissions for now
|
||||
if (false) {
|
||||
//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]);
|
||||
let duplicateCheckRow = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]);
|
||||
|
||||
if (duplicateCheckRow.count >= 8) {
|
||||
if (duplicateCheckRow.count >= 16) {
|
||||
//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 shadowBanRow = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
let shadowBanned = shadowBanRow.userCount;
|
||||
|
||||
|
@ -226,7 +223,7 @@ module.exports = async function postSkipSegments(req, res) {
|
|||
let startingVotes = 0;
|
||||
if (vipRow.userCount > 0) {
|
||||
//this user is a vip, start them at a higher approval rating
|
||||
startingVotes = 10;
|
||||
startingVotes = 10000;
|
||||
}
|
||||
|
||||
for (const segmentInfo of segments) {
|
||||
|
@ -237,11 +234,13 @@ module.exports = async function postSkipSegments(req, res) {
|
|||
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);
|
||||
db.prepare('run', "INSERT INTO sponsorTimes " +
|
||||
"(videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden)" +
|
||||
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [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);
|
||||
privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]);
|
||||
} catch (err) {
|
||||
//a DB change probably occurred
|
||||
res.sendStatus(502);
|
||||
|
|
|
@ -33,14 +33,14 @@ module.exports = function setUsername(req, res) {
|
|||
|
||||
try {
|
||||
//check if username is already set
|
||||
let row = db.prepare("SELECT count(*) as count FROM userNames WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT count(*) as count FROM userNames WHERE userID = ?", [userID]);
|
||||
|
||||
if (row.count > 0) {
|
||||
//already exists, update this row
|
||||
db.prepare("UPDATE userNames SET userName = ? WHERE userID = ?").run(userName, userID);
|
||||
db.prepare('run', "UPDATE userNames SET userName = ? WHERE userID = ?", [userName, userID]);
|
||||
} else {
|
||||
//add to the db
|
||||
db.prepare("INSERT INTO userNames VALUES(?, ?)").run(userID, userName);
|
||||
db.prepare('run', "INSERT INTO userNames VALUES(?, ?)", [userID, userName]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
|
|
|
@ -41,23 +41,23 @@ module.exports = async function shadowBanUser(req, res) {
|
|||
}
|
||||
|
||||
//check to see if this user is already shadowbanned
|
||||
let row = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [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);
|
||||
privateDB.prepare('run', "INSERT INTO shadowBannedUsers VALUES(?)", [userID]);
|
||||
|
||||
//find all previous submissions and hide them
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?").run(userID);
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?", [userID]);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
privateDB.prepare("DELETE FROM shadowBannedUsers WHERE userID = ?").run(userID);
|
||||
privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
//find all previous submissions and unhide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?").run(userID);
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ module.exports = function viewedVideoSponsorTime(req, res) {
|
|||
}
|
||||
|
||||
//up the view count by one
|
||||
db.prepare("UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?").run(UUID);
|
||||
db.prepare('run', "UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?", [UUID]);
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ var config = require('../config.js');
|
|||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||
var isUserTrustworthy = require('../utils/isUserTrustworthy.js')
|
||||
|
||||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
|
@ -11,12 +12,72 @@ var privateDB = databases.privateDB;
|
|||
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||
var request = require('request');
|
||||
|
||||
function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
|
||||
// Check if they've already made a vote
|
||||
let previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]);
|
||||
|
||||
if (previousVoteInfo > 0 && previousVoteInfo.category === category) {
|
||||
// Double vote, ignore
|
||||
res.sendStatus(200);
|
||||
return;
|
||||
}
|
||||
|
||||
let currentCategory = db.prepare('get', "select category from sponsorTimes where UUID = ?", [UUID]);
|
||||
if (!currentCategory) {
|
||||
// Submission doesn't exist
|
||||
res.status("400").send("Submission doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let voteAmount = isVIP ? 500 : 1;
|
||||
|
||||
// Add the vote
|
||||
if (db.prepare('get', "select count(*) as count from categoryVotes where UUID = ? and category = ?", [UUID, category]).count > 0) {
|
||||
// Update the already existing db entry
|
||||
db.prepare('run', "update categoryVotes set votes = votes + ? where UUID = ? and category = ?", [voteAmount, UUID, category]);
|
||||
} else {
|
||||
// Add a db entry
|
||||
db.prepare('run', "insert into categoryVotes (UUID, category, votes) values (?, ?, ?)", [UUID, category, voteAmount]);
|
||||
}
|
||||
|
||||
// Add the info into the private db
|
||||
if (previousVoteInfo > 0) {
|
||||
// Reverse the previous vote
|
||||
db.prepare('run', "update categoryVotes set votes -= 1 where UUID = ? and category = ?", [UUID, previousVoteInfo.category]);
|
||||
|
||||
privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?", [category, timeSubmitted, hashedIP]);
|
||||
} else {
|
||||
privateDB.prepare('run', "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, userID, hashedIP, category, timeSubmitted]);
|
||||
}
|
||||
|
||||
// See if the submissions category is ready to change
|
||||
let currentCategoryInfo = db.prepare('get', "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]);
|
||||
|
||||
// Change this value from 1 in the future to make it harder to change categories
|
||||
// Done this way without ORs incase the value is zero
|
||||
let currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? 1 : currentCategoryInfo.votes;
|
||||
|
||||
let nextCategoryCount = (previousVoteInfo.votes || 0) + 1;
|
||||
|
||||
//TODO: In the future, raise this number from zero to make it harder to change categories
|
||||
// VIPs change it every time
|
||||
if (nextCategoryCount - currentCategoryCount >= 0 || isVIP) {
|
||||
// Replace the category
|
||||
db.prepare('run', "update sponsorTimes set category = ? where UUID = ?", [category, UUID]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
|
||||
module.exports = async function voteOnSponsorTime(req, res) {
|
||||
let UUID = req.query.UUID;
|
||||
let userID = req.query.userID;
|
||||
let type = req.query.type;
|
||||
let category = req.query.category;
|
||||
|
||||
if (UUID == undefined || userID == undefined || type == undefined) {
|
||||
if (UUID === undefined || userID === undefined || (type === undefined && category === undefined)) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
|
@ -32,18 +93,42 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
//hash the ip 5000 times so no one can get it from the database
|
||||
let hashedIP = getHash(ip + config.globalSalt);
|
||||
|
||||
//check if this user is on the vip list
|
||||
let isVIP = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [nonAnonUserID]).userCount > 0;
|
||||
|
||||
if (type === undefined && category !== undefined) {
|
||||
return categoryVote(UUID, userID, isVIP, category, hashedIP, res);
|
||||
}
|
||||
|
||||
if (type == 1 && !isVIP) {
|
||||
// Check if upvoting hidden segment
|
||||
let voteInfo = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
|
||||
if (voteInfo && voteInfo.votes <= -2) {
|
||||
res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let voteTypes = {
|
||||
normal: 0,
|
||||
incorrect: 1
|
||||
}
|
||||
|
||||
let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect;
|
||||
|
||||
try {
|
||||
//check if vote has already happened
|
||||
let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID);
|
||||
let votesRow = privateDB.prepare('get', "SELECT type FROM votes WHERE userID = ? AND UUID = ?", [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) {
|
||||
if (type == 1 || type == 11) {
|
||||
//upvote
|
||||
incrementAmount = 1;
|
||||
} else if (type == 0) {
|
||||
} else if (type == 0 || type == 10) {
|
||||
//downvote
|
||||
incrementAmount = -1;
|
||||
} else {
|
||||
|
@ -52,28 +137,32 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
return;
|
||||
}
|
||||
if (votesRow != undefined) {
|
||||
if (votesRow.type == 1) {
|
||||
if (votesRow.type === 1 || type === 11) {
|
||||
//upvote
|
||||
oldIncrementAmount = 1;
|
||||
} else if (votesRow.type == 0) {
|
||||
} else if (votesRow.type === 0 || type === 10) {
|
||||
//downvote
|
||||
oldIncrementAmount = -1;
|
||||
} else if (votesRow.type == 2) {
|
||||
} else if (votesRow.type === 2) {
|
||||
//extra downvote
|
||||
oldIncrementAmount = -4;
|
||||
} else if (votesRow.type < 0) {
|
||||
//vip downvote
|
||||
oldIncrementAmount = votesRow.type;
|
||||
} else if (votesRow.type === 12) {
|
||||
// VIP downvote for completely incorrect
|
||||
oldIncrementAmount = -500;
|
||||
} else if (votesRow.type === 13) {
|
||||
// VIP upvote for completely incorrect
|
||||
oldIncrementAmount = 500;
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
let row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
|
||||
if (vipRow.userCount != 0 && incrementAmount < 0) {
|
||||
if (voteTypeEnum === voteTypes.normal) {
|
||||
if (isVIP && incrementAmount < 0) {
|
||||
//this user is a vip and a downvote
|
||||
incrementAmount = - (row.votes + 2 - oldIncrementAmount);
|
||||
type = incrementAmount;
|
||||
|
@ -82,15 +171,34 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount));
|
||||
type = incrementAmount;
|
||||
}
|
||||
} else if (voteTypeEnum == voteTypes.incorrect) {
|
||||
if (isVIP) {
|
||||
//this user is a vip and a downvote
|
||||
incrementAmount = 500 * incrementAmount;
|
||||
type = incrementAmount < 0 ? 12 : 13;
|
||||
}
|
||||
}
|
||||
|
||||
// Send discord message
|
||||
if (type != 1) {
|
||||
if (incrementAmount < 0) {
|
||||
// Get video ID
|
||||
let submissionInfoRow = db.prepare("SELECT videoID, userID, startTime, endTime FROM sponsorTimes WHERE UUID = ?").get(UUID);
|
||||
let submissionInfoRow = db.prepare('get', "SELECT s.videoID, s.userID, s.startTime, s.endTime, s.category, u.userName, " +
|
||||
"(select count(1) from sponsorTimes where userID = s.userID) count, " +
|
||||
"(select count(1) from sponsorTimes where userID = s.userID and votes <= -2) disregarded " +
|
||||
"FROM sponsorTimes s left join userNames u on s.userID = u.userID where s.UUID=?",
|
||||
[UUID]);
|
||||
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID);
|
||||
let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]);
|
||||
|
||||
if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) {
|
||||
if (submissionInfoRow !== undefined && userSubmissionCountRow != undefined) {
|
||||
let webhookURL = null;
|
||||
if (voteTypeEnum === voteTypes.normal) {
|
||||
webhookURL = config.discordReportChannelWebhookURL;
|
||||
} else if (voteTypeEnum === voteTypes.incorrect) {
|
||||
webhookURL = config.discordCompletelyIncorrectReportWebhookURL;
|
||||
}
|
||||
|
||||
if (config.youtubeAPIKey !== null && webhookURL !== null) {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "snippet",
|
||||
id: submissionInfoRow.videoID
|
||||
|
@ -100,19 +208,23 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
request.post(config.discordReportChannelWebhookURL, {
|
||||
request.post(webhookURL, {
|
||||
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: " +
|
||||
"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\n**Submission ID:** " + UUID
|
||||
+ "\n**Category:** " + submissionInfoRow.category
|
||||
+ "\n\n**Submitted by:** "+submissionInfoRow.userName+"\n " + submissionInfoRow.userID
|
||||
+ "\n\n**Total User Submissions:** "+submissionInfoRow.count
|
||||
+ "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded
|
||||
+"\n\n**Timestamp:** " +
|
||||
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" : "")
|
||||
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (isVIP ? "Report by VIP User" : "")
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||
|
@ -133,32 +245,47 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update the votes table
|
||||
if (votesRow != undefined) {
|
||||
privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
|
||||
privateDB.prepare('run', "UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?", [type, userID, UUID]);
|
||||
} else {
|
||||
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type);
|
||||
privateDB.prepare('run', "INSERT INTO votes VALUES(?, ?, ?, ?)", [UUID, userID, hashedIP, type]);
|
||||
}
|
||||
|
||||
let columnName = "";
|
||||
if (voteTypeEnum === voteTypes.normal) {
|
||||
columnName = "votes";
|
||||
} else if (voteTypeEnum === voteTypes.incorrect) {
|
||||
columnName = "incorrectVotes";
|
||||
}
|
||||
|
||||
//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);
|
||||
db.prepare('run', "UPDATE sponsorTimes SET " + columnName + " = " + columnName + " + ? WHERE UUID = ?", [incrementAmount - oldIncrementAmount, UUID]);
|
||||
|
||||
//for each positive vote, see if a hidden submission can be shown again
|
||||
if (incrementAmount > 0) {
|
||||
if (incrementAmount > 0 && voteTypeEnum === voteTypes.normal) {
|
||||
//find the UUID that submitted the submission that was voted on
|
||||
let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID;
|
||||
let submissionUserIDInfo = db.prepare('get', "SELECT userID FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
if (!submissionUserIDInfo) {
|
||||
// They are voting on a non-existent submission
|
||||
res.status(400).send("Voting on a non-existent submission");
|
||||
return;
|
||||
}
|
||||
|
||||
let submissionUserID = submissionUserIDInfo.userID;
|
||||
|
||||
//check if any submissions are hidden
|
||||
let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID);
|
||||
let hiddenSubmissionsRow = db.prepare('get', "SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0", [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)
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)", [submissionUserID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,5 +294,7 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
|||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
res.status(500).json({error: 'Internal error creating segment vote'});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
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;
|
||||
if (config.behindProxy === true || config.behindProxy === "true") config.behindProxy = "X-Forwarded-For";
|
||||
|
||||
switch (config.behindProxy) {
|
||||
case "X-Forwarded-For":
|
||||
return req.headers['x-forwarded-for'];
|
||||
case "Cloudflare":
|
||||
return req.headers['cf-connecting-ip'];
|
||||
case "X-Real-IP":
|
||||
return req.headers['x-real-ip'];
|
||||
default:
|
||||
return req.connection.remoteAddress;
|
||||
}
|
||||
}
|
20
src/utils/isUserTrustworthy.js
Normal file
20
src/utils/isUserTrustworthy.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
|
||||
//returns true if the user is considered trustworthy
|
||||
//this happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
|
||||
module.exports = async (userID) => {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
let totalSubmissionsRow = db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
let downvotedSubmissionsRow = db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
2
test.js
2
test.js
|
@ -34,7 +34,7 @@ var mockServer = createMockServer(() => {
|
|||
mocha.run(function(failures) {
|
||||
mockServer.close();
|
||||
server.close();
|
||||
process.exit(failures ? 1 : 0); // exit with non-zero status if there were failures
|
||||
process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
"youtubeAPIKey": "",
|
||||
"discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook",
|
||||
"discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook",
|
||||
"discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/CompletelyIncorrectReportWebhook",
|
||||
"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",
|
||||
"schemaFolder": "./databases",
|
||||
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||
"privateDBSchema": "./databases/_private.db.sql",
|
||||
"mode": "test",
|
||||
"readOnly": false
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
var request = require('request');
|
||||
var utils = require('../utils.js');
|
||||
var db = require('../../src/databases/databases.js').db;
|
||||
var db = require('../../src/databases/databases.js').db.getConnection();
|
||||
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)");
|
||||
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||
db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)");
|
||||
});
|
||||
|
||||
it('Should be able to get a 200', (done) => {
|
||||
|
|
|
@ -18,13 +18,14 @@ var utils = require('../utils.js');
|
|||
|
||||
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)");
|
||||
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||
db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0)");
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ var utils = require('../utils.js');
|
|||
|
||||
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)");
|
||||
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||
db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)");
|
||||
});
|
||||
|
||||
it('Should be able to get a time', (done) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('postVideoSponsorTime (Old submission method)', () => {
|
|||
(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");
|
||||
let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcQ"]);
|
||||
if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") {
|
||||
done()
|
||||
} else {
|
||||
|
@ -31,7 +31,7 @@ describe('postVideoSponsorTime (Old submission method)', () => {
|
|||
(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");
|
||||
let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcE"]);
|
||||
if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") {
|
||||
done()
|
||||
} else {
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('postSkipSegments', () => {
|
|||
(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");
|
||||
let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]);
|
||||
if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") {
|
||||
done()
|
||||
} else {
|
||||
|
@ -40,7 +40,7 @@ describe('postSkipSegments', () => {
|
|||
(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");
|
||||
let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcF"]);
|
||||
if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") {
|
||||
done()
|
||||
} else {
|
||||
|
@ -70,7 +70,7 @@ describe('postSkipSegments', () => {
|
|||
(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 rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]);
|
||||
let success = true;
|
||||
if (rows.length === 2) {
|
||||
for (const row of rows) {
|
||||
|
@ -90,6 +90,26 @@ describe('postSkipSegments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should be accepted if a non-sponsor is less than 1 second', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing&category=intro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode === 200) done(); // pass
|
||||
else done("non 200 status code: " + res.statusCode + " ("+body+")");
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be rejected if a sponsor is less than 1 second', (done) => {
|
||||
request.post(utils.getbaseURL()
|
||||
+ "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode === 400) done(); // pass
|
||||
else done("non 403 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=30&endTime=1000000&userID=testing", null,
|
||||
|
@ -100,6 +120,16 @@ describe('postSkipSegments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Should be allowed if youtube thinks duration is 0', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null,
|
||||
(err, res, body) => {
|
||||
if (err) done("Couldn't call endpoint");
|
||||
else if (res.statusCode === 200) done(); // pass
|
||||
else done("non 200 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,
|
||||
|
|
174
test/cases/voteOnSponsorTime.js
Normal file
174
test/cases/voteOnSponsorTime.js
Normal file
|
@ -0,0 +1,174 @@
|
|||
var request = require('request');
|
||||
var db = require('../../src/databases/databases.js').db;
|
||||
var utils = require('../utils.js');
|
||||
var getHash = require('../../src/utils/getHash.js')
|
||||
|
||||
describe('voteOnSponsorTime', () => {
|
||||
before(() => {
|
||||
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||
db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('vote-testtesttest', 20, 33, 10, 'vote-uuid-2', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0)");
|
||||
db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0)");
|
||||
db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0)");
|
||||
|
||||
db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')");
|
||||
});
|
||||
|
||||
|
||||
it('Should be able to upvote a segment', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID&UUID=vote-uuid-0&type=1", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-0"]);
|
||||
if (row.votes === 3) {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from 2 votes to " + row.votes);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to downvote a segment', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-2&type=0", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-2"]);
|
||||
if (row.votes < 10) {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from 10 votes to " + row.votes);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('VIP should be able to completely downvote a segment', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-3&type=0", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-3"]);
|
||||
if (row.votes <= -2) {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from 100 votes to " + row.votes);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to vote for a category and it should immediately change (for now)', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=intro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]);
|
||||
if (row.category === "intro") {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from sponsor to " + row.category);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to change your vote for a category and it should immediately change (for now)', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=outro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]);
|
||||
if (row.category === "outro") {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from intro to " + row.category);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('VIP should be able to vote for a category and it should immediately change', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&category=outro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]);
|
||||
let row2 = db.prepare('get', "SELECT votes FROM categoryVotes WHERE UUID = ? and category = ?", ["vote-uuid-5", "outro"]);
|
||||
if (row.category === "outro" && row2.votes === 500) {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Submission went from intro to " + row.category + ". Category votes are " + row2.votes + " and should be 500.");
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not be able to category-vote on an invalid UUID submission', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID3&UUID=invalid-uuid&category=intro", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 400) {
|
||||
done();
|
||||
} else {
|
||||
done("Status code was " + res.statusCode + " instead of 400.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Non-VIP should not be able to upvote "dead" submission', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-5&type=1", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 403) {
|
||||
done();
|
||||
} else {
|
||||
done("Status code was " + res.statusCode + " instead of 403");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('VIP should be able to upvote "dead" submission', (done) => {
|
||||
request.get(utils.getbaseURL()
|
||||
+ "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&type=1", null,
|
||||
(err, res, body) => {
|
||||
if (err) done(err);
|
||||
else if (res.statusCode === 200) {
|
||||
let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]);
|
||||
if (row.votes > -3) {
|
||||
done()
|
||||
} else {
|
||||
done("Vote did not succeed. Votes raised from -3 to " + row.votes);
|
||||
}
|
||||
} else {
|
||||
done("Status code was " + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
|
@ -1,23 +0,0 @@
|
|||
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;
|
|
@ -4,11 +4,15 @@ var app = express();
|
|||
var config = require('../src/config.js');
|
||||
|
||||
app.post('/ReportChannelWebhook', (req, res) => {
|
||||
res.status(200);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
|
||||
res.status(200);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
app.post('/CompletelyIncorrectReportWebhook', (req, res) => {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
module.exports = function createMockServer(callback) {
|
||||
|
|
|
@ -10,13 +10,34 @@ YouTubeAPI.videos.list({
|
|||
const YouTubeAPI = {
|
||||
videos: {
|
||||
list: (obj, callback) => {
|
||||
if (obj.videoID === "knownWrongID") {
|
||||
if (obj.id === "knownWrongID") {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
totalResults: 0
|
||||
},
|
||||
items: []
|
||||
});
|
||||
} if (obj.id === "noDuration") {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
totalResults: 1
|
||||
},
|
||||
items: [
|
||||
{
|
||||
contentDetails: {
|
||||
duration: "PT0S"
|
||||
},
|
||||
snippet: {
|
||||
title: "Example Title",
|
||||
thumbnails: {
|
||||
maxres: {
|
||||
url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
|
|
Loading…
Reference in a new issue