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;");
@@ -29,3 +44,16 @@ 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');
module.exports = async function voteOnSponsorTime(req, res) { function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
let UUID = req.query.UUID; // Check if they've already made a vote
let userID = req.query.userID; let previousVoteInfo = privateDB.prepare("select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?").get(UUID, userID);
let type = req.query.type;
if (UUID == undefined || userID == undefined || type == undefined) { if (previousVoteInfo > 0 && previousVoteInfo.category === category) {
//invalid request // Double vote, ignore
res.sendStatus(400); res.sendStatus(200);
return; return;
} }
//hash the userID let timeSubmitted = Date.now();
let nonAnonUserID = getHash(userID);
userID = getHash(userID + UUID);
//x-forwarded-for if this server is behind a proxy let voteAmount = isVIP ? 500 : 1;
let ip = getIP(req);
//hash the ip 5000 times so no one can get it from the database // Add the vote
let hashedIP = getHash(ip + config.globalSalt); 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);
}
try { // Add the info into the private db
//check if vote has already happened if (previousVoteInfo > 0) {
let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID); // Reverse the previous vote
db.prepare("update categoryVotes set votes -= 1 where UUID = ? and category = ?").run(UUID, previousVoteInfo.category);
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future privateDB.prepare("update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?").run(category, timeSubmitted, hashedIP)
let incrementAmount = 0; } else {
let oldIncrementAmount = 0; privateDB.prepare("insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)").run(UUID, userID, hashedIP, category, timeSubmitted);
}
if (type == 1) { // See if the submissions categort is ready to change
//upvote let currentCategory = db.prepare("select category from sponsorTimes where UUID = ?").get(UUID);
incrementAmount = 1; let currentCategoryInfo = db.prepare("select votes from categoryVotes where UUID = ? and category = ?").get(UUID, currentCategory.category);
} 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 // Change this value from 1 in the future to make it harder to change categories
let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID); // Done this way without ORs incase the value is zero
let currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? 1 : currentCategoryInfo.votes;
//check if the increment amount should be multiplied (downvotes have more power if there have been many views) let nextCategoryCount = (previousVoteInfo.votes || 0) + 1;
let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID);
if (vipRow.userCount != 0 && incrementAmount < 0) { //TODO: In the future, raise this number from zero to make it harder to change categories
//this user is a vip and a downvote // VIPs change it every time
incrementAmount = - (row.votes + 2 - oldIncrementAmount); if (nextCategoryCount - currentCategoryCount >= 0 || isVIP) {
type = incrementAmount; // Replace the category
} else if (row !== undefined && (row.votes > 8 || row.views > 15) && incrementAmount < 0) { db.prepare("update sponsorTimes set category = ? where UUID = ?").run(category, UUID);
//increase the power of this downvote }
incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount));
type = incrementAmount;
}
// Send discord message res.sendStatus(200);
if (type != 1) { }
// Get video ID
let submissionInfoRow = db.prepare("SELECT s.videoID, s.userID, s.startTime, s.endTime, u.userName, "+ module.exports = async function voteOnSponsorTime(req, res) {
"(select count(1) from sponsorTimes where userID = s.userID) count, "+ let UUID = req.query.UUID;
"(select count(1) from sponsorTimes where userID = s.userID and votes <= -2) disregarded "+ let userID = req.query.userID;
"FROM sponsorTimes s inner join userNames u on s.userID = u.userID where s.UUID=?" let type = req.query.type;
).get(UUID); let category = req.query.category;
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID); if (UUID === undefined || userID === undefined || (type === undefined && category === undefined)) {
//invalid request
if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) { res.sendStatus(400);
YouTubeAPI.videos.list({ return;
part: "snippet", }
id: submissionInfoRow.videoID
}, function (err, data) { //hash the userID
if (err || data.items.length === 0) { let nonAnonUserID = getHash(userID);
err && console.log(err); userID = getHash(userID + UUID);
return;
} //x-forwarded-for if this server is behind a proxy
let ip = getIP(req);
request.post(config.discordReportChannelWebhookURL, {
json: { //hash the ip 5000 times so no one can get it from the database
"embeds": [{ let hashedIP = getHash(ip + config.globalSalt);
"title": data.items[0].snippet.title,
"url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID //check if this user is on the vip list
+ "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), let isVIP = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID).userCount > 0;
"description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views
+ " Views**\n\n**Submission ID:** " + UUID if (type === undefined && category !== undefined) {
+ "\n\n**Submitted by:** "+submissionInfoRow.userName+"\n " + submissionInfoRow.userID return categoryVote(UUID, userID, isVIP, category, hashedIP, res);
+ "\n\n**Total User Submissions:** "+submissionInfoRow.count }
+ "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded
+"\n\n**Timestamp:** " + if (type == 1 && !isVIP) {
getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), // Check if upvoting hidden segment
"color": 10813440, let voteInfo = db.prepare("SELECT votes FROM sponsorTimes WHERE UUID = ?").get(UUID);
"author": {
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (vipRow.userCount !== 0 ? "Report by VIP User" : "") if (voteInfo && voteInfo.votes <= -2) {
}, res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.")
"thumbnail": { return;
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", }
} }
}]
} let voteTypes = {
}, (err, res) => { normal: 0,
if (err) { incorrect: 1
console.log("Failed to send reported submission Discord hook."); }
console.log(JSON.stringify(err));
console.log("\n"); let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect;
} else if (res && res.statusCode >= 400) {
console.log("Error sending reported submission Discord hook"); try {
console.log(JSON.stringify(res)); //check if vote has already happened
console.log("\n"); 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 || type == 11) {
//update the votes table //upvote
if (votesRow != undefined) { incrementAmount = 1;
privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID); } else if (type == 0 || type == 10) {
} else { //downvote
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); incrementAmount = -1;
} } else {
//unrecongnised type of vote
//update the vote count on this sponsorTime res.sendStatus(400);
//oldIncrementAmount will be zero is row is null return;
db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID); }
if (votesRow != undefined) {
//for each positive vote, see if a hidden submission can be shown again if (votesRow.type === 1 || type === 11) {
if (incrementAmount > 0) { //upvote
//find the UUID that submitted the submission that was voted on oldIncrementAmount = 1;
let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID; } else if (votesRow.type === 0 || type === 10) {
//downvote
//check if any submissions are hidden oldIncrementAmount = -1;
let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID); } else if (votesRow.type === 2) {
//extra downvote
if (hiddenSubmissionsRow.hiddenSubmissions > 0) { oldIncrementAmount = -4;
//see if some of this users submissions should be visible again } else if (votesRow.type < 0) {
//vip downvote
if (await isUserTrustworthy(submissionUserID)) { oldIncrementAmount = votesRow.type;
//they are trustworthy again, show 2 of their submissions again, if there are two to show } else if (votesRow.type === 12) {
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID) // VIP downvote for completely incorrect
} oldIncrementAmount = -500;
} } else if (votesRow.type === 13) {
} // VIP upvote for completely incorrect
oldIncrementAmount = 500;
//added to db }
res.sendStatus(200); }
} catch (err) {
console.error(err); //check if the increment amount should be multiplied (downvotes have more power if there have been many views)
res.status(500).json({error: 'Internal error creating segment vote'}); 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
if (votesRow != undefined) {
privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
} else {
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type);
}
let columnName = "";
if (voteTypeEnum === voteTypes.normal) {
columnName = "votes";
} else if (voteTypeEnum === voteTypes.incorrect) {
columnName = "incorrectVotes";
}
//update the vote count on this sponsorTime
//oldIncrementAmount will be zero is row is null
db.prepare("UPDATE sponsorTimes SET " + columnName + " = " + columnName + " + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID);
//for each positive vote, see if a hidden submission can be shown again
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;
//check if any submissions are hidden
let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID);
if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
//see if some of this users submissions should be visible again
if (await isUserTrustworthy(submissionUserID)) {
//they are trustworthy again, show 2 of their submissions again, if there are two to show
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)").run(submissionUserID)
}
}
}
//added to db
res.sendStatus(200);
} catch (err) {
console.error(err);
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);
} }