Merge pull request #78 from ajayyy/new-vote-options

New vote options + Auto-schema upgrade
This commit is contained in:
Ajay Ramachandran
2020-05-10 20:21:50 -04:00
committed by GitHub
16 changed files with 530 additions and 203 deletions

View File

@@ -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]
"discordCompletelyIncorrectReportWebhookURL": null, //URL from discord if you would like notifications when someone reports a submission as completely incorrect [optional]
"behindProxy": true, "behindProxy": true,
"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",

View File

@@ -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;

View File

@@ -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;

View 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
View File

@@ -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",

View File

@@ -1,12 +1,21 @@
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');
let options = { let options = {
readonly: config.readOnly, readonly: config.readOnly,
fileMustExist: !config.createDatabaseIfNotExist fileMustExist: !config.createDatabaseIfNotExist
}; };
// Make dirs if required
if (!fs.existsSync(path.join(config.db, "../"))) {
fs.mkdirSync(path.join(config.db, "../"));
}
if (!fs.existsSync(path.join(config.privateDB, "../"))) {
fs.mkdirSync(path.join(config.privateDB, "../"));
}
var db = new Sqlite3(config.db, options); var db = new Sqlite3(config.db, options);
var privateDB = new Sqlite3(config.privateDB, options); var privateDB = new Sqlite3(config.privateDB, options);
@@ -15,6 +24,12 @@ if (config.createDatabaseIfNotExist && !config.readOnly) {
if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString()); if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString());
} }
// Upgrade database if required
if (!config.readOnly) {
ugradeDB(db, "sponsorTimes");
ugradeDB(privateDB, "private")
}
// Enable WAL mode checkpoint number // Enable WAL mode checkpoint number
if (!config.readOnly && config.mode === "production") { if (!config.readOnly && config.mode === "production") {
db.exec("PRAGMA journal_mode=WAL;"); db.exec("PRAGMA journal_mode=WAL;");
@@ -28,4 +43,17 @@ privateDB.exec("pragma mmap_size= 500000000;");
module.exports = { module.exports = {
db: db, db: db,
privateDB: privateDB privateDB: 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";
}
}

View File

@@ -239,7 +239,9 @@ 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("INSERT INTO sponsorTimes " +
"(videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden)" +
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0],
segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned); segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned);
//add to private db as well //add to private db as well

View File

@@ -11,169 +11,275 @@ 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("select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?").get(UUID, userID);
if (previousVoteInfo > 0 && previousVoteInfo.category === category) {
// Double vote, ignore
res.sendStatus(200);
return;
}
let timeSubmitted = Date.now();
let voteAmount = isVIP ? 500 : 1;
// Add the vote
if (db.prepare("select count(*) as count from categoryVotes where UUID = ? and category = ?").get(UUID, category).count > 0) {
// Update the already existing db entry
db.prepare("update categoryVotes set votes = votes + ? where UUID = ? and category = ?").run(voteAmount, UUID, category);
} else {
// Add a db entry
db.prepare("insert into categoryVotes (UUID, category, votes) values (?, ?, ?)").run(UUID, category, voteAmount);
}
// Add the info into the private db
if (previousVoteInfo > 0) {
// Reverse the previous vote
db.prepare("update categoryVotes set votes -= 1 where UUID = ? and category = ?").run(UUID, previousVoteInfo.category);
privateDB.prepare("update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?").run(category, timeSubmitted, hashedIP)
} else {
privateDB.prepare("insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)").run(UUID, userID, hashedIP, category, timeSubmitted);
}
// See if the submissions categort is ready to change
let currentCategory = db.prepare("select category from sponsorTimes where UUID = ?").get(UUID);
let currentCategoryInfo = db.prepare("select votes from categoryVotes where UUID = ? and category = ?").get(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("update sponsorTimes set category = ? where UUID = ?").run(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("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(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("SELECT votes FROM sponsorTimes WHERE UUID = ?").get(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 s.videoID, s.userID, s.startTime, s.endTime, 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 inner join userNames u on s.userID = u.userID where s.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("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(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\n**Submission ID:** " + UUID return;
+ "\n\n**Submitted by:** "+submissionInfoRow.userName+"\n " + submissionInfoRow.userID }
+ "\n\n**Total User Submissions:** "+submissionInfoRow.count if (votesRow != undefined) {
+ "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded if (votesRow.type === 1 || type === 11) {
+"\n\n**Timestamp:** " + //upvote
getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), oldIncrementAmount = 1;
"color": 10813440, } else if (votesRow.type === 0 || type === 10) {
"author": { //downvote
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "") oldIncrementAmount = -1;
}, } else if (votesRow.type === 2) {
"thumbnail": { //extra downvote
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", oldIncrementAmount = -4;
} } else if (votesRow.type < 0) {
}] //vip downvote
} oldIncrementAmount = votesRow.type;
}, (err, res) => { } else if (votesRow.type === 12) {
if (err) { // VIP downvote for completely incorrect
console.log("Failed to send reported submission Discord hook."); oldIncrementAmount = -500;
console.log(JSON.stringify(err)); } else if (votesRow.type === 13) {
console.log("\n"); // VIP upvote for completely incorrect
} else if (res && res.statusCode >= 400) { oldIncrementAmount = 500;
console.log("Error sending reported submission Discord hook"); }
console.log(JSON.stringify(res)); }
console.log("\n");
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID);
if (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("SELECT s.videoID, s.userID, s.startTime, s.endTime, 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=?"
).get(UUID);
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(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\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("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
} else { } else {
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(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("UPDATE sponsorTimes SET " + columnName + " = " + columnName + " + ? WHERE UUID = ?").run(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 submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID;
if (hiddenSubmissionsRow.hiddenSubmissions > 0) { //check if any submissions are hidden
//see if some of this users submissions should be visible again let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID);
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 if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
res.sendStatus(200); //see if some of this users submissions should be visible again
} catch (err) {
console.error(err); if (await isUserTrustworthy(submissionUserID)) {
res.status(500).json({error: 'Internal error creating segment vote'}); //they are trustworthy again, show 2 of their submissions again, if there are two to show
} db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID)
}
}
}
//added to db
res.sendStatus(200);
} catch (err) {
console.error(err);
res.status(500).json({error: 'Internal error creating segment vote'});
}
} }

View File

@@ -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
} }

View File

@@ -5,7 +5,8 @@ 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) => {

View File

@@ -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)");
}); });

View File

@@ -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) => {

View File

@@ -0,0 +1,161 @@
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("SELECT votes FROM sponsorTimes WHERE UUID = ?").get("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("SELECT votes FROM sponsorTimes WHERE UUID = ?").get("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("SELECT votes FROM sponsorTimes WHERE UUID = ?").get("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("SELECT category FROM sponsorTimes WHERE UUID = ?").get("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("SELECT category FROM sponsorTimes WHERE UUID = ?").get("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("SELECT category FROM sponsorTimes WHERE UUID = ?").get("vote-uuid-5");
let row2 = db.prepare("SELECT votes FROM categoryVotes WHERE UUID = ? and category = ?").get("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('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("SELECT votes FROM sponsorTimes WHERE UUID = ?").get("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);
}
});
});
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -11,6 +11,10 @@ app.post('/FirstTimeSubmissionsWebhook', (req, res) => {
res.sendStatus(200); res.sendStatus(200);
}); });
app.post('/CompletelyIncorrectReportWebhook', (req, res) => {
res.sendStatus(200);
});
module.exports = function createMockServer(callback) { module.exports = function createMockServer(callback) {
return app.listen(config.mockPort, callback); return app.listen(config.mockPort, callback);
} }