diff --git a/config.json.example b/config.json.example index b8a9d79..c548ffc 100644 --- a/config.json.example +++ b/config.json.example @@ -7,10 +7,12 @@ "youtubeAPIKey": null, //get this from Google Cloud Platform [optional] "discordReportChannelWebhookURL": null, //URL from discord if you would like notifications when someone makes a report [optional] "discordFirstTimeSubmissionsWebhookURL": null, //URL from discord if you would like notifications when someone makes a first time submission [optional] - "behindProxy": true, + "discordCompletelyIncorrectReportWebhookURL": null, //URL from discord if you would like notifications when someone reports a submission as completely incorrect [optional] + "behindProxy": "X-Forwarded-For", //Options: "X-Forwarded-For", "Cloudflare", "X-Real-IP", anything else will mean it is not behind a proxy. True defaults to "X-Forwarded-For" "db": "./databases/sponsorTimes.db", "privateDB": "./databases/private.db", "createDatabaseIfNotExist": true, //This will run on startup every time (unless readOnly is true) - so ensure "create table if not exists" is used in the schema + "schemaFolder": "./databases", "dbSchema": "./databases/_sponsorTimes.db.sql", "privateDBSchema": "./databases/_private.db.sql", "mode": "development", diff --git a/databases/_private.db.sql b/databases/_private.db.sql index b93aaee..0324481 100644 --- a/databases/_private.db.sql +++ b/databases/_private.db.sql @@ -1,18 +1,30 @@ BEGIN TRANSACTION; + CREATE TABLE IF NOT EXISTS "shadowBannedUsers" ( "userID" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "votes" ( "UUID" TEXT NOT NULL, - "userID" INTEGER NOT NULL, - "hashedIP" INTEGER NOT NULL, + "userID" TEXT NOT NULL, + "hashedIP" TEXT NOT NULL, "type" INTEGER NOT NULL ); + +CREATE TABLE IF NOT EXISTS "categoryVotes" ( + "UUID" TEXT NOT NULL, + "userID" TEXT NOT NULL, + "hashedIP" TEXT NOT NULL, + "category" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL +); + CREATE TABLE IF NOT EXISTS "sponsorTimes" ( "videoID" TEXT NOT NULL, "hashedIP" TEXT NOT NULL, "timeSubmitted" INTEGER NOT NULL ); + CREATE INDEX IF NOT EXISTS sponsorTimes_hashedIP on sponsorTimes(hashedIP); CREATE INDEX IF NOT EXISTS votes_userID on votes(UUID); + COMMIT; diff --git a/databases/_sponsorTimes.db.sql b/databases/_sponsorTimes.db.sql index dfca8bf..707cb5c 100644 --- a/databases/_sponsorTimes.db.sql +++ b/databases/_sponsorTimes.db.sql @@ -1,4 +1,5 @@ BEGIN TRANSACTION; + CREATE TABLE IF NOT EXISTS "vipUsers" ( "userID" TEXT NOT NULL ); @@ -18,6 +19,17 @@ CREATE TABLE IF NOT EXISTS "userNames" ( "userID" TEXT NOT NULL, "userName" TEXT NOT NULL ); +CREATE TABLE IF NOT EXISTS "categoryVotes" ( + "UUID" TEXT NOT NULL, + "category" TEXT NOT NULL, + "votes" INTEGER NOT NULL default '0' +); + +CREATE TABLE IF NOT EXISTS "version" ( + "code" INTEGER NOT NULL default '0' +); + CREATE INDEX IF NOT EXISTS sponsorTimes_videoID on sponsorTimes(videoID); CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID); + COMMIT; \ No newline at end of file diff --git a/databases/_upgrade_0.sql b/databases/_upgrade_0.sql new file mode 100644 index 0000000..4f747bc --- /dev/null +++ b/databases/_upgrade_0.sql @@ -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"; + +/* Increase version number */ +INSERT INTO version VALUES(1); + +COMMIT; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2c6dcc0..ecb01c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1398,6 +1398,11 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "iso8601-duration": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz", + "integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg==" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", diff --git a/src/databases/databases.js b/src/databases/databases.js index d01d01f..bd3aad1 100644 --- a/src/databases/databases.js +++ b/src/databases/databases.js @@ -1,12 +1,21 @@ var config = require('../config.js'); var Sqlite3 = require('better-sqlite3'); var fs = require('fs'); +var path = require('path'); let options = { readonly: config.readOnly, 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 privateDB = new Sqlite3(config.privateDB, options); @@ -15,6 +24,18 @@ if (config.createDatabaseIfNotExist && !config.readOnly) { if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString()); } +// Upgrade database if required +if (!config.readOnly) { + let versionCode = db.prepare("SELECT code FROM version").get() || 0; + let path = config.schemaFolder + "/_upgrade_" + versionCode + ".sql"; + while (fs.existsSync(path)) { + db.exec(fs.readFileSync(path).toString()); + + versionCode = db.prepare("SELECT code FROM version").get(); + path = config.schemaFolder + "/_upgrade_" + versionCode + ".sql"; + } +} + // Enable WAL mode checkpoint number if (!config.readOnly && config.mode === "production") { db.exec("PRAGMA journal_mode=WAL;"); diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.js index 3e5b632..7a52bc3 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.js @@ -265,7 +265,9 @@ module.exports = async function postSkipSegments(req, res) { segmentInfo.segment[1] + segmentInfo.category + userID, 1); try { - db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0], + 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); //add to private db as well diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index dda73ac..2918d3f 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -11,169 +11,265 @@ var privateDB = databases.privateDB; var YouTubeAPI = require('../utils/youtubeAPI.js'); var request = require('request'); +function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { + // Check if they've already made a vote + let previousVoteInfo = privateDB.prepare("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) { - let UUID = req.query.UUID; - let userID = req.query.userID; - let type = req.query.type; + let UUID = req.query.UUID; + let userID = req.query.userID; + let type = req.query.type; + let category = req.query.category; - if (UUID == undefined || userID == undefined || type == undefined) { - //invalid request - res.sendStatus(400); - return; - } + if (UUID === undefined || userID === undefined || (type === undefined && category === undefined)) { + //invalid request + res.sendStatus(400); + return; + } - //hash the userID - let nonAnonUserID = getHash(userID); - userID = getHash(userID + UUID); + //hash the userID + let nonAnonUserID = getHash(userID); + userID = getHash(userID + UUID); - //x-forwarded-for if this server is behind a proxy - let ip = getIP(req); + //x-forwarded-for if this server is behind a proxy + let ip = getIP(req); - //hash the ip 5000 times so no one can get it from the database - let hashedIP = getHash(ip + config.globalSalt); + //hash the ip 5000 times so no one can get it from the database + let hashedIP = getHash(ip + config.globalSalt); - try { - //check if vote has already happened - let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID); - - //-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future - let incrementAmount = 0; - let oldIncrementAmount = 0; + //check if this user is on the vip list + let isVIP = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID).userCount > 0; - if (type == 1) { - //upvote - incrementAmount = 1; - } else if (type == 0) { - //downvote - incrementAmount = -1; - } else { - //unrecongnised type of vote - res.sendStatus(400); - return; - } - if (votesRow != undefined) { - if (votesRow.type == 1) { - //upvote - oldIncrementAmount = 1; - } else if (votesRow.type == 0) { - //downvote - oldIncrementAmount = -1; - } else if (votesRow.type == 2) { - //extra downvote - oldIncrementAmount = -4; - } else if (votesRow.type < 0) { - //vip downvote - oldIncrementAmount = votesRow.type; - } - } + if (type === undefined && category !== undefined) { + return categoryVote(UUID, userID, isVIP, category, hashedIP, res); + } - //check if this user is on the vip list - let vipRow = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(nonAnonUserID); + let voteTypes = { + normal: 0, + incorrect: 1 + } - //check if the increment amount should be multiplied (downvotes have more power if there have been many views) - let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID); - - if (vipRow.userCount != 0 && incrementAmount < 0) { - //this user is a vip and a downvote - incrementAmount = - (row.votes + 2 - oldIncrementAmount); - type = incrementAmount; - } else if (row !== undefined && (row.votes > 8 || row.views > 15) && incrementAmount < 0) { - //increase the power of this downvote - incrementAmount = -Math.abs(Math.min(10, row.votes + 2 - oldIncrementAmount)); - type = incrementAmount; - } + let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect; - // Send discord message - if (type != 1) { - // 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 inner join userNames u on s.userID = u.userID where s.UUID=?" - ).get(UUID); + try { + //check if vote has already happened + let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID); + + //-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future + let incrementAmount = 0; + let oldIncrementAmount = 0; - let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID); + if (type == 1 || type == 11) { + //upvote + incrementAmount = 1; + } else if (type == 0 || type == 10) { + //downvote + incrementAmount = -1; + } else { + //unrecongnised type of vote + res.sendStatus(400); + return; + } + if (votesRow != undefined) { + if (votesRow.type === 1 || type === 11) { + //upvote + oldIncrementAmount = 1; + } else if (votesRow.type === 0 || type === 10) { + //downvote + oldIncrementAmount = -1; + } else if (votesRow.type === 2) { + //extra downvote + oldIncrementAmount = -4; + } else if (votesRow.type < 0) { + //vip downvote + oldIncrementAmount = votesRow.type; + } else if (votesRow.type === 12) { + // VIP downvote for completely incorrect + oldIncrementAmount = -500; + } else if (votesRow.type === 13) { + // VIP upvote for completely incorrect + oldIncrementAmount = 500; + } + } - if (config.youtubeAPIKey !== null && config.discordReportChannelWebhookURL !== null) { - YouTubeAPI.videos.list({ - part: "snippet", - id: submissionInfoRow.videoID - }, function (err, data) { - if (err || data.items.length === 0) { - err && console.log(err); - return; - } - - request.post(config.discordReportChannelWebhookURL, { - json: { - "embeds": [{ - "title": data.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID - + "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), - "description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views - + " Views**\n\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" : (vipRow.userCount !== 0 ? "Report by VIP User" : "") - }, - "thumbnail": { - "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } - }, (err, res) => { - if (err) { - console.log("Failed to send reported submission Discord hook."); - console.log(JSON.stringify(err)); - console.log("\n"); - } else if (res && res.statusCode >= 400) { - console.log("Error sending reported submission Discord hook"); - console.log(JSON.stringify(res)); - console.log("\n"); + //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 - if (votesRow != undefined) { - privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID); - } else { - privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); - } + //update the votes table + if (votesRow != undefined) { + privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID); + } else { + privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type); + } - //update the vote count on this sponsorTime - //oldIncrementAmount will be zero is row is null - db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID); + let columnName = ""; + if (voteTypeEnum === voteTypes.normal) { + columnName = "votes"; + } else if (voteTypeEnum === voteTypes.incorrect) { + columnName = "incorrectVotes"; + } - //for each positive vote, see if a hidden submission can be shown again - if (incrementAmount > 0) { - //find the UUID that submitted the submission that was voted on - let submissionUserID = db.prepare("SELECT userID FROM sponsorTimes WHERE UUID = ?").get(UUID).userID; + //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); - //check if any submissions are hidden - let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID); + //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; - 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) - } - } - } + //check if any submissions are hidden + let hiddenSubmissionsRow = db.prepare("SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0").get(submissionUserID); - //added to db - res.sendStatus(200); - } catch (err) { - console.error(err); - res.status(500).json({error: 'Internal error creating segment vote'}); - } + 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'}); + } } \ No newline at end of file diff --git a/src/utils/getIP.js b/src/utils/getIP.js index 78244fa..e8d0c26 100644 --- a/src/utils/getIP.js +++ b/src/utils/getIP.js @@ -2,5 +2,16 @@ var fs = require('fs'); var config = require('../config.js'); module.exports = function getIP(req) { - return config.behindProxy ? req.headers['x-forwarded-for'] : req.connection.remoteAddress; + if (config.behindProxy === true) config.behindProxy = "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; + } } \ No newline at end of file diff --git a/test.json b/test.json index f07fbf8..28373a1 100644 --- a/test.json +++ b/test.json @@ -6,12 +6,14 @@ "youtubeAPIKey": "", "discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook", "discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook", + "discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/CompletelyIncorrectReportWebhook", "behindProxy": true, "db": "./test/databases/sponsorTimes.db", "privateDB": "./test/databases/private.db", "createDatabaseIfNotExist": true, - "dbSchema": "./test/databases/_sponsorTimes.db.sql", - "privateDBSchema": "./test/databases/_private.db.sql", + "schemaFolder": "./databases", + "dbSchema": "./databases/_sponsorTimes.db.sql", + "privateDBSchema": "./databases/_private.db.sql", "mode": "test", "readOnly": false } diff --git a/test/cases/getSavedTimeForUser.js b/test/cases/getSavedTimeForUser.js index e3976c1..5df8a48 100644 --- a/test/cases/getSavedTimeForUser.js +++ b/test/cases/getSavedTimeForUser.js @@ -5,7 +5,8 @@ var getHash = require('../../src/utils/getHash.js'); describe('getSavedTimeForUser', () => { before(() => { - db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; + db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)"); }); it('Should be able to get a 200', (done) => { diff --git a/test/cases/getSkipSegments.js b/test/cases/getSkipSegments.js index 21c9f91..5c15c63 100644 --- a/test/cases/getSkipSegments.js +++ b/test/cases/getSkipSegments.js @@ -18,13 +18,14 @@ var utils = require('../utils.js'); describe('getSkipSegments', () => { before(() => { - db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; + db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)"); + db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)"); + db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)"); + db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)"); + db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)"); + db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0)"); + db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0)"); }); diff --git a/test/cases/oldGetSponsorTime.js b/test/cases/oldGetSponsorTime.js index ffb1264..569b109 100644 --- a/test/cases/oldGetSponsorTime.js +++ b/test/cases/oldGetSponsorTime.js @@ -19,8 +19,9 @@ var utils = require('../utils.js'); describe('getVideoSponsorTime (Old get method)', () => { before(() => { - db.exec("INSERT INTO sponsorTimes VALUES ('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; + db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)"); + db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)"); }); it('Should be able to get a time', (done) => { diff --git a/test/cases/voteOnSponsorTime.js b/test/cases/voteOnSponsorTime.js new file mode 100644 index 0000000..167347c --- /dev/null +++ b/test/cases/voteOnSponsorTime.js @@ -0,0 +1,130 @@ +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); + } + }); + }); + +}); \ No newline at end of file diff --git a/test/databases/_private.db.sql b/test/databases/_private.db.sql deleted file mode 100644 index b93aaee..0000000 --- a/test/databases/_private.db.sql +++ /dev/null @@ -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; diff --git a/test/databases/_sponsorTimes.db.sql b/test/databases/_sponsorTimes.db.sql deleted file mode 100644 index dfca8bf..0000000 --- a/test/databases/_sponsorTimes.db.sql +++ /dev/null @@ -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; \ No newline at end of file diff --git a/test/mocks.js b/test/mocks.js index 49060af..b58a47f 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -11,6 +11,10 @@ app.post('/FirstTimeSubmissionsWebhook', (req, res) => { res.sendStatus(200); }); +app.post('/CompletelyIncorrectReportWebhook', (req, res) => { + res.sendStatus(200); +}); + module.exports = function createMockServer(callback) { return app.listen(config.mockPort, callback); } \ No newline at end of file