mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-25 17:08:35 +03:00
Merge branch 'master' into nb-mod-fetch
This commit is contained in:
@@ -4,7 +4,6 @@ var config = require('../config.js');
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
|
||||
module.exports = async function addUserAsVIP (req, res) {
|
||||
let userID = req.query.userID;
|
||||
let adminUserIDInput = req.query.adminUserID;
|
||||
@@ -25,21 +24,21 @@ module.exports = async function addUserAsVIP (req, res) {
|
||||
//hash the userID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput !== adminUserID) {
|
||||
if (adminUserIDInput !== config.adminUserID) {
|
||||
//not authorized
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if this user is already a vip
|
||||
let row = db.prepare("SELECT count(*) as userCount FROM vipUsers WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]);
|
||||
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the vip list
|
||||
db.prepare("INSERT INTO vipUsers VALUES(?)").run(userID);
|
||||
db.prepare('run', "INSERT INTO vipUsers VALUES(?)", [userID]);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
db.prepare("DELETE FROM vipUsers WHERE userID = ?").run(userID);
|
||||
db.prepare('run', "DELETE FROM vipUsers WHERE userID = ?", [userID]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
|
||||
module.exports = function getDaysSavedFormatted (req, res) {
|
||||
let row = db.prepare("SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1").get();
|
||||
let row = db.prepare('get', "SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1", []);
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = function getSavedTimeForUser (req, res) {
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ").get(userID);
|
||||
let row = db.prepare("get", "SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ", [userID]);
|
||||
|
||||
if (row.minutesSaved != null) {
|
||||
res.send({
|
||||
|
||||
@@ -5,300 +5,182 @@ var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
var privateDB = databases.privateDB;
|
||||
|
||||
var logger = require('../utils/logger.js');
|
||||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
|
||||
|
||||
//gets the getWeightedRandomChoice for each group in an array of groups
|
||||
function getWeightedRandomChoiceForArray(choiceGroups, weights) {
|
||||
let finalChoices = [];
|
||||
//the indexes either chosen to be added to final indexes or chosen not to be added
|
||||
let choicesDealtWith = [];
|
||||
//for each choice group, what are the sums of the weights
|
||||
let weightSums = [];
|
||||
|
||||
for (let i = 0; i < choiceGroups.length; i++) {
|
||||
//find weight sums for this group
|
||||
weightSums.push(0);
|
||||
for (let j = 0; j < choiceGroups[i].length; j++) {
|
||||
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||
if (weights[choiceGroups[i][j]] > 0) {
|
||||
weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]];
|
||||
}
|
||||
}
|
||||
|
||||
//create a random choice for this group
|
||||
let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1)
|
||||
finalChoices.push(randomChoice.finalChoices);
|
||||
|
||||
for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) {
|
||||
choicesDealtWith.push(randomChoice.choicesDealtWith[j])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith,
|
||||
weightSums: weightSums
|
||||
};
|
||||
}
|
||||
|
||||
//gets a weighted random choice from the indexes array based on the weights.
|
||||
//amountOfChoices speicifies the amount of choices to return, 1 or more.
|
||||
//gets a weighted random choice from the choices array based on their `votes` property.
|
||||
//amountOfChoices specifies the maximum amount of choices to return, 1 or more.
|
||||
//choices are unique
|
||||
function getWeightedRandomChoice(choices, weights, amountOfChoices) {
|
||||
if (amountOfChoices > choices.length) {
|
||||
//not possible, since all choices must be unique
|
||||
return null;
|
||||
function getWeightedRandomChoice(choices, amountOfChoices) {
|
||||
//trivial case: no need to go through the whole process
|
||||
if (amountOfChoices >= choices.length) {
|
||||
return choices;
|
||||
}
|
||||
|
||||
let finalChoices = [];
|
||||
let choicesDealtWith = [];
|
||||
//assign a weight to each choice
|
||||
let totalWeight = 0;
|
||||
choices = choices.map(choice => {
|
||||
//The 3 makes -2 the minimum votes before being ignored completely
|
||||
//https://www.desmos.com/calculator/c1duhfrmts
|
||||
//this can be changed if this system increases in popularity.
|
||||
const weight = Math.exp((choice.votes + 3), 0.85);
|
||||
totalWeight += weight;
|
||||
|
||||
let sqrtWeightsList = [];
|
||||
//the total of all the weights run through the cutom sqrt function
|
||||
let totalSqrtWeights = 0;
|
||||
for (let j = 0; j < choices.length; j++) {
|
||||
//multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3)
|
||||
//The 3 makes -2 the minimum votes before being ignored completely
|
||||
//https://www.desmos.com/calculator/ljftxolg9j
|
||||
//this can be changed if this system increases in popularity.
|
||||
let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10);
|
||||
sqrtWeightsList.push(sqrtVote)
|
||||
totalSqrtWeights += sqrtVote;
|
||||
|
||||
//this index has now been deat with
|
||||
choicesDealtWith.push(choices[j]);
|
||||
}
|
||||
return { ...choice, weight };
|
||||
});
|
||||
|
||||
//iterate and find amountOfChoices choices
|
||||
let randomNumber = Math.random();
|
||||
|
||||
//this array will keep adding to this variable each time one sqrt vote has been dealt with
|
||||
//this is the sum of all the sqrtVotes under this index
|
||||
let currentVoteNumber = 0;
|
||||
for (let j = 0; j < sqrtWeightsList.length; j++) {
|
||||
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) {
|
||||
//this one was randomly generated
|
||||
finalChoices.push(choices[j]);
|
||||
//remove that from original array, for next recursion pass if it happens
|
||||
choices.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
const chosen = [];
|
||||
while (amountOfChoices-- > 0) {
|
||||
//weighted random draw of one element of choices
|
||||
const randomNumber = Math.random() * totalWeight;
|
||||
let stackWeight = choices[0].weight;
|
||||
let i = 0;
|
||||
while (stackWeight < randomNumber) {
|
||||
stackWeight += choices[++i].weight;
|
||||
}
|
||||
|
||||
//add on to the count
|
||||
currentVoteNumber += sqrtWeightsList[j];
|
||||
}
|
||||
|
||||
//add on the other choices as well using recursion
|
||||
if (amountOfChoices > 1) {
|
||||
let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices;
|
||||
//add all these choices to the finalChoices array being returned
|
||||
for (let i = 0; i < otherChoices.length; i++) {
|
||||
finalChoices.push(otherChoices[i]);
|
||||
}
|
||||
//add it to the chosen ones and remove it from the choices before the next iteration
|
||||
chosen.push(choices[i]);
|
||||
totalWeight -= choices[i].weight;
|
||||
choices.splice(i, 1);
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith
|
||||
};
|
||||
return chosen;
|
||||
}
|
||||
|
||||
|
||||
//This function will find sponsor times that are contained inside of eachother, called similar sponsor times
|
||||
//This function will find segments that are contained inside of eachother, called similar segments
|
||||
//Only one similar time will be returned, randomly generated based on the sqrt of votes.
|
||||
//This allows new less voted items to still sometimes appear to give them a chance at getting votes.
|
||||
//Sponsor times with less than -1 votes are already ignored before this function is called
|
||||
function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) {
|
||||
//list of sponsors that are contained inside eachother
|
||||
let similarSponsors = [];
|
||||
//Segments with less than -1 votes are already ignored before this function is called
|
||||
function chooseSegments(segments) {
|
||||
//Create groups of segments that are similar to eachother
|
||||
//Segments must be sorted by their startTime so that we can build groups chronologically:
|
||||
//1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group
|
||||
//2. If a segment starts after the end of the currentGroup (> cursor), no other segment will ever fall
|
||||
// inside that group (because they're sorted) so we can create a new one
|
||||
const similarSegmentsGroups = [];
|
||||
let currentGroup;
|
||||
let cursor = -1; //-1 to make sure that, even if the 1st segment starts at 0, a new group is created
|
||||
segments.forEach(segment => {
|
||||
if (segment.startTime > cursor) {
|
||||
currentGroup = { segments: [], votes: 0 };
|
||||
similarSegmentsGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
//see if the start time is located between the start and end time of the other sponsor time.
|
||||
for (let j = i + 1; j < sponsorTimes.length; j++) {
|
||||
if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) {
|
||||
//sponsor j is contained in sponsor i
|
||||
similarSponsors.push([i, j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentGroup.segments.push(segment);
|
||||
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||
if (segment.votes > 0) {
|
||||
currentGroup.votes += segment.votes;
|
||||
}
|
||||
|
||||
let similarSponsorsGroups = [];
|
||||
//once they have been added to a group, they don't need to be dealt with anymore
|
||||
let dealtWithSimilarSponsors = [];
|
||||
cursor = Math.max(cursor, segment.endTime);
|
||||
});
|
||||
|
||||
//create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3)
|
||||
for (let i = 0; i < similarSponsors.length; i++) {
|
||||
if (dealtWithSimilarSponsors.includes(i)) {
|
||||
//dealt with already
|
||||
continue;
|
||||
}
|
||||
|
||||
//this is the group of indexes that are similar
|
||||
let group = similarSponsors[i];
|
||||
for (let j = 0; j < similarSponsors.length; j++) {
|
||||
if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) {
|
||||
//this is a similar group
|
||||
group.push(similarSponsors[j][0]);
|
||||
group.push(similarSponsors[j][1]);
|
||||
dealtWithSimilarSponsors.push(j);
|
||||
}
|
||||
}
|
||||
similarSponsorsGroups.push(group);
|
||||
}
|
||||
|
||||
//remove duplicate indexes in group arrays
|
||||
for (let i = 0; i < similarSponsorsGroups.length; i++) {
|
||||
uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) {
|
||||
return self.indexOf(item) == pos;
|
||||
});
|
||||
|
||||
similarSponsorsGroups[i] = uniqueArray;
|
||||
}
|
||||
|
||||
let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes);
|
||||
|
||||
let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices;
|
||||
//the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added
|
||||
let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith;
|
||||
|
||||
let voteSums = weightedRandomIndexes.weightSums;
|
||||
//convert these into the votes
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
//it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor.
|
||||
votes[finalSponsorTimeIndexes[i]] = voteSums[i];
|
||||
}
|
||||
|
||||
//find the indexes never dealt with and add them
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (!finalSponsorTimeIndexesDealtWith.includes(i)) {
|
||||
finalSponsorTimeIndexes.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
//if there are too many indexes, find the best 4
|
||||
if (finalSponsorTimeIndexes.length > 8) {
|
||||
finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices;
|
||||
}
|
||||
|
||||
//convert this to a final array to return
|
||||
let finalSponsorTimes = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
//convert this to a final array of UUIDs as well
|
||||
let finalUUIDs = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
return {
|
||||
sponsorTimes: finalSponsorTimes,
|
||||
UUIDs: finalUUIDs
|
||||
};
|
||||
//if there are too many groups, find the best 8
|
||||
return getWeightedRandomChoice(similarSegmentsGroups, 8).map(
|
||||
//randomly choose 1 good segment per group and return them
|
||||
group => getWeightedRandomChoice(group.segments, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Returns what would be sent to the client.
|
||||
* Will resond with errors if required. Returns false if it errors.
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*
|
||||
* Will respond with errors if required. Returns false if it errors.
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
function handleGetSegments(req, res) {
|
||||
const videoID = req.query.videoID;
|
||||
// Default to sponsor
|
||||
// If using params instead of JSON, only one category can be pulled
|
||||
const categories = req.query.categories ? JSON.parse(req.query.categories)
|
||||
: (req.query.category ? [req.query.category] : ["sponsor"]);
|
||||
const videoID = req.query.videoID;
|
||||
// Default to sponsor
|
||||
// If using params instead of JSON, only one category can be pulled
|
||||
const categories = req.query.categories
|
||||
? JSON.parse(req.query.categories)
|
||||
: req.query.category
|
||||
? [req.query.category]
|
||||
: ['sponsor'];
|
||||
|
||||
/**
|
||||
* @type {Array<{
|
||||
* segment: number[],
|
||||
* category: string,
|
||||
* UUID: string
|
||||
* }>
|
||||
* }
|
||||
*/
|
||||
let segments = [];
|
||||
/**
|
||||
* @type {Array<{
|
||||
* segment: number[],
|
||||
* category: string,
|
||||
* UUID: string
|
||||
* }>
|
||||
* }
|
||||
*/
|
||||
const segments = [];
|
||||
|
||||
let hashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
let userHashedIP, shadowHiddenSegments;
|
||||
|
||||
try {
|
||||
for (const category of categories) {
|
||||
let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime")
|
||||
.all(videoID, category);
|
||||
try {
|
||||
for (const category of categories) {
|
||||
const categorySegments = db
|
||||
.prepare(
|
||||
'all',
|
||||
'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime',
|
||||
[videoID, category]
|
||||
)
|
||||
.filter(segment => {
|
||||
if (segment.votes < -1) {
|
||||
return false; //too untrustworthy, just ignore it
|
||||
}
|
||||
|
||||
let sponsorTimes = [];
|
||||
let votes = []
|
||||
let UUIDs = [];
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
//check if votes are above -1
|
||||
if (rows[i].votes < -1) {
|
||||
//too untrustworthy, just ignore it
|
||||
continue;
|
||||
}
|
||||
|
||||
//check if shadowHidden
|
||||
//this means it is hidden to everyone but the original ip that submitted it
|
||||
if (rows[i].shadowHidden == 1) {
|
||||
//get the ip
|
||||
//await the callback
|
||||
let hashedIPRow = privateDB.prepare("SELECT hashedIP FROM sponsorTimes WHERE videoID = ?").all(videoID);
|
||||
|
||||
if (!hashedIPRow.some((e) => e.hashedIP === hashedIP)) {
|
||||
//this isn't their ip, don't send it to them
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sponsorTimes.push([rows[i].startTime, rows[i].endTime]);
|
||||
votes.push(rows[i].votes);
|
||||
UUIDs.push(rows[i].UUID);
|
||||
//check if shadowHidden
|
||||
//this means it is hidden to everyone but the original ip that submitted it
|
||||
if (segment.shadowHidden != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shadowHiddenSegments === undefined) {
|
||||
shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
|
||||
}
|
||||
|
||||
//if this isn't their ip, don't send it to them
|
||||
return shadowHiddenSegments.some(shadowHiddenSegment => {
|
||||
if (userHashedIP === undefined) {
|
||||
//hash the IP only if it's strictly necessary
|
||||
userHashedIP = getHash(getIP(req) + config.globalSalt);
|
||||
}
|
||||
|
||||
organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs);
|
||||
sponsorTimes = organisedData.sponsorTimes;
|
||||
UUIDs = organisedData.UUIDs;
|
||||
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
segments.push({
|
||||
segment: sponsorTimes[i],
|
||||
category: category,
|
||||
UUID: UUIDs[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
res.send(500);
|
||||
return shadowHiddenSegment.hashedIP === userHashedIP;
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
chooseSegments(categorySegments).forEach(chosenSegment => {
|
||||
segments.push({
|
||||
category,
|
||||
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||
UUID: chosenSegment.UUID,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (segments.length == 0) {
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
||||
return segments;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.sendStatus(500);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
handleGetSegments,
|
||||
endpoint: function (req, res) {
|
||||
let segments = handleGetSegments(req, res);
|
||||
handleGetSegments,
|
||||
endpoint: function (req, res) {
|
||||
let segments = handleGetSegments(req, res);
|
||||
|
||||
if (segments) {
|
||||
//send result
|
||||
res.send(segments)
|
||||
}
|
||||
if (segments) {
|
||||
//send result
|
||||
res.send(segments);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ var db = require('../databases/databases.js').db;
|
||||
|
||||
module.exports = function getTopUsers (req, res) {
|
||||
let sortType = req.query.sortType;
|
||||
let categoryStatsEnabled = req.query.categoryStats;
|
||||
|
||||
if (sortType == undefined) {
|
||||
//invalid request
|
||||
@@ -27,11 +28,27 @@ module.exports = function getTopUsers (req, res) {
|
||||
let viewCounts = [];
|
||||
let totalSubmissions = [];
|
||||
let minutesSaved = [];
|
||||
let categoryStats = categoryStatsEnabled ? [] : undefined;
|
||||
|
||||
let additionalFields = '';
|
||||
if (categoryStatsEnabled) {
|
||||
additionalFields += "SUM(CASE WHEN category = 'sponsor' THEN 1 ELSE 0 END) as categorySponsor, " +
|
||||
"SUM(CASE WHEN category = 'intro' THEN 1 ELSE 0 END) as categorySumIntro, " +
|
||||
"SUM(CASE WHEN category = 'outro' THEN 1 ELSE 0 END) as categorySumOutro, " +
|
||||
"SUM(CASE WHEN category = 'interaction' THEN 1 ELSE 0 END) as categorySumInteraction, " +
|
||||
"SUM(CASE WHEN category = 'selfpromo' THEN 1 ELSE 0 END) as categorySelfpromo, " +
|
||||
"SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as categoryMusicOfftopic, ";
|
||||
}
|
||||
|
||||
let rows = db.prepare("SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
|
||||
let rows = db.prepare('all', "SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
|
||||
"SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " +
|
||||
"SUM(votes) as userVotes, " +
|
||||
additionalFields +
|
||||
"IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " +
|
||||
"WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 GROUP BY IFNULL(userName, sponsorTimes.userID) ORDER BY " + sortBy + " DESC LIMIT 100").all();
|
||||
"LEFT JOIN privateDB.shadowBannedUsers ON sponsorTimes.userID=privateDB.shadowBannedUsers.userID " +
|
||||
"WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 AND privateDB.shadowBannedUsers.userID IS NULL " +
|
||||
"GROUP BY IFNULL(userName, sponsorTimes.userID) HAVING userVotes > 50 " +
|
||||
"ORDER BY " + sortBy + " DESC LIMIT 100", []);
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
userNames[i] = rows[i].userName;
|
||||
@@ -39,13 +56,24 @@ module.exports = function getTopUsers (req, res) {
|
||||
viewCounts[i] = rows[i].viewCount;
|
||||
totalSubmissions[i] = rows[i].totalSubmissions;
|
||||
minutesSaved[i] = rows[i].minutesSaved;
|
||||
if (categoryStatsEnabled) {
|
||||
categoryStats[i] = [
|
||||
rows[i].categorySponsor,
|
||||
rows[i].categorySumIntro,
|
||||
rows[i].categorySumOutro,
|
||||
rows[i].categorySumInteraction,
|
||||
rows[i].categorySelfpromo,
|
||||
rows[i].categoryMusicOfftopic,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
//send this result
|
||||
res.send({
|
||||
userNames: userNames,
|
||||
viewCounts: viewCounts,
|
||||
totalSubmissions: totalSubmissions,
|
||||
minutesSaved: minutesSaved
|
||||
userNames,
|
||||
viewCounts,
|
||||
totalSubmissions,
|
||||
minutesSaved,
|
||||
categoryStats
|
||||
});
|
||||
}
|
||||
@@ -8,8 +8,8 @@ var lastUserCountCheck = 0;
|
||||
|
||||
|
||||
module.exports = function getTotalStats (req, res) {
|
||||
let row = db.prepare("SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " +
|
||||
"SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1").get();
|
||||
let row = db.prepare('get', "SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " +
|
||||
"SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1 AND votes >= 0", []);
|
||||
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
const logger = require('../utils/logger.js');
|
||||
|
||||
module.exports = function getUsername (req, res) {
|
||||
let userID = req.query.userID;
|
||||
@@ -15,7 +16,7 @@ module.exports = function getUsername (req, res) {
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT userName FROM userNames WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]);
|
||||
|
||||
if (row !== undefined) {
|
||||
res.send({
|
||||
@@ -28,7 +29,7 @@ module.exports = function getUsername (req, res) {
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
|
||||
var logger = require('../utils/logger.js');
|
||||
module.exports = function getViewsForUser(req, res) {
|
||||
let userID = req.query.userID;
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = function getViewsForUser(req, res) {
|
||||
userID = getHash(userID);
|
||||
|
||||
try {
|
||||
let row = db.prepare("SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
//increase the view count by one
|
||||
if (row.viewCount != null) {
|
||||
@@ -25,7 +25,7 @@ module.exports = function getViewsForUser(req, res) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
var config = require('../config.js');
|
||||
|
||||
var postSkipSegments = require('./postSkipSegments.js');
|
||||
|
||||
module.exports = async function submitSponsorTimes(req, res) {
|
||||
|
||||
@@ -4,37 +4,20 @@ var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
var privateDB = databases.privateDB;
|
||||
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||
var logger = require('../utils/logger.js');
|
||||
var request = require('request');
|
||||
var isoDurations = require('iso8601-duration');
|
||||
|
||||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
// TODO: might need to be a util
|
||||
//returns true if the user is considered trustworthy
|
||||
//this happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
async function isUserTrustworthy(userID) {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
let totalSubmissionsRow = db.prepare("SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
let downvotedSubmissionsRow = db.prepare("SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)").get(userID);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
var isUserTrustworthy = require('../utils/isUserTrustworthy.js')
|
||||
|
||||
function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||
//check if they are a first time user
|
||||
//if so, send a notification to discord
|
||||
if (config.youtubeAPIKey !== null && config.discordFirstTimeSubmissionsWebhookURL !== null) {
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID);
|
||||
let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
// If it is a first time submission
|
||||
if (userSubmissionCountRow.submissionCount <= 1) {
|
||||
@@ -43,7 +26,7 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||
id: videoID
|
||||
}, function (err, data) {
|
||||
if (err || data.items.length === 0) {
|
||||
err && console.log(err);
|
||||
err && logger.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,13 +53,13 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||
}
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
console.log("Failed to send first time submission Discord hook.");
|
||||
console.log(JSON.stringify(err));
|
||||
console.log("\n");
|
||||
logger.error("Failed to send first time submission Discord hook.");
|
||||
logger.error(JSON.stringify(err));
|
||||
logger.error("\n");
|
||||
} else if (res && res.statusCode >= 400) {
|
||||
console.log("Error sending first time submission Discord hook");
|
||||
console.log(JSON.stringify(res));
|
||||
console.log("\n");
|
||||
logger.error("Error sending first time submission Discord hook");
|
||||
logger.error(JSON.stringify(res));
|
||||
logger.error("\n");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -86,9 +69,13 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) {
|
||||
|
||||
// callback: function(reject: "String containing reason the submission was rejected")
|
||||
// returns: string when an error, false otherwise
|
||||
async function autoModerateSubmission(videoID, segments) {
|
||||
|
||||
// Looks like this was broken for no defined youtube key - fixed but IMO we shouldn't return
|
||||
// false for a pass - it was confusing and lead to this bug - any use of this function in
|
||||
// the future could have the same problem.
|
||||
async function autoModerateSubmission(submission, callback) {
|
||||
// Get the video information from the youtube API
|
||||
if (config.youtubeAPI !== null) {
|
||||
if (config.youtubeAPIKey !== null) {
|
||||
let {err, data} = await new Promise((resolve, reject) => {
|
||||
YouTubeAPI.videos.list({
|
||||
part: "contentDetails",
|
||||
@@ -119,7 +106,7 @@ async function autoModerateSubmission(videoID, segments) {
|
||||
|
||||
let neuralBlockURL = config.neuralBlockURL;
|
||||
if (!neuralBlockURL) return false;
|
||||
|
||||
|
||||
let overlap = true;
|
||||
|
||||
let response = await fetch(neuralBlockURL + "/api/getSponsorSegments?vid=" + videoID);
|
||||
@@ -142,7 +129,7 @@ async function autoModerateSubmission(videoID, segments) {
|
||||
if (!thisSegmentOverlaps){
|
||||
overlap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (overlap) {
|
||||
@@ -154,15 +141,31 @@ async function autoModerateSubmission(videoID, segments) {
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log("Skipped YouTube API");
|
||||
logger.debug("Skipped YouTube API");
|
||||
|
||||
// Can't moderate the submission without calling the youtube API
|
||||
// so allow by default.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function proxySubmission(req) {
|
||||
request.post(config.proxySubmission + '/api/skipSegments?userID='+req.query.userID+'&videoID='+req.query.videoID, {json: req.body}, (err, result) => {
|
||||
if (config.mode === 'development') {
|
||||
if (!err) {
|
||||
logger.error('Proxy Submission: ' + result.statusCode + ' ('+result.body+')');
|
||||
} else {
|
||||
logger.debug("Proxy Submission: Failed to make call");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = async function postSkipSegments(req, res) {
|
||||
if (config.proxySubmission) {
|
||||
proxySubmission(req);
|
||||
}
|
||||
|
||||
let videoID = req.query.videoID || req.body.videoID;
|
||||
let userID = req.query.userID || req.body.userID;
|
||||
|
||||
@@ -203,16 +206,22 @@ module.exports = async function postSkipSegments(req, res) {
|
||||
let startTime = parseFloat(segments[i].segment[0]);
|
||||
let endTime = parseFloat(segments[i].segment[1]);
|
||||
|
||||
if (Math.abs(startTime - endTime) < 1 || isNaN(startTime) || isNaN(endTime)
|
||||
|| startTime === Infinity || endTime === Infinity || startTime > endTime) {
|
||||
if (isNaN(startTime) || isNaN(endTime)
|
||||
|| startTime === Infinity || endTime === Infinity || startTime < 0 || startTime >= endTime) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (segments[i].category === "sponsor" && Math.abs(startTime - endTime) < 1) {
|
||||
// Too short
|
||||
res.status(400).send("Sponsors must be longer than 1 second long");
|
||||
return;
|
||||
}
|
||||
|
||||
//check if this info has already been submitted before
|
||||
let duplicateCheck2Row = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
||||
"and endTime = ? and category = ? and videoID = ?").get(startTime, endTime, segments[i].category, videoID);
|
||||
let duplicateCheck2Row = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
|
||||
"and endTime = ? and category = ? and videoID = ?", [startTime, endTime, segments[i].category, videoID]);
|
||||
if (duplicateCheck2Row.count > 0) {
|
||||
res.sendStatus(409);
|
||||
return;
|
||||
@@ -229,33 +238,42 @@ module.exports = async function postSkipSegments(req, res) {
|
||||
}
|
||||
|
||||
try {
|
||||
//check if this user is on the vip list
|
||||
let vipRow = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]);
|
||||
|
||||
//get current time
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let yesterday = timeSubmitted - 86400000;
|
||||
|
||||
//check to see if this ip has submitted too many sponsors today
|
||||
let rateLimitCheckRow = privateDB.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday]);
|
||||
// Disable IP ratelimiting for now
|
||||
if (false) {
|
||||
//check to see if this ip has submitted too many sponsors today
|
||||
let rateLimitCheckRow = privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]);
|
||||
|
||||
if (rateLimitCheckRow.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
res.sendStatus(429);
|
||||
if (rateLimitCheckRow.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
res.sendStatus(429);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//check to see if the user has already submitted sponsors for this video
|
||||
let duplicateCheckRow = db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID]);
|
||||
// Disable max submissions for now
|
||||
if (false) {
|
||||
//check to see if the user has already submitted sponsors for this video
|
||||
let duplicateCheckRow = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]);
|
||||
|
||||
if (duplicateCheckRow.count >= 8) {
|
||||
//too many sponsors for the same video from the same user
|
||||
res.sendStatus(429);
|
||||
if (duplicateCheckRow.count >= 16) {
|
||||
//too many sponsors for the same video from the same user
|
||||
res.sendStatus(429);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//check to see if this user is shadowbanned
|
||||
let shadowBanRow = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
let shadowBanRow = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
let shadowBanned = shadowBanRow.userCount;
|
||||
|
||||
@@ -267,7 +285,7 @@ module.exports = async function postSkipSegments(req, res) {
|
||||
let startingVotes = 0;
|
||||
if (isVIP) {
|
||||
//this user is a vip, start them at a higher approval rating
|
||||
startingVotes = 10;
|
||||
startingVotes = 10000;
|
||||
}
|
||||
|
||||
for (const segmentInfo of segments) {
|
||||
@@ -278,17 +296,17 @@ module.exports = async function postSkipSegments(req, res) {
|
||||
segmentInfo.segment[1] + segmentInfo.category + userID, 1);
|
||||
|
||||
try {
|
||||
db.prepare("INSERT INTO sponsorTimes " +
|
||||
db.prepare('run', "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);
|
||||
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [videoID, segmentInfo.segment[0],
|
||||
segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned]);
|
||||
|
||||
//add to private db as well
|
||||
privateDB.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?)").run(videoID, hashedIP, timeSubmitted);
|
||||
privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]);
|
||||
} catch (err) {
|
||||
//a DB change probably occurred
|
||||
res.sendStatus(502);
|
||||
console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
|
||||
logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
|
||||
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
|
||||
|
||||
return;
|
||||
@@ -298,7 +316,7 @@ module.exports = async function postSkipSegments(req, res) {
|
||||
sendDiscordNotification(userID, videoID, UUID, segmentInfo);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err);
|
||||
|
||||
res.sendStatus(500);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ var config = require('../config.js');
|
||||
|
||||
var db = require('../databases/databases.js').db;
|
||||
var getHash = require('../utils/getHash.js');
|
||||
const logger = require('../utils/logger.js');
|
||||
|
||||
|
||||
module.exports = function setUsername(req, res) {
|
||||
@@ -17,6 +18,12 @@ module.exports = function setUsername(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userName.includes("discord")) {
|
||||
// Don't allow
|
||||
res.sendStatus(200);
|
||||
return;
|
||||
}
|
||||
|
||||
if (adminUserIDInput != undefined) {
|
||||
//this is the admin controlling the other users account, don't hash the controling account's ID
|
||||
adminUserIDInput = getHash(adminUserIDInput);
|
||||
@@ -33,19 +40,19 @@ module.exports = function setUsername(req, res) {
|
||||
|
||||
try {
|
||||
//check if username is already set
|
||||
let row = db.prepare("SELECT count(*) as count FROM userNames WHERE userID = ?").get(userID);
|
||||
let row = db.prepare('get', "SELECT count(*) as count FROM userNames WHERE userID = ?", [userID]);
|
||||
|
||||
if (row.count > 0) {
|
||||
//already exists, update this row
|
||||
db.prepare("UPDATE userNames SET userName = ? WHERE userID = ?").run(userName, userID);
|
||||
db.prepare('run', "UPDATE userNames SET userName = ? WHERE userID = ?", [userName, userID]);
|
||||
} else {
|
||||
//add to the db
|
||||
db.prepare("INSERT INTO userNames VALUES(?, ?)").run(userID, userName);
|
||||
db.prepare('run', "INSERT INTO userNames VALUES(?, ?)", [userID, userName]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error(err);
|
||||
res.sendStatus(500);
|
||||
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,7 @@ var getHash = require('../utils/getHash.js');
|
||||
|
||||
module.exports = async function shadowBanUser(req, res) {
|
||||
let userID = req.query.userID;
|
||||
let hashedIP = req.query.hashedIP;
|
||||
let adminUserIDInput = req.query.adminUserID;
|
||||
|
||||
let enabled = req.query.enabled;
|
||||
@@ -18,14 +19,9 @@ module.exports = async function shadowBanUser(req, res) {
|
||||
}
|
||||
|
||||
//if enabled is false and the old submissions should be made visible again
|
||||
let unHideOldSubmissions = req.query.unHideOldSubmissions;
|
||||
if (enabled === undefined){
|
||||
unHideOldSubmissions = true;
|
||||
} else {
|
||||
unHideOldSubmissions = unHideOldSubmissions === "true";
|
||||
}
|
||||
let unHideOldSubmissions = req.query.unHideOldSubmissions !== "false";
|
||||
|
||||
if (adminUserIDInput == undefined || userID == undefined) {
|
||||
if (adminUserIDInput == undefined || (userID == undefined && hashedIP == undefined)) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
@@ -40,25 +36,57 @@ module.exports = async function shadowBanUser(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
//check to see if this user is already shadowbanned
|
||||
let row = privateDB.prepare("SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?").get(userID);
|
||||
if (userID) {
|
||||
//check to see if this user is already shadowbanned
|
||||
let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the shadow ban list
|
||||
if (enabled && row.userCount == 0) {
|
||||
//add them to the shadow ban list
|
||||
|
||||
//add it to the table
|
||||
privateDB.prepare("INSERT INTO shadowBannedUsers VALUES(?)").run(userID);
|
||||
//add it to the table
|
||||
privateDB.prepare('run', "INSERT INTO shadowBannedUsers VALUES(?)", [userID]);
|
||||
|
||||
//find all previous submissions and hide them
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?").run(userID);
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
privateDB.prepare("DELETE FROM shadowBannedUsers WHERE userID = ?").run(userID);
|
||||
//find all previous submissions and hide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?", [userID]);
|
||||
}
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
//remove them from the shadow ban list
|
||||
privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
//find all previous submissions and unhide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare("UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?").run(userID);
|
||||
}
|
||||
//find all previous submissions and unhide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]);
|
||||
}
|
||||
}
|
||||
} else if (hashedIP) {
|
||||
//check to see if this user is already shadowbanned
|
||||
// let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedIPs WHERE hashedIP = ?", [hashedIP]);
|
||||
|
||||
// if (enabled && row.userCount == 0) {
|
||||
if (enabled) {
|
||||
//add them to the shadow ban list
|
||||
|
||||
//add it to the table
|
||||
// privateDB.prepare('run', "INSERT INTO shadowBannedIPs VALUES(?)", [hashedIP]);
|
||||
|
||||
|
||||
|
||||
//find all previous submissions and hide them
|
||||
if (unHideOldSubmissions) {
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE timeSubmitted IN " +
|
||||
"(SELECT privateDB.timeSubmitted FROM sponsorTimes LEFT JOIN privateDB.sponsorTimes as privateDB ON sponsorTimes.timeSubmitted=privateDB.timeSubmitted " +
|
||||
"WHERE privateDB.hashedIP = ?)", [hashedIP]);
|
||||
}
|
||||
} else if (!enabled && row.userCount > 0) {
|
||||
// //remove them from the shadow ban list
|
||||
// privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]);
|
||||
|
||||
// //find all previous submissions and unhide them
|
||||
// if (unHideOldSubmissions) {
|
||||
// db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = function viewedVideoSponsorTime(req, res) {
|
||||
}
|
||||
|
||||
//up the view count by one
|
||||
db.prepare("UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?").run(UUID);
|
||||
db.prepare('run', "UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?", [UUID]);
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,30 @@ var config = require('../config.js');
|
||||
var getHash = require('../utils/getHash.js');
|
||||
var getIP = require('../utils/getIP.js');
|
||||
var getFormattedTime = require('../utils/getFormattedTime.js');
|
||||
var isUserTrustworthy = require('../utils/isUserTrustworthy.js')
|
||||
|
||||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
var privateDB = databases.privateDB;
|
||||
var YouTubeAPI = require('../utils/youtubeAPI.js');
|
||||
var request = require('request');
|
||||
const logger = require('../utils/logger.js');
|
||||
|
||||
function getVoteAuthor(submissionCount, isVIP, isOwnSubmission) {
|
||||
if (submissionCount === 0) {
|
||||
return "Report by New User";
|
||||
} else if (isVIP) {
|
||||
return "Report by VIP User";
|
||||
} else if (isOwnSubmission) {
|
||||
return "Report by Submitter";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
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);
|
||||
let previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]);
|
||||
|
||||
if (previousVoteInfo > 0 && previousVoteInfo.category === category) {
|
||||
// Double vote, ignore
|
||||
@@ -21,32 +35,38 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentCategory = db.prepare('get', "select category from sponsorTimes where UUID = ?", [UUID]);
|
||||
if (!currentCategory) {
|
||||
// Submission doesn't exist
|
||||
res.status("400").send("Submission doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let voteAmount = isVIP ? 500 : 1;
|
||||
|
||||
// Add the vote
|
||||
if (db.prepare("select count(*) as count from categoryVotes where UUID = ? and category = ?").get(UUID, category).count > 0) {
|
||||
if (db.prepare('get', "select count(*) as count from categoryVotes where UUID = ? and category = ?", [UUID, category]).count > 0) {
|
||||
// Update the already existing db entry
|
||||
db.prepare("update categoryVotes set votes = votes + ? where UUID = ? and category = ?").run(voteAmount, UUID, category);
|
||||
db.prepare('run', "update categoryVotes set votes = votes + ? where UUID = ? and category = ?", [voteAmount, UUID, category]);
|
||||
} else {
|
||||
// Add a db entry
|
||||
db.prepare("insert into categoryVotes (UUID, category, votes) values (?, ?, ?)").run(UUID, category, voteAmount);
|
||||
db.prepare('run', "insert into categoryVotes (UUID, category, votes) values (?, ?, ?)", [UUID, category, voteAmount]);
|
||||
}
|
||||
|
||||
// Add the info into the private db
|
||||
if (previousVoteInfo > 0) {
|
||||
// Reverse the previous vote
|
||||
db.prepare("update categoryVotes set votes -= 1 where UUID = ? and category = ?").run(UUID, previousVoteInfo.category);
|
||||
db.prepare('run', "update categoryVotes set votes -= 1 where UUID = ? and category = ?", [UUID, previousVoteInfo.category]);
|
||||
|
||||
privateDB.prepare("update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?").run(category, timeSubmitted, hashedIP)
|
||||
privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ?", [category, timeSubmitted, hashedIP]);
|
||||
} else {
|
||||
privateDB.prepare("insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)").run(UUID, userID, hashedIP, category, timeSubmitted);
|
||||
privateDB.prepare('run', "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [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);
|
||||
// See if the submissions category is ready to change
|
||||
let currentCategoryInfo = db.prepare('get', "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]);
|
||||
|
||||
// Change this value from 1 in the future to make it harder to change categories
|
||||
// Done this way without ORs incase the value is zero
|
||||
@@ -58,13 +78,13 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) {
|
||||
// VIPs change it every time
|
||||
if (nextCategoryCount - currentCategoryCount >= 0 || isVIP) {
|
||||
// Replace the category
|
||||
db.prepare("update sponsorTimes set category = ? where UUID = ?").run(category, UUID);
|
||||
db.prepare('run', "update sponsorTimes set category = ? where UUID = ?", [category, UUID]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
|
||||
module.exports = async function voteOnSponsorTime(req, res) {
|
||||
async function voteOnSponsorTime(req, res) {
|
||||
let UUID = req.query.UUID;
|
||||
let userID = req.query.userID;
|
||||
let type = req.query.type;
|
||||
@@ -87,12 +107,25 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
let hashedIP = getHash(ip + config.globalSalt);
|
||||
|
||||
//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;
|
||||
let isVIP = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [nonAnonUserID]).userCount > 0;
|
||||
|
||||
//check if user voting on own submission
|
||||
let isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined;
|
||||
|
||||
if (type === undefined && category !== undefined) {
|
||||
return categoryVote(UUID, userID, isVIP, category, hashedIP, res);
|
||||
}
|
||||
|
||||
if (type == 1 && !isVIP && !isOwnSubmission) {
|
||||
// Check if upvoting hidden segment
|
||||
let voteInfo = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
|
||||
if (voteInfo && voteInfo.votes <= -2) {
|
||||
res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let voteTypes = {
|
||||
normal: 0,
|
||||
incorrect: 1
|
||||
@@ -102,7 +135,7 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
|
||||
try {
|
||||
//check if vote has already happened
|
||||
let votesRow = privateDB.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID);
|
||||
let votesRow = privateDB.prepare('get', "SELECT type FROM votes WHERE userID = ? AND UUID = ?", [userID, UUID]);
|
||||
|
||||
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future
|
||||
let incrementAmount = 0;
|
||||
@@ -114,6 +147,9 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
} else if (type == 0 || type == 10) {
|
||||
//downvote
|
||||
incrementAmount = -1;
|
||||
} else if (type == 20) {
|
||||
//undo/cancel vote
|
||||
incrementAmount = 0;
|
||||
} else {
|
||||
//unrecongnised type of vote
|
||||
res.sendStatus(400);
|
||||
@@ -142,20 +178,16 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
}
|
||||
|
||||
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
|
||||
let row = db.prepare("SELECT votes, views FROM sponsorTimes WHERE UUID = ?").get(UUID);
|
||||
let row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
|
||||
if (voteTypeEnum === voteTypes.normal) {
|
||||
if (isVIP && incrementAmount < 0) {
|
||||
if ((isVIP || isOwnSubmission) && 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) {
|
||||
if (isVIP || isOwnSubmission) {
|
||||
//this user is a vip and a downvote
|
||||
incrementAmount = 500 * incrementAmount;
|
||||
type = incrementAmount < 0 ? 12 : 13;
|
||||
@@ -165,13 +197,13 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
// Send discord message
|
||||
if (incrementAmount < 0) {
|
||||
// Get video ID
|
||||
let submissionInfoRow = db.prepare("SELECT s.videoID, s.userID, s.startTime, s.endTime, u.userName, " +
|
||||
let submissionInfoRow = db.prepare('get', "SELECT s.videoID, s.userID, s.startTime, s.endTime, s.category, u.userName, " +
|
||||
"(select count(1) from sponsorTimes where userID = s.userID) count, " +
|
||||
"(select count(1) from sponsorTimes where userID = s.userID and votes <= -2) disregarded " +
|
||||
"FROM sponsorTimes s left join userNames u on s.userID = u.userID where s.UUID=?"
|
||||
).get(UUID);
|
||||
"FROM sponsorTimes s left join userNames u on s.userID = u.userID where s.UUID=?",
|
||||
[UUID]);
|
||||
|
||||
let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(nonAnonUserID);
|
||||
let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]);
|
||||
|
||||
if (submissionInfoRow !== undefined && userSubmissionCountRow != undefined) {
|
||||
let webhookURL = null;
|
||||
@@ -187,7 +219,7 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
id: submissionInfoRow.videoID
|
||||
}, function (err, data) {
|
||||
if (err || data.items.length === 0) {
|
||||
err && console.log(err);
|
||||
err && logger.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,6 +231,7 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
+ "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2),
|
||||
"description": "**" + row.votes + " Votes Prior | " + (row.votes + incrementAmount - oldIncrementAmount) + " Votes Now | " + row.views
|
||||
+ " Views**\n\n**Submission ID:** " + UUID
|
||||
+ "\n**Category:** " + submissionInfoRow.category
|
||||
+ "\n\n**Submitted by:** "+submissionInfoRow.userName+"\n " + submissionInfoRow.userID
|
||||
+ "\n\n**Total User Submissions:** "+submissionInfoRow.count
|
||||
+ "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded
|
||||
@@ -206,7 +239,7 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime),
|
||||
"color": 10813440,
|
||||
"author": {
|
||||
"name": userSubmissionCountRow.submissionCount === 0 ? "Report by New User" : (isVIP ? "Report by VIP User" : "")
|
||||
"name": getVoteAuthor(userSubmissionCountRow.submissionCount, isVIP, isOwnSubmission)
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||
@@ -215,13 +248,13 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
}
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
console.log("Failed to send reported submission Discord hook.");
|
||||
console.log(JSON.stringify(err));
|
||||
console.log("\n");
|
||||
logger.error("Failed to send reported submission Discord hook.");
|
||||
logger.error(JSON.stringify(err));
|
||||
logger.error("\n");
|
||||
} else if (res && res.statusCode >= 400) {
|
||||
console.log("Error sending reported submission Discord hook");
|
||||
console.log(JSON.stringify(res));
|
||||
console.log("\n");
|
||||
logger.error("Error sending reported submission Discord hook");
|
||||
logger.error(JSON.stringify(res));
|
||||
logger.error("\n");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -229,47 +262,68 @@ module.exports = async function voteOnSponsorTime(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
//update the votes table
|
||||
if (votesRow != undefined) {
|
||||
privateDB.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
|
||||
} else {
|
||||
privateDB.prepare("INSERT INTO votes VALUES(?, ?, ?, ?)").run(UUID, userID, hashedIP, type);
|
||||
}
|
||||
// Only change the database if they have made a submission before and haven't voted recently
|
||||
let ableToVote = isVIP
|
||||
|| (db.prepare("get", "SELECT userID FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]) !== undefined
|
||||
&& privateDB.prepare("get", "SELECT userID FROM shadowBannedUsers WHERE userID = ?", [nonAnonUserID]) === undefined
|
||||
&& privateDB.prepare("get", "SELECT UUID FROM votes WHERE UUID = ? AND hashedIP = ? AND userID != ?", [UUID, hashedIP, userID]) === undefined);
|
||||
|
||||
let columnName = "";
|
||||
if (voteTypeEnum === voteTypes.normal) {
|
||||
columnName = "votes";
|
||||
} else if (voteTypeEnum === voteTypes.incorrect) {
|
||||
columnName = "incorrectVotes";
|
||||
}
|
||||
if (ableToVote) {
|
||||
//update the votes table
|
||||
if (votesRow != undefined) {
|
||||
privateDB.prepare('run', "UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?", [type, userID, UUID]);
|
||||
} else {
|
||||
privateDB.prepare('run', "INSERT INTO votes VALUES(?, ?, ?, ?)", [UUID, userID, hashedIP, type]);
|
||||
}
|
||||
|
||||
//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);
|
||||
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 && 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;
|
||||
//update the vote count on this sponsorTime
|
||||
//oldIncrementAmount will be zero is row is null
|
||||
db.prepare('run', "UPDATE sponsorTimes SET " + columnName + " = " + columnName + " + ? WHERE UUID = ?", [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 submissionUserIDInfo = db.prepare('get', "SELECT userID FROM sponsorTimes WHERE UUID = ?", [UUID]);
|
||||
if (!submissionUserIDInfo) {
|
||||
// They are voting on a non-existent submission
|
||||
res.status(400).send("Voting on a non-existent submission");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
|
||||
//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)
|
||||
let submissionUserID = submissionUserIDInfo.userID;
|
||||
|
||||
//check if any submissions are hidden
|
||||
let hiddenSubmissionsRow = db.prepare('get', "SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0", [submissionUserID]);
|
||||
|
||||
if (hiddenSubmissionsRow.hiddenSubmissions > 0) {
|
||||
//see if some of this users submissions should be visible again
|
||||
|
||||
if (await isUserTrustworthy(submissionUserID)) {
|
||||
//they are trustworthy again, show 2 of their submissions again, if there are two to show
|
||||
db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)", [submissionUserID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//added to db
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err);
|
||||
|
||||
res.status(500).json({error: 'Internal error creating segment vote'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
voteOnSponsorTime,
|
||||
endpoint: function (req, res) {
|
||||
voteOnSponsorTime(req, res);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user