Switch to case sensitive and get submitting + getting working

This commit is contained in:
Ajay Ramachandran
2021-03-04 20:23:05 -05:00
parent 1a66be8665
commit 2772a9dcc6
16 changed files with 87 additions and 87 deletions

View File

@@ -1,42 +1,42 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS vipUsers (
userID TEXT NOT NULL
CREATE TABLE IF NOT EXISTS "vipUsers" (
"userID" TEXT NOT NULL
);
COMMIT;
BEGIN TRANSACTION;
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 "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 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 "categoryVotes" (
"UUID" TEXT NOT NULL,
"category" TEXT NOT NULL,
"votes" INTEGER NOT NULL default 0
);
CREATE TABLE IF NOT EXISTS config (
key TEXT NOT NULL UNIQUE,
value TEXT NOT NULL
CREATE TABLE IF NOT EXISTS "config" (
"key" TEXT NOT NULL UNIQUE,
"value" TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS sponsorTimes_videoID on sponsorTimes(videoID);
CREATE INDEX IF NOT EXISTS sponsorTimes_UUID on sponsorTimes(UUID);
CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" on "sponsorTimes"("videoID");
CREATE INDEX IF NOT EXISTS "sponsorTimes_UUID" on "sponsorTimes"("UUID");
CREATE EXTENSION IF NOT EXISTS pgcrypto; --!sqlite-ignore

View File

@@ -14,9 +14,9 @@ CREATE TABLE "sqlb_temp_table_1" (
"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;
INSERT INTO sqlb_temp_table_1 SELECT "videoID","startTime","endTime","votes",'1',"UUID","userID","timeSubmitted","views","category","shadowHidden" FROM "sponsorTimes";
DROP TABLE sponsorTimes;
DROP TABLE "sponsorTimes";
ALTER TABLE sqlb_temp_table_1 RENAME TO "sponsorTimes";
/* Add version to config */

View File

@@ -8,6 +8,6 @@ CREATE TABLE "noSegments" (
);
/* Add version to config */
UPDATE config SET value = 2 WHERE key = 'version';
UPDATE "config" SET value = 2 WHERE key = 'version';
COMMIT;

View File

@@ -2,12 +2,12 @@ BEGIN TRANSACTION;
/* hash upgrade test sha256('vid') = '1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663' */
/* Add hash field */
ALTER TABLE sponsorTimes ADD hashedVideoID TEXT NOT NULL default '';
UPDATE sponsorTimes SET hashedVideoID = sha256(videoID);
ALTER TABLE "sponsorTimes" ADD "hashedVideoID" TEXT NOT NULL default '';
UPDATE "sponsorTimes" SET "hashedVideoID" = sha256("videoID");
CREATE INDEX IF NOT EXISTS sponsorTimes_hashedVideoID on sponsorTimes(hashedVideoID);
CREATE INDEX IF NOT EXISTS "sponsorTimes_hashedVideoID" on "sponsorTimes"("hashedVideoID");
/* Bump version in config */
UPDATE config SET value = 3 WHERE key = 'version';
UPDATE "config" SET value = 3 WHERE key = 'version';
COMMIT;

View File

@@ -2,11 +2,11 @@ BEGIN TRANSACTION;
/* Create warnings table */
CREATE TABLE "warnings" (
userID TEXT NOT NULL,
issueTime INTEGER NOT NULL,
issuerUserID TEXT NOT NULL
"userID" TEXT NOT NULL,
"issueTime" INTEGER NOT NULL,
"issuerUserID" TEXT NOT NULL
);
UPDATE config SET value = 4 WHERE key = 'version';
UPDATE "config" SET value = 4 WHERE key = 'version';
COMMIT;

View File

@@ -2,16 +2,16 @@ BEGIN TRANSACTION;
/* Add enabled field */
CREATE TABLE "sqlb_temp_table_5" (
userID TEXT NOT NULL,
issueTime INTEGER NOT NULL,
issuerUserID TEXT NOT NULL,
"userID" TEXT NOT NULL,
"issueTime" INTEGER NOT NULL,
"issuerUserID" TEXT NOT NULL,
enabled INTEGER NOT NULL
);
INSERT INTO sqlb_temp_table_5 SELECT userID,issueTime,issuerUserID,1 FROM warnings;
INSERT INTO sqlb_temp_table_5 SELECT "userID","issueTime","issuerUserID",1 FROM "warnings";
DROP TABLE warnings;
ALTER TABLE sqlb_temp_table_5 RENAME TO "warnings";;
UPDATE config SET value = 5 WHERE key = 'version';
UPDATE "config" SET value = 5 WHERE key = 'version';
COMMIT;

View File

@@ -17,11 +17,11 @@ CREATE TABLE "sqlb_temp_table_6" (
"hashedVideoID" TEXT NOT NULL default ''
);
INSERT INTO sqlb_temp_table_6 SELECT videoID,startTime,endTime,votes,'0',incorrectVotes,UUID,userID,timeSubmitted,views,category,shadowHidden,hashedVideoID FROM sponsorTimes;
INSERT INTO sqlb_temp_table_6 SELECT "videoID","startTime","endTime","votes",'0',"incorrectVotes","UUID","userID","timeSubmitted","views","category","shadowHidden","hashedVideoID" FROM "sponsorTimes";
DROP TABLE sponsorTimes;
DROP TABLE "sponsorTimes";
ALTER TABLE sqlb_temp_table_6 RENAME TO "sponsorTimes";
UPDATE config SET value = 6 WHERE key = 'version';
UPDATE "config" SET value = 6 WHERE key = 'version';
COMMIT;

View File

@@ -67,9 +67,9 @@ export class Postgres implements IDatabase {
}
private processUpgradeQuery(query: string): string {
let result = query.toLocaleLowerCase();
let result = query;
result = result.replace(/sha256\((.*?)\)/gm, "digest($1, 'sha256')");
result = result.replace(/integer/gm, "numeric");
result = result.replace(/integer/gmi, "NUMERIC");
return result;
}

View File

@@ -27,14 +27,14 @@ export async function addUserAsVIP(req: Request, res: Response) {
}
//check to see if this user is already a vip
const row = await db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]);
const row = await db.prepare('get', 'SELECT count(*) as "userCount" FROM vipUsers WHERE userID = ?', [userID]);
if (enabled && row.userCount == 0) {
//add them to the vip list
await db.prepare('run', "INSERT INTO vipUsers VALUES(?)", [userID]);
await db.prepare('run', 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
} else if (!enabled && row.userCount > 0) {
//remove them from the shadow ban list
await db.prepare('run', "DELETE FROM vipUsers WHERE userID = ?", [userID]);
await db.prepare('run', 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
}
res.sendStatus(200);

View File

@@ -33,12 +33,12 @@ export async function deleteNoSegments(req: Request, res: Response) {
return;
}
const entries = (await db.prepare("all", 'SELECT * FROM noSegments WHERE videoID = ?', [videoID])).filter((entry: any) => {
const entries = (await db.prepare("all", 'SELECT * FROM "noSegments" WHERE "videoID" = ?', [videoID])).filter((entry: any) => {
return (categories.indexOf(entry.category) !== -1);
});
for (const entry of entries) {
await db.prepare('run', 'DELETE FROM noSegments WHERE videoID = ? AND category = ?', [videoID, entry.category]);
await db.prepare('run', 'DELETE FROM "noSegments" WHERE "videoID" = ? AND "category" = ?', [videoID, entry.category]);
}
res.status(200).json({message: 'Removed no segments entrys for video ' + videoID});

View File

@@ -2,7 +2,7 @@ import {db} from '../databases/databases';
import {Request, Response} from 'express';
export async function getDaysSavedFormatted(req: Request, res: Response) {
let row = await db.prepare('get', "SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1", []);
let row = await db.prepare('get', 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []);
if (row !== undefined) {
//send this result

View File

@@ -15,7 +15,7 @@ export async function getSavedTimeForUser(req: Request, res: Response) {
userID = getHash(userID);
try {
let row = await db.prepare("get", "SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ", [userID]);
let row = await 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({

View File

@@ -24,7 +24,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category:
}
if (cache.shadowHiddenSegmentIPs[videoID] === undefined) {
cache.shadowHiddenSegmentIPs[videoID] = await privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]) as { hashedIP: HashedIP }[];
cache.shadowHiddenSegmentIPs[videoID] = await privateDB.prepare('all', 'SELECT "hashedIP" FROM "sponsorTimes" WHERE "videoID" = ?', [videoID]) as { hashedIP: HashedIP }[];
}
//if this isn't their ip, don't send it to them
@@ -57,8 +57,8 @@ async function getSegmentsByVideoID(req: Request, videoID: string, categories: C
const segmentsByCategory: SBRecord<Category, DBSegment[]> = (await db
.prepare(
'all',
`SELECT startTime, endTime, votes, locked, UUID, category, shadowHidden FROM sponsorTimes
WHERE videoID = ? AND category IN (${categories.map((c) => "'" + c + "'")}) ORDER BY startTime`,
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "category", "shadowHidden" FROM "sponsorTimes"
WHERE "videoID" = ? AND "category" IN (${categories.map((c) => "'" + c + "'")}) ORDER BY "startTime"`,
[videoID]
)).reduce((acc: SBRecord<Category, DBSegment[]>, segment: DBSegment) => {
acc[segment.category] = acc[segment.category] || [];
@@ -97,8 +97,8 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
const segmentPerVideoID: SegmentWithHashPerVideoID = (await db
.prepare(
'all',
`SELECT videoID, startTime, endTime, votes, locked, UUID, category, shadowHidden, hashedVideoID FROM sponsorTimes
WHERE hashedVideoID LIKE ? AND category IN (${categories.map((c) => "'" + c + "'")}) ORDER BY startTime`,
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "category", "shadowHidden", "hashedVideoID" FROM "sponsorTimes"
WHERE "hashedVideoID" LIKE ? AND "category" IN (${categories.map((c) => "'" + c + "'")}) ORDER BY "startTime"`,
[hashedVideoIDPrefix + '%']
)).reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => {
acc[segment.videoID] = acc[segment.videoID] || {

View File

@@ -23,15 +23,15 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled: boole
"SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as categoryMusicOfftopic, ";
}
const rows = await 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, " +
const rows = await 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 " +
"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 > 20 " +
"ORDER BY " + sortBy + " DESC LIMIT 100", []);
`IFNULL("userNames.userName", "sponsorTimes.userID") as "userName" FROM "sponsorTimes" LEFT JOIN "userNames" ON "sponsorTimes.userID"="userNames.userID"
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" > 20
ORDER BY "` + sortBy + `" DESC LIMIT 100`, []);
for (let i = 0; i < rows.length; i++) {
userNames[i] = rows[i].userName;

View File

@@ -16,7 +16,7 @@ import redis from '../utils/redis';
async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) {
const row = await db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]);
const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
const userName = row !== undefined ? row.userName : null;
const video = youtubeData.items[0];
@@ -47,7 +47,7 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st
async function sendWebhooks(userID: string, videoID: string, UUID: string, segmentInfo: any) {
if (config.youtubeAPIKey !== null) {
const userSubmissionCountRow = await db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]);
const userSubmissionCountRow = await db.prepare('get', `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
YouTubeAPI.listVideos(videoID, (err: any, data: any) => {
if (err || data.items.length === 0) {
@@ -106,10 +106,10 @@ async function sendWebhooks(userID: string, videoID: string, UUID: string, segme
}
async function sendWebhooksNB(userID: string, videoID: string, UUID: string, startTime: number, endTime: number, category: string, probability: number, ytData: any) {
const submissionInfoRow = await db.prepare('get', "SELECT " +
"(select count(1) from sponsorTimes where userID = ?) count, " +
"(select count(1) from sponsorTimes where userID = ? and votes <= -2) disregarded, " +
"coalesce((select userName FROM userNames WHERE userID = ?), ?) userName",
const submissionInfoRow = await db.prepare('get', `SELECT
(select count(1) from "sponsorTimes" where "userID" = ?) count,
(select count(1) from "sponsorTimes" where "userID" = ? and "votes" <= -2) disregarded,
coalesce((select "userName" FROM "userNames" WHERE "userID" = ?), ?) "userName"`,
[userID, userID, userID, userID]);
let submittedBy: string;
@@ -304,7 +304,7 @@ export async function postSkipSegments(req: Request, res: Response) {
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
const warningsCount = (await db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ? AND enabled = 1",
const warningsCount = (await db.prepare('get', `SELECT count(1) as count FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1`,
[userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))],
)).count;
@@ -312,12 +312,12 @@ export async function postSkipSegments(req: Request, res: Response) {
return res.status(403).send('Submission rejected due to a warning from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. Could you please send a message in Discord or Matrix so we can further help you?');
}
const noSegmentList = (await db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID])).map((list: any) => {
const noSegmentList = (await db.prepare('all', 'SELECT category from "noSegments" where "videoID" = ?', [videoID])).map((list: any) => {
return list.category;
});
//check if this user is on the vip list
const isVIP = (await db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID])).userCount > 0;
const isVIP = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?`, [userID])).userCount > 0;
const decreaseVotes = 0;
@@ -366,8 +366,8 @@ export async function postSkipSegments(req: Request, res: Response) {
}
//check if this info has already been submitted before
const duplicateCheck2Row = await db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " +
"and endTime = ? and category = ? and videoID = ?", [startTime, endTime, segments[i].category, videoID]);
const duplicateCheck2Row = await 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;
@@ -401,7 +401,7 @@ export async function postSkipSegments(req: Request, res: Response) {
// Disable IP ratelimiting for now
if (false) {
//check to see if this ip has submitted too many sponsors today
const rateLimitCheckRow = await privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]);
const rateLimitCheckRow = await 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
@@ -414,7 +414,7 @@ export async function postSkipSegments(req: Request, res: Response) {
// Disable max submissions for now
if (false) {
//check to see if the user has already submitted sponsors for this video
const duplicateCheckRow = await db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]);
const duplicateCheckRow = await db.prepare('get', `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ?`, [userID, videoID]);
if (duplicateCheckRow.count >= 16) {
//too many sponsors for the same video from the same user
@@ -425,7 +425,7 @@ export async function postSkipSegments(req: Request, res: Response) {
}
//check to see if this user is shadowbanned
const shadowBanRow = await privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]);
const shadowBanRow = await privateDB.prepare('get', `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]);
let shadowBanned = shadowBanRow.userCount;
@@ -445,7 +445,7 @@ export async function postSkipSegments(req: Request, res: Response) {
Logger.error("Error while submitting when connecting to YouTube API: " + err);
} else {
//get all segments for this video and user
const allSubmittedByUser = await db.prepare('all', "SELECT startTime, endTime FROM sponsorTimes WHERE userID = ? and videoID = ? and votes > -1", [userID, videoID]);
const allSubmittedByUser = await db.prepare('all', `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ? and "votes" > -1`, [userID, videoID]);
const allSegmentTimes = [];
if (allSubmittedByUser !== undefined) {
//add segments the user has previously submitted
@@ -489,15 +489,15 @@ export async function postSkipSegments(req: Request, res: Response) {
const startingLocked = isVIP ? 1 : 0;
try {
await db.prepare('run', "INSERT INTO sponsorTimes " +
"(videoID, startTime, endTime, votes, locked, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID)" +
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
await db.prepare('run', `INSERT INTO "sponsorTimes"
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "shadowHidden", "hashedVideoID")
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned, getHash(videoID, 1),
],
);
//add to private db as well
await privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]);
await privateDB.prepare('run', `INSERT INTO "sponsorTimes" VALUES(?, ?, ?)`, [videoID, hashedIP, timeSubmitted]);
// Clear redis cache for this video
redis.delAsync(skipSegmentsKey(videoID));

View File

@@ -6,11 +6,11 @@ import {db} from '../databases/databases';
*/
export async function isUserTrustworthy(userID: string): Promise<boolean> {
//check to see if this user how many submissions this user has submitted
const totalSubmissionsRow = await db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]);
const totalSubmissionsRow = await db.prepare('get', `SELECT count(*) as "totalSubmissions", sum(votes) as "voteSum" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
if (totalSubmissionsRow.totalSubmissions > 5) {
//check if they have a high downvote ratio
const downvotedSubmissionsRow = await db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]);
const downvotedSubmissionsRow = await db.prepare('get', `SELECT count(*) as "downvotedSubmissions" FROM "sponsorTimes" WHERE "userID" = ? AND (votes < 0 OR "shadowHidden" > 0)`, [userID]);
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);