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]
|
"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]
|
"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]
|
"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",
|
"db": "./databases/sponsorTimes.db",
|
||||||
"privateDB": "./databases/private.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
|
"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",
|
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||||
"privateDBSchema": "./databases/_private.db.sql",
|
"privateDBSchema": "./databases/_private.db.sql",
|
||||||
"mode": "development",
|
"mode": "development",
|
||||||
|
|
|
@ -1,18 +1,35 @@
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "shadowBannedUsers" (
|
CREATE TABLE IF NOT EXISTS "shadowBannedUsers" (
|
||||||
"userID" TEXT NOT NULL
|
"userID" TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS "votes" (
|
CREATE TABLE IF NOT EXISTS "votes" (
|
||||||
"UUID" TEXT NOT NULL,
|
"UUID" TEXT NOT NULL,
|
||||||
"userID" INTEGER NOT NULL,
|
"userID" TEXT NOT NULL,
|
||||||
"hashedIP" INTEGER NOT NULL,
|
"hashedIP" TEXT NOT NULL,
|
||||||
"type" INTEGER 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" (
|
CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||||
"videoID" TEXT NOT NULL,
|
"videoID" TEXT NOT NULL,
|
||||||
"hashedIP" TEXT NOT NULL,
|
"hashedIP" TEXT NOT NULL,
|
||||||
"timeSubmitted" INTEGER 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 sponsorTimes_hashedIP on sponsorTimes(hashedIP);
|
||||||
CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID);
|
CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID);
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "vipUsers" (
|
CREATE TABLE IF NOT EXISTS "vipUsers" (
|
||||||
"userID" TEXT NOT NULL
|
"userID" TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -18,6 +19,18 @@ CREATE TABLE IF NOT EXISTS "userNames" (
|
||||||
"userID" TEXT NOT NULL,
|
"userID" TEXT NOT NULL,
|
||||||
"userName" 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_videoID on sponsorTimes(videoID);
|
||||||
CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID);
|
CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID);
|
||||||
|
|
||||||
COMMIT;
|
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=",
|
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||||
"dev": true
|
"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": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http": "0.0.0",
|
"http": "0.0.0",
|
||||||
"iso8601-duration": "^1.2.0",
|
"iso8601-duration": "^1.2.0",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"sync-mysql": "^3.0.1",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^3.3.2",
|
||||||
"youtube-api": "^2.0.10"
|
"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,31 +1,68 @@
|
||||||
var config = require('../config.js');
|
var config = require('../config.js');
|
||||||
var Sqlite3 = require('better-sqlite3');
|
var Sqlite3 = require('better-sqlite3');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var Sqlite = require('./Sqlite.js')
|
||||||
|
var Mysql = require('./Mysql.js')
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
readonly: config.readOnly,
|
readonly: config.readOnly,
|
||||||
fileMustExist: !config.createDatabaseIfNotExist
|
fileMustExist: !config.createDatabaseIfNotExist
|
||||||
};
|
};
|
||||||
|
|
||||||
var db = new Sqlite3(config.db, options);
|
if (config.mysql) {
|
||||||
var privateDB = new Sqlite3(config.privateDB, options);
|
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, "../"));
|
||||||
|
}
|
||||||
|
|
||||||
if (config.createDatabaseIfNotExist && !config.readOnly) {
|
var db = new Sqlite3(config.db, options);
|
||||||
if (fs.existsSync(config.dbSchema)) db.exec(fs.readFileSync(config.dbSchema).toString());
|
var privateDB = new Sqlite3(config.privateDB, options);
|
||||||
if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable WAL mode checkpoint number
|
if (config.createDatabaseIfNotExist && !config.readOnly) {
|
||||||
if (!config.readOnly && config.mode === "production") {
|
if (fs.existsSync(config.dbSchema)) db.exec(fs.readFileSync(config.dbSchema).toString());
|
||||||
db.exec("PRAGMA journal_mode=WAL;");
|
if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString());
|
||||||
db.exec("PRAGMA wal_autocheckpoint=1;");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Enable Memory-Mapped IO
|
// Upgrade database if required
|
||||||
db.exec("pragma mmap_size= 500000000;");
|
if (!config.readOnly) {
|
||||||
privateDB.exec("pragma mmap_size= 500000000;");
|
ugradeDB(db, "sponsorTimes");
|
||||||
|
ugradeDB(privateDB, "private")
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
// Enable WAL mode checkpoint number
|
||||||
db: db,
|
if (!config.readOnly && config.mode === "production") {
|
||||||
privateDB: privateDB
|
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: 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 db = require('../databases/databases.js').db;
|
||||||
var getHash = require('../utils/getHash.js');
|
var getHash = require('../utils/getHash.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = async function addUserAsVIP (req, res) {
|
module.exports = async function addUserAsVIP (req, res) {
|
||||||
let userID = req.query.userID;
|
let userID = req.query.userID;
|
||||||
let adminUserIDInput = req.query.adminUserID;
|
let adminUserIDInput = req.query.adminUserID;
|
||||||
|
@ -25,21 +24,21 @@ module.exports = async function addUserAsVIP (req, res) {
|
||||||
//hash the userID
|
//hash the userID
|
||||||
adminUserIDInput = getHash(adminUserIDInput);
|
adminUserIDInput = getHash(adminUserIDInput);
|
||||||
|
|
||||||
if (adminUserIDInput !== adminUserID) {
|
if (adminUserIDInput !== config.adminUserID) {
|
||||||
//not authorized
|
//not authorized
|
||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//check to see if this user is already a vip
|
//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) {
|
if (enabled && row.userCount == 0) {
|
||||||
//add them to the vip list
|
//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) {
|
} else if (!enabled && row.userCount > 0) {
|
||||||
//remove them from the shadow ban list
|
//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);
|
res.sendStatus(200);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var db = require('../databases/databases.js').db;
|
var db = require('../databases/databases.js').db;
|
||||||
|
|
||||||
module.exports = function getDaysSavedFormatted (req, res) {
|
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) {
|
if (row !== undefined) {
|
||||||
//send this result
|
//send this result
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function getSavedTimeForUser (req, res) {
|
||||||
userID = getHash(userID);
|
userID = getHash(userID);
|
||||||
|
|
||||||
try {
|
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) {
|
if (row.minutesSaved != null) {
|
||||||
res.send({
|
res.send({
|
||||||
|
|
|
@ -8,297 +8,178 @@ var privateDB = databases.privateDB;
|
||||||
var getHash = require('../utils/getHash.js');
|
var getHash = require('../utils/getHash.js');
|
||||||
var getIP = require('../utils/getIP.js');
|
var getIP = require('../utils/getIP.js');
|
||||||
|
|
||||||
|
//gets a weighted random choice from the choices array based on their `votes` property.
|
||||||
//gets the getWeightedRandomChoice for each group in an array of groups
|
//amountOfChoices specifies the maximum amount of choices to return, 1 or more.
|
||||||
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
|
//choices are unique
|
||||||
function getWeightedRandomChoice(choices, weights, amountOfChoices) {
|
function getWeightedRandomChoice(choices, amountOfChoices) {
|
||||||
if (amountOfChoices > choices.length) {
|
//trivial case: no need to go through the whole process
|
||||||
//not possible, since all choices must be unique
|
if (amountOfChoices >= choices.length) {
|
||||||
return null;
|
return choices;
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalChoices = [];
|
//assign a weight to each choice
|
||||||
let choicesDealtWith = [];
|
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.
|
||||||
|
const weight = Math.sqrt((choice.votes + 3) * 10);
|
||||||
|
totalWeight += weight;
|
||||||
|
|
||||||
let sqrtWeightsList = [];
|
return { ...choice, weight };
|
||||||
//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
|
//iterate and find amountOfChoices choices
|
||||||
let randomNumber = Math.random();
|
const chosen = [];
|
||||||
|
while (amountOfChoices-- > 0) {
|
||||||
//this array will keep adding to this variable each time one sqrt vote has been dealt with
|
//weighted random draw of one element of choices
|
||||||
//this is the sum of all the sqrtVotes under this index
|
const randomNumber = Math.random() * totalWeight;
|
||||||
let currentVoteNumber = 0;
|
let stackWeight = choices[0].weight;
|
||||||
for (let j = 0; j < sqrtWeightsList.length; j++) {
|
let i = 0;
|
||||||
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) {
|
while (stackWeight < randomNumber) {
|
||||||
//this one was randomly generated
|
stackWeight += choices[++i].weight;
|
||||||
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
|
//add it to the chosen ones and remove it from the choices before the next iteration
|
||||||
currentVoteNumber += sqrtWeightsList[j];
|
chosen.push(choices[i]);
|
||||||
}
|
choices.splice(i, 1);
|
||||||
|
|
||||||
//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 {
|
return chosen;
|
||||||
finalChoices: finalChoices,
|
|
||||||
choicesDealtWith: choicesDealtWith
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This function will find segments that are contained inside of eachother, called similar segments
|
||||||
//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.
|
//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.
|
//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
|
//Segments with less than -1 votes are already ignored before this function is called
|
||||||
function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) {
|
function chooseSegments(segments) {
|
||||||
//list of sponsors that are contained inside eachother
|
//Create groups of segments that are similar to eachother
|
||||||
let similarSponsors = [];
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
currentGroup.segments.push(segment);
|
||||||
//see if the start time is located between the start and end time of the other sponsor time.
|
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||||
for (let j = i + 1; j < sponsorTimes.length; j++) {
|
if (segment.votes > 0) {
|
||||||
if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) {
|
currentGroup.votes += segment.votes;
|
||||||
//sponsor j is contained in sponsor i
|
}
|
||||||
similarSponsors.push([i, j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let similarSponsorsGroups = [];
|
cursor = Math.max(cursor, segment.endTime);
|
||||||
//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)
|
//if there are too many groups, find the best 8
|
||||||
for (let i = 0; i < similarSponsors.length; i++) {
|
return getWeightedRandomChoice(similarSegmentsGroups, 8).map(
|
||||||
if (dealtWithSimilarSponsors.includes(i)) {
|
//randomly choose 1 good segment per group and return them
|
||||||
//dealt with already
|
group => getWeightedRandomChoice(group.segments, 1)[0]
|
||||||
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.
|
* 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 req
|
||||||
* @param res
|
* @param res
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function handleGetSegments(req, res) {
|
function handleGetSegments(req, res) {
|
||||||
const videoID = req.query.videoID;
|
const videoID = req.query.videoID;
|
||||||
// Default to sponsor
|
// Default to sponsor
|
||||||
// If using params instead of JSON, only one category can be pulled
|
// If using params instead of JSON, only one category can be pulled
|
||||||
const categories = req.query.categories ? JSON.parse(req.query.categories)
|
const categories = req.query.categories
|
||||||
: (req.query.category ? [req.query.category] : ["sponsor"]);
|
? JSON.parse(req.query.categories)
|
||||||
|
: req.query.category
|
||||||
|
? [req.query.category]
|
||||||
|
: ['sponsor'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<{
|
* @type {Array<{
|
||||||
* segment: number[],
|
* segment: number[],
|
||||||
* category: string,
|
* category: string,
|
||||||
* UUID: string
|
* UUID: string
|
||||||
* }>
|
* }>
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
let segments = [];
|
const segments = [];
|
||||||
|
|
||||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
let userHashedIP, shadowHiddenSegments;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime")
|
const categorySegments = db
|
||||||
.all(videoID, category);
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
let sponsorTimes = [];
|
//check if shadowHidden
|
||||||
let votes = []
|
//this means it is hidden to everyone but the original ip that submitted it
|
||||||
let UUIDs = [];
|
if (segment.shadowHidden != 1) {
|
||||||
|
return true;
|
||||||
for (let i = 0; i < rows.length; i++) {
|
}
|
||||||
//check if votes are above -1
|
|
||||||
if (rows[i].votes < -1) {
|
if (shadowHiddenSegments === undefined) {
|
||||||
//too untrustworthy, just ignore it
|
shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
//if this isn't their ip, don't send it to them
|
||||||
//check if shadowHidden
|
return shadowHiddenSegments.some(shadowHiddenSegment => {
|
||||||
//this means it is hidden to everyone but the original ip that submitted it
|
if (userHashedIP === undefined) {
|
||||||
if (rows[i].shadowHidden == 1) {
|
//hash the IP only if it's strictly necessary
|
||||||
//get the ip
|
userHashedIP = getHash(getIP(req) + config.globalSalt);
|
||||||
//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);
|
|
||||||
}
|
}
|
||||||
|
return shadowHiddenSegment.hashedIP === userHashedIP;
|
||||||
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;
|
chooseSegments(categorySegments).forEach(chosenSegment => {
|
||||||
|
segments.push({
|
||||||
|
category,
|
||||||
|
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||||
|
UUID: chosenSegment.UUID,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segments.length == 0) {
|
if (segments.length == 0) {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
return segments;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.sendStatus(500);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
handleGetSegments,
|
handleGetSegments,
|
||||||
endpoint: function (req, res) {
|
endpoint: function (req, res) {
|
||||||
let segments = handleGetSegments(req, res);
|
let segments = handleGetSegments(req, res);
|
||||||
|
|
||||||
if (segments) {
|
if (segments) {
|
||||||
//send result
|
//send result
|
||||||
res.send(segments)
|
res.send(segments);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -28,10 +28,10 @@ module.exports = function getTopUsers (req, res) {
|
||||||
let totalSubmissions = [];
|
let totalSubmissions = [];
|
||||||
let minutesSaved = [];
|
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, " +
|
"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 " +
|
"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++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
userNames[i] = rows[i].userName;
|
userNames[i] = rows[i].userName;
|
||||||
|
|
|
@ -8,8 +8,8 @@ var lastUserCountCheck = 0;
|
||||||
|
|
||||||
|
|
||||||
module.exports = function getTotalStats (req, res) {
|
module.exports = function getTotalStats (req, res) {
|
||||||
let row = db.prepare("SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " +
|
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").get();
|
"SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1", []);
|
||||||
|
|
||||||
if (row !== undefined) {
|
if (row !== undefined) {
|
||||||
//send this result
|
//send this result
|
||||||
|
|
|
@ -15,7 +15,7 @@ module.exports = function getUsername (req, res) {
|
||||||
userID = getHash(userID);
|
userID = getHash(userID);
|
||||||
|
|
||||||
try {
|
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) {
|
if (row !== undefined) {
|
||||||
res.send({
|
res.send({
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function getViewsForUser(req, res) {
|
||||||
userID = getHash(userID);
|
userID = getHash(userID);
|
||||||
|
|
||||||
try {
|
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
|
//increase the view count by one
|
||||||
if (row.viewCount != null) {
|
if (row.viewCount != null) {
|
||||||
|
|
|
@ -10,30 +10,13 @@ var isoDurations = require('iso8601-duration');
|
||||||
var getHash = require('../utils/getHash.js');
|
var getHash = require('../utils/getHash.js');
|
||||||
var getIP = require('../utils/getIP.js');
|
var getIP = require('../utils/getIP.js');
|
||||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||||
|
var isUserTrustworthy = require('../utils/isUserTrustworthy.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) {
|
function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||||
//check if they are a first time user
|
//check if they are a first time user
|
||||||
//if so, send a notification to discord
|
//if so, send a notification to discord
|
||||||
if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null) {
|
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 it is a first time submission
|
||||||
if (userSubmissionCountRow.submissionCount <= 1) {
|
if (userSubmissionCountRow.submissionCount <= 1) {
|
||||||
|
@ -101,13 +84,15 @@ async function autoModerateSubmission(submission, callback) {
|
||||||
} else {
|
} else {
|
||||||
// Check to see if video exists
|
// Check to see if video exists
|
||||||
if (data.pageInfo.totalResults === 0) {
|
if (data.pageInfo.totalResults === 0) {
|
||||||
callback("No video exists with id " + submission.videoID);
|
return "No video exists with id " + submission.videoID;
|
||||||
} else {
|
} else {
|
||||||
let duration = data.items[0].contentDetails.duration;
|
let duration = data.items[0].contentDetails.duration;
|
||||||
duration = isoDurations.toSeconds(isoDurations.parse(duration));
|
duration = isoDurations.toSeconds(isoDurations.parse(duration));
|
||||||
|
if (duration == 0) {
|
||||||
// Reject submission if over 80% of the video
|
// Allow submission if the duration is 0 (bug in youtube api)
|
||||||
if ((submission.endTime - submission.startTime) > (duration/100)*80) {
|
return false;
|
||||||
|
} else if ((submission.endTime - submission.startTime) > (duration/100)*80) {
|
||||||
|
// Reject submission if over 80% of the video
|
||||||
return "Sponsor segment is over 80% of the video.";
|
return "Sponsor segment is over 80% of the video.";
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -162,16 +147,22 @@ module.exports = async function postSkipSegments(req, res) {
|
||||||
let startTime = parseFloat(segments[i].segment[0]);
|
let startTime = parseFloat(segments[i].segment[0]);
|
||||||
let endTime = parseFloat(segments[i].segment[1]);
|
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) {
|
|| startTime === Infinity || endTime === Infinity || startTime > endTime) {
|
||||||
//invalid request
|
//invalid request
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
return;
|
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
|
//check if this info has already been submitted before
|
||||||
let duplicateCheck2Row = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
let duplicateCheck2Row = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
||||||
"and endTime = ? and category = ? and videoID = ?").get(startTime, endTime, segments[i].category, videoID);
|
"and endTime = ? and category = ? and videoID = ?", [startTime, endTime, segments[i].category, videoID]);
|
||||||
if (duplicateCheck2Row.count > 0) {
|
if (duplicateCheck2Row.count > 0) {
|
||||||
res.sendStatus(409);
|
res.sendStatus(409);
|
||||||
return;
|
return;
|
||||||
|
@ -186,35 +177,41 @@ module.exports = async function postSkipSegments(req, res) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//check if this user is on the vip list
|
//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
|
//get current time
|
||||||
let timeSubmitted = Date.now();
|
let timeSubmitted = Date.now();
|
||||||
|
|
||||||
let yesterday = timeSubmitted - 86400000;
|
let yesterday = timeSubmitted - 86400000;
|
||||||
|
|
||||||
//check to see if this ip has submitted too many sponsors today
|
// Disable IP ratelimiting for now
|
||||||
let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]);
|
if (false) {
|
||||||
|
//check to see if this ip has submitted too many sponsors today
|
||||||
if (rateLimitCheckRow.count >= 10) {
|
let rateLimitCheckRow = privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]);
|
||||||
//too many sponsors for the same video from the same ip address
|
|
||||||
res.sendStatus(429);
|
if (rateLimitCheckRow.count >= 10) {
|
||||||
|
//too many sponsors for the same video from the same ip address
|
||||||
|
res.sendStatus(429);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//check to see if the user has already submitted sponsors for this video
|
// Disable max submissions for now
|
||||||
let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]);
|
if (false) {
|
||||||
|
//check to see if the user has already submitted sponsors for this video
|
||||||
if (duplicateCheckRow.count >= 8) {
|
let duplicateCheckRow = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]);
|
||||||
//too many sponsors for the same video from the same user
|
|
||||||
res.sendStatus(429);
|
if (duplicateCheckRow.count >= 16) {
|
||||||
|
//too many sponsors for the same video from the same user
|
||||||
|
res.sendStatus(429);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//check to see if this user is shadowbanned
|
//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;
|
let shadowBanned = shadowBanRow.userCount;
|
||||||
|
|
||||||
|
@ -226,7 +223,7 @@ module.exports = async function postSkipSegments(req, res) {
|
||||||
let startingVotes = 0;
|
let startingVotes = 0;
|
||||||
if (vipRow.userCount > 0) {
|
if (vipRow.userCount > 0) {
|
||||||
//this user is a vip, start them at a higher approval rating
|
//this user is a vip, start them at a higher approval rating
|
||||||
startingVotes = 10;
|
startingVotes = 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const segmentInfo of segments) {
|
for (const segmentInfo of segments) {
|
||||||
|
@ -237,11 +234,13 @@ module.exports = async function postSkipSegments(req, res) {
|
||||||
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
|
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0],
|
db.prepare('run', "INSERT INTO sponsorTimes " +
|
||||||
segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned);
|
"(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
|
//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) {
|
} catch (err) {
|
||||||
//a DB change probably occurred
|
//a DB change probably occurred
|
||||||
res.sendStatus(502);
|
res.sendStatus(502);
|
||||||
|
|
|
@ -33,14 +33,14 @@ module.exports = function setUsername(req, res) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//check if username is already set
|
//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) {
|
if (row.count > 0) {
|
||||||
//already exists, update this row
|
//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 {
|
} else {
|
||||||
//add to the db
|
//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);
|
res.sendStatus(200);
|
||||||
|
|
|
@ -41,23 +41,23 @@ module.exports = async function shadowBanUser(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//check to see if this user is already shadowbanned
|
//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) {
|
if (enabled && row.userCount == 0) {
|
||||||
//add them to the shadow ban list
|
//add them to the shadow ban list
|
||||||
|
|
||||||
//add it to the table
|
//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
|
//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) {
|
} else if (!enabled && row.userCount > 0) {
|
||||||
//remove them from the shadow ban list
|
//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
|
//find all previous submissions and unhide them
|
||||||
if (unHideOldSubmissions) {
|
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
|
//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);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ var config = require('../config.js');
|
||||||
var getHash = require('../utils/getHash.js');
|
var getHash = require('../utils/getHash.js');
|
||||||
var getIP = require('../utils/getIP.js');
|
var getIP = require('../utils/getIP.js');
|
||||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||||
|
var isUserTrustworthy = require('../utils/isUserTrustworthy.js')
|
||||||
|
|
||||||
var databases = require('../databases/databases.js');
|
var databases = require('../databases/databases.js');
|
||||||
var db = databases.db;
|
var db = databases.db;
|
||||||
|
@ -11,161 +12,289 @@ var privateDB = databases.privateDB;
|
||||||
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||||
var request = require('request');
|
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) {
|
module.exports = async function voteOnSponsorTime(req, res) {
|
||||||
let UUID = req.query.UUID;
|
let UUID = req.query.UUID;
|
||||||
let userID = req.query.userID;
|
let userID = req.query.userID;
|
||||||
let type = req.query.type;
|
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
|
//invalid request
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//hash the userID
|
//hash the userID
|
||||||
let nonAnonUserID = getHash(userID);
|
let nonAnonUserID = getHash(userID);
|
||||||
userID = getHash(userID + UUID);
|
userID = getHash(userID + UUID);
|
||||||
|
|
||||||
//x-forwarded-for if this server is behind a proxy
|
//x-forwarded-for if this server is behind a proxy
|
||||||
let ip = getIP(req);
|
let ip = getIP(req);
|
||||||
|
|
||||||
//hash the ip 5000 times so no one can get it from the database
|
//hash the ip 5000 times so no one can get it from the database
|
||||||
let hashedIP = getHash(ip + config.globalSalt);
|
let hashedIP = getHash(ip + config.globalSalt);
|
||||||
|
|
||||||
try {
|
//check if this user is on the vip list
|
||||||
//check if vote has already happened
|
let isVIP = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [nonAnonUserID]).userCount > 0;
|
||||||
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) {
|
if (type === undefined && category !== undefined) {
|
||||||
//upvote
|
return categoryVote(UUID, userID, isVIP, category, hashedIP, res);
|
||||||
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
|
if (type == 1 && !isVIP) {
|
||||||
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID);
|
// Check if upvoting hidden segment
|
||||||
|
let voteInfo = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||||
|
|
||||||
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
|
if (voteInfo && voteInfo.votes <= -2) {
|
||||||
let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID);
|
res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.")
|
||||||
|
return;
|
||||||
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
|
let voteTypes = {
|
||||||
if (type != 1) {
|
normal: 0,
|
||||||
// Get video ID
|
incorrect: 1
|
||||||
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);
|
let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect;
|
||||||
|
|
||||||
if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) {
|
try {
|
||||||
YouTubeAPI.videos.list({
|
//check if vote has already happened
|
||||||
part: "snippet",
|
let votesRow = privateDB.prepare('get', "SELECT type FROM votes WHERE userID = ? AND UUID = ?", [userID, UUID]);
|
||||||
id: submissionInfoRow.videoID
|
|
||||||
}, function (err, data) {
|
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future
|
||||||
if (err || data.items.length === 0) {
|
let incrementAmount = 0;
|
||||||
err && console.log(err);
|
let oldIncrementAmount = 0;
|
||||||
return;
|
|
||||||
}
|
if (type == 1 || type == 11) {
|
||||||
|
//upvote
|
||||||
request.post(config.discordReportChannelWebhookURL, {
|
incrementAmount = 1;
|
||||||
json: {
|
} else if (type == 0 || type == 10) {
|
||||||
"embeds": [{
|
//downvote
|
||||||
"title": data.items[0].snippet.title,
|
incrementAmount = -1;
|
||||||
"url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID +
|
} else {
|
||||||
"&t=" + (submissionInfoRow.startTime.toFixed(0) - 2),
|
//unrecongnised type of vote
|
||||||
"description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views +
|
res.sendStatus(400);
|
||||||
" Views**\n\nSubmission ID: " + UUID +
|
return;
|
||||||
"\n\nSubmitted by: " + submissionInfoRow.userID + "\n\nTimestamp: " +
|
}
|
||||||
getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime),
|
if (votesRow != undefined) {
|
||||||
"color": 10813440,
|
if (votesRow.type === 1 || type === 11) {
|
||||||
"author": {
|
//upvote
|
||||||
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "")
|
oldIncrementAmount = 1;
|
||||||
},
|
} else if (votesRow.type === 0 || type === 10) {
|
||||||
"thumbnail": {
|
//downvote
|
||||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
oldIncrementAmount = -1;
|
||||||
}
|
} else if (votesRow.type === 2) {
|
||||||
}]
|
//extra downvote
|
||||||
}
|
oldIncrementAmount = -4;
|
||||||
}, (err, res) => {
|
} else if (votesRow.type < 0) {
|
||||||
if (err) {
|
//vip downvote
|
||||||
console.log("Failed to send reported submission Discord hook.");
|
oldIncrementAmount = votesRow.type;
|
||||||
console.log(JSON.stringify(err));
|
} else if (votesRow.type === 12) {
|
||||||
console.log("\n");
|
// VIP downvote for completely incorrect
|
||||||
} else if (res && res.statusCode >= 400) {
|
oldIncrementAmount = -500;
|
||||||
console.log("Error sending reported submission Discord hook");
|
} else if (votesRow.type === 13) {
|
||||||
console.log(JSON.stringify(res));
|
// VIP upvote for completely incorrect
|
||||||
console.log("\n");
|
oldIncrementAmount = 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
|
||||||
|
let row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||||
|
|
||||||
|
if (voteTypeEnum === voteTypes.normal) {
|
||||||
|
if (isVIP && 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;
|
||||||
|
}
|
||||||
|
} 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 (incrementAmount < 0) {
|
||||||
|
// Get video ID
|
||||||
|
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('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]);
|
||||||
|
|
||||||
|
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
|
||||||
|
}, function (err, data) {
|
||||||
|
if (err || data.items.length === 0) {
|
||||||
|
err && console.log(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
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\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" : (isVIP ? "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
|
//update the votes table
|
||||||
if (votesRow != undefined) {
|
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 {
|
} else {
|
||||||
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type);
|
privateDB.prepare('run', "INSERT INTO votes VALUES(?, ?, ?, ?)", [UUID, userID, hashedIP, type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update the vote count on this sponsorTime
|
let columnName = "";
|
||||||
//oldIncrementAmount will be zero is row is null
|
if (voteTypeEnum === voteTypes.normal) {
|
||||||
db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID);
|
columnName = "votes";
|
||||||
|
} else if (voteTypeEnum === voteTypes.incorrect) {
|
||||||
|
columnName = "incorrectVotes";
|
||||||
|
}
|
||||||
|
|
||||||
//for each positive vote, see if a hidden submission can be shown again
|
//update the vote count on this sponsorTime
|
||||||
if (incrementAmount > 0) {
|
//oldIncrementAmount will be zero is row is null
|
||||||
//find the UUID that submitted the submission that was voted on
|
db.prepare('run', "UPDATE sponsorTimes SET " + columnName + " = " + columnName + " + ? WHERE UUID = ?", [incrementAmount - oldIncrementAmount, UUID]);
|
||||||
let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID;
|
|
||||||
|
|
||||||
//check if any submissions are hidden
|
//for each positive vote, see if a hidden submission can be shown again
|
||||||
let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID);
|
if (incrementAmount > 0 && voteTypeEnum === voteTypes.normal) {
|
||||||
|
//find the UUID that submitted the submission that was voted on
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
|
let submissionUserID = submissionUserIDInfo.userID;
|
||||||
//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
|
//check if any submissions are hidden
|
||||||
res.sendStatus(200);
|
let hiddenSubmissionsRow = db.prepare('get', "SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0", [submissionUserID]);
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
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('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)", [submissionUserID]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//added to db
|
||||||
|
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');
|
var config = require('../config.js');
|
||||||
|
|
||||||
module.exports = function getIP(req) {
|
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) {
|
mocha.run(function(failures) {
|
||||||
mockServer.close();
|
mockServer.close();
|
||||||
server.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": "",
|
"youtubeAPIKey": "",
|
||||||
"discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook",
|
"discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook",
|
||||||
"discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook",
|
"discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook",
|
||||||
|
"discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/CompletelyIncorrectReportWebhook",
|
||||||
"behindProxy": true,
|
"behindProxy": true,
|
||||||
"db": "./test/databases/sponsorTimes.db",
|
"db": "./test/databases/sponsorTimes.db",
|
||||||
"privateDB": "./test/databases/private.db",
|
"privateDB": "./test/databases/private.db",
|
||||||
"createDatabaseIfNotExist": true,
|
"createDatabaseIfNotExist": true,
|
||||||
"dbSchema": "./test/databases/_sponsorTimes.db.sql",
|
"schemaFolder": "./databases",
|
||||||
"privateDBSchema": "./test/databases/_private.db.sql",
|
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||||
|
"privateDBSchema": "./databases/_private.db.sql",
|
||||||
"mode": "test",
|
"mode": "test",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var utils = require('../utils.js');
|
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');
|
var getHash = require('../../src/utils/getHash.js');
|
||||||
|
|
||||||
describe('getSavedTimeForUser', () => {
|
describe('getSavedTimeForUser', () => {
|
||||||
before(() => {
|
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) => {
|
it('Should be able to get a 200', (done) => {
|
||||||
|
|
|
@ -18,13 +18,14 @@ var utils = require('../utils.js');
|
||||||
|
|
||||||
describe('getSkipSegments', () => {
|
describe('getSkipSegments', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)");
|
db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)");
|
db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)");
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)");
|
db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', '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(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', '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(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)");
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 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)', () => {
|
describe('getVideoSponsorTime (Old get method)', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)");
|
let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES";
|
||||||
db.exec("INSERT INTO sponsorTimes VALUES ('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)");
|
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) => {
|
it('Should be able to get a time', (done) => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe('postVideoSponsorTime (Old submission method)', () => {
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
else if (res.statusCode === 200) {
|
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") {
|
if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,7 +31,7 @@ describe('postVideoSponsorTime (Old submission method)', () => {
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
else if (res.statusCode === 200) {
|
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") {
|
if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe('postSkipSegments', () => {
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
else if (res.statusCode === 200) {
|
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") {
|
if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +40,7 @@ describe('postSkipSegments', () => {
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
else if (res.statusCode === 200) {
|
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") {
|
if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,7 +70,7 @@ describe('postSkipSegments', () => {
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
else if (res.statusCode === 200) {
|
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;
|
let success = true;
|
||||||
if (rows.length === 2) {
|
if (rows.length === 2) {
|
||||||
for (const row of rows) {
|
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) => {
|
it('Should be rejected if over 80% of the video', (done) => {
|
||||||
request.get(utils.getbaseURL()
|
request.get(utils.getbaseURL()
|
||||||
+ "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null,
|
+ "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null,
|
||||||
|
@ -99,6 +119,16 @@ describe('postSkipSegments', () => {
|
||||||
else done("non 403 status code: " + res.statusCode + " ("+body+")");
|
else done("non 403 status code: " + res.statusCode + " ("+body+")");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should be allowed if youtube thinks duration is 0', (done) => {
|
||||||
|
request.get(utils.getbaseURL()
|
||||||
|
+ "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null,
|
||||||
|
(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) => {
|
it('Should be rejected if not a valid videoID', (done) => {
|
||||||
request.get(utils.getbaseURL()
|
request.get(utils.getbaseURL()
|
||||||
|
|
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');
|
var config = require('../src/config.js');
|
||||||
|
|
||||||
app.post('/ReportChannelWebhook', (req, res) => {
|
app.post('/ReportChannelWebhook', (req, res) => {
|
||||||
res.status(200);
|
res.sendStatus(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
|
app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
|
||||||
res.status(200);
|
res.sendStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/CompletelyIncorrectReportWebhook', (req, res) => {
|
||||||
|
res.sendStatus(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = function createMockServer(callback) {
|
module.exports = function createMockServer(callback) {
|
||||||
|
|
|
@ -10,14 +10,35 @@ YouTubeAPI.videos.list({
|
||||||
const YouTubeAPI = {
|
const YouTubeAPI = {
|
||||||
videos: {
|
videos: {
|
||||||
list: (obj, callback) => {
|
list: (obj, callback) => {
|
||||||
if (obj.videoID === "knownWrongID") {
|
if (obj.id === "knownWrongID") {
|
||||||
callback(undefined, {
|
callback(undefined, {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
totalResults: 0
|
totalResults: 0
|
||||||
},
|
},
|
||||||
items: []
|
items: []
|
||||||
});
|
});
|
||||||
} else {
|
} 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, {
|
callback(undefined, {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
totalResults: 1
|
totalResults: 1
|
||||||
|
|
Loading…
Reference in a new issue