diff --git a/.eslintrc.js b/.eslintrc.js index 08a23de..85b90cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,24 +1,29 @@ module.exports = { - env: { - browser: false, - es2021: true, - node: true, - }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - ], - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaVersion: 12, - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - rules: { - // TODO: Remove warn rules when not needed anymore - "no-self-assign": "off", - "semi": "warn", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off" - }, -}; \ No newline at end of file + env: { + browser: false, + es2021: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 12, + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + rules: { + // TODO: Remove warn rules when not needed anymore + "no-self-assign": "off", + "semi": "warn", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-explicit-any": "off", + "no-trailing-spaces": "warn", + "prefer-template": "warn", + "quotes": ["warn", "double", { "avoidEscape": true, "allowTemplateLiterals": true }], + "no-multiple-empty-lines": ["error", { max: 2, maxEOF: 0 }], + "indent": ["warn", 4], + }, +}; diff --git a/src/app.ts b/src/app.ts index b7d5ce6..827f767 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,42 +1,42 @@ -import express, {Request, RequestHandler, Response, Router} from 'express'; -import {config} from './config'; -import {oldSubmitSponsorTimes} from './routes/oldSubmitSponsorTimes'; -import {oldGetVideoSponsorTimes} from './routes/oldGetVideoSponsorTimes'; -import {postSegmentShift} from './routes/postSegmentShift'; -import {postWarning} from './routes/postWarning'; -import {getIsUserVIP} from './routes/getIsUserVIP'; -import {deleteLockCategoriesEndpoint} from './routes/deleteLockCategories'; -import {postLockCategories} from './routes/postLockCategories'; -import {getUserInfo} from './routes/getUserInfo'; -import {getDaysSavedFormatted} from './routes/getDaysSavedFormatted'; -import {getTotalStats} from './routes/getTotalStats'; -import {getTopUsers} from './routes/getTopUsers'; -import {getViewsForUser} from './routes/getViewsForUser'; -import {getSavedTimeForUser} from './routes/getSavedTimeForUser'; -import {addUserAsVIP} from './routes/addUserAsVIP'; -import {shadowBanUser} from './routes/shadowBanUser'; -import {getUsername} from './routes/getUsername'; -import {setUsername} from './routes/setUsername'; -import {viewedVideoSponsorTime} from './routes/viewedVideoSponsorTime'; -import {voteOnSponsorTime, getUserID as voteGetUserID} from './routes/voteOnSponsorTime'; -import {getSkipSegmentsByHash} from './routes/getSkipSegmentsByHash'; -import {postSkipSegments} from './routes/postSkipSegments'; -import {endpoint as getSkipSegments} from './routes/getSkipSegments'; -import {userCounter} from './middleware/userCounter'; -import {loggerMiddleware} from './middleware/logger'; -import {corsMiddleware} from './middleware/cors'; -import {apiCspMiddleware} from './middleware/apiCsp'; -import {rateLimitMiddleware} from './middleware/requestRateLimit'; -import dumpDatabase, {redirectLink} from './routes/dumpDatabase'; -import {endpoint as getSegmentInfo} from './routes/getSegmentInfo'; -import {postClearCache} from './routes/postClearCache'; -import { addUnlistedVideo } from './routes/addUnlistedVideo'; -import {postPurgeAllSegments} from './routes/postPurgeAllSegments'; -import {getUserID} from './routes/getUserID'; -import {getLockCategories} from './routes/getLockCategories'; -import {getLockCategoriesByHash} from './routes/getLockCategoriesByHash'; -import ExpressPromiseRouter from 'express-promise-router'; -import { Server } from 'http'; +import express, {Request, RequestHandler, Response, Router} from "express"; +import {config} from "./config"; +import {oldSubmitSponsorTimes} from "./routes/oldSubmitSponsorTimes"; +import {oldGetVideoSponsorTimes} from "./routes/oldGetVideoSponsorTimes"; +import {postSegmentShift} from "./routes/postSegmentShift"; +import {postWarning} from "./routes/postWarning"; +import {getIsUserVIP} from "./routes/getIsUserVIP"; +import {deleteLockCategoriesEndpoint} from "./routes/deleteLockCategories"; +import {postLockCategories} from "./routes/postLockCategories"; +import {getUserInfo} from "./routes/getUserInfo"; +import {getDaysSavedFormatted} from "./routes/getDaysSavedFormatted"; +import {getTotalStats} from "./routes/getTotalStats"; +import {getTopUsers} from "./routes/getTopUsers"; +import {getViewsForUser} from "./routes/getViewsForUser"; +import {getSavedTimeForUser} from "./routes/getSavedTimeForUser"; +import {addUserAsVIP} from "./routes/addUserAsVIP"; +import {shadowBanUser} from "./routes/shadowBanUser"; +import {getUsername} from "./routes/getUsername"; +import {setUsername} from "./routes/setUsername"; +import {viewedVideoSponsorTime} from "./routes/viewedVideoSponsorTime"; +import {voteOnSponsorTime, getUserID as voteGetUserID} from "./routes/voteOnSponsorTime"; +import {getSkipSegmentsByHash} from "./routes/getSkipSegmentsByHash"; +import {postSkipSegments} from "./routes/postSkipSegments"; +import {endpoint as getSkipSegments} from "./routes/getSkipSegments"; +import {userCounter} from "./middleware/userCounter"; +import {loggerMiddleware} from "./middleware/logger"; +import {corsMiddleware} from "./middleware/cors"; +import {apiCspMiddleware} from "./middleware/apiCsp"; +import {rateLimitMiddleware} from "./middleware/requestRateLimit"; +import dumpDatabase, {redirectLink} from "./routes/dumpDatabase"; +import {endpoint as getSegmentInfo} from "./routes/getSegmentInfo"; +import {postClearCache} from "./routes/postClearCache"; +import { addUnlistedVideo } from "./routes/addUnlistedVideo"; +import {postPurgeAllSegments} from "./routes/postPurgeAllSegments"; +import {getUserID} from "./routes/getUserID"; +import {getLockCategories} from "./routes/getLockCategories"; +import {getLockCategoriesByHash} from "./routes/getLockCategoriesByHash"; +import ExpressPromiseRouter from "express-promise-router"; +import { Server } from "http"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -54,10 +54,10 @@ export function createServer(callback: () => void): Server { if (config.userCounterURL) app.use(userCounter); // Setup pretty JSON - if (config.mode === "development") app.set('json spaces', 2); + if (config.mode === "development") app.set("json spaces", 2); // Set production mode - app.set('env', config.mode || 'production'); + app.set("env", config.mode || "production"); setupRoutes(router); @@ -74,102 +74,102 @@ function setupRoutes(router: Router) { } //add the get function - router.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes); + router.get("/api/getVideoSponsorTimes", oldGetVideoSponsorTimes); //add the oldpost function - router.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); - router.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); + router.get("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); + router.post("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); //add the skip segments functions - router.get('/api/skipSegments', getSkipSegments); - router.post('/api/skipSegments', postSkipSegments); + router.get("/api/skipSegments", getSkipSegments); + router.post("/api/skipSegments", postSkipSegments); // add the privacy protecting skip segments functions - router.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); + router.get("/api/skipSegments/:prefix", getSkipSegmentsByHash); //voting endpoint - router.get('/api/voteOnSponsorTime', ...voteEndpoints); - router.post('/api/voteOnSponsorTime', ...voteEndpoints); + router.get("/api/voteOnSponsorTime", ...voteEndpoints); + router.post("/api/voteOnSponsorTime", ...voteEndpoints); //Endpoint when a submission is skipped - router.get('/api/viewedVideoSponsorTime', ...viewEndpoints); - router.post('/api/viewedVideoSponsorTime', ...viewEndpoints); + router.get("/api/viewedVideoSponsorTime", ...viewEndpoints); + router.post("/api/viewedVideoSponsorTime", ...viewEndpoints); //To set your username for the stats view - router.post('/api/setUsername', setUsername); + router.post("/api/setUsername", setUsername); //get what username this user has - router.get('/api/getUsername', getUsername); + router.get("/api/getUsername", getUsername); //Endpoint used to hide a certain user's data - router.post('/api/shadowBanUser', shadowBanUser); + router.post("/api/shadowBanUser", shadowBanUser); //Endpoint used to make a user a VIP user with special privileges - router.post('/api/addUserAsVIP', addUserAsVIP); + router.post("/api/addUserAsVIP", addUserAsVIP); //Gets all the views added up for one userID //Useful to see how much one user has contributed - router.get('/api/getViewsForUser', getViewsForUser); + router.get("/api/getViewsForUser", getViewsForUser); //Gets all the saved time added up (views * sponsor length) for one userID //Useful to see how much one user has contributed //In minutes - router.get('/api/getSavedTimeForUser', getSavedTimeForUser); + router.get("/api/getSavedTimeForUser", getSavedTimeForUser); - router.get('/api/getTopUsers', getTopUsers); + router.get("/api/getTopUsers", getTopUsers); //send out totals //send the total submissions, total views and total minutes saved - router.get('/api/getTotalStats', getTotalStats); + router.get("/api/getTotalStats", getTotalStats); - router.get('/api/getUserInfo', getUserInfo); - router.get('/api/userInfo', getUserInfo); + router.get("/api/getUserInfo", getUserInfo); + router.get("/api/userInfo", getUserInfo); //send out a formatted time saved total - router.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); + router.get("/api/getDaysSavedFormatted", getDaysSavedFormatted); //submit video to lock categories - router.post('/api/noSegments', postLockCategories); - router.post('/api/lockCategories', postLockCategories); + router.post("/api/noSegments", postLockCategories); + router.post("/api/lockCategories", postLockCategories); - router.delete('/api/noSegments', deleteLockCategoriesEndpoint); - router.delete('/api/lockCategories', deleteLockCategoriesEndpoint); + router.delete("/api/noSegments", deleteLockCategoriesEndpoint); + router.delete("/api/lockCategories", deleteLockCategoriesEndpoint); //get if user is a vip - router.get('/api/isUserVIP', getIsUserVIP); + router.get("/api/isUserVIP", getIsUserVIP); //sent user a warning - router.post('/api/warnUser', postWarning); + router.post("/api/warnUser", postWarning); //get if user is a vip - router.post('/api/segmentShift', postSegmentShift); + router.post("/api/segmentShift", postSegmentShift); //get segment info - router.get('/api/segmentInfo', getSegmentInfo); + router.get("/api/segmentInfo", getSegmentInfo); //clear cache as VIP - router.post('/api/clearCache', postClearCache); + router.post("/api/clearCache", postClearCache); //purge all segments for VIP - router.post('/api/purgeAllSegments', postPurgeAllSegments); + router.post("/api/purgeAllSegments", postPurgeAllSegments); + + router.post("/api/unlistedVideo", addUnlistedVideo); - router.post('/api/unlistedVideo', addUnlistedVideo); - // get userID from username - router.get('/api/userID', getUserID); + router.get("/api/userID", getUserID); // get lock categores from userID - router.get('/api/lockCategories', getLockCategories); + router.get("/api/lockCategories", getLockCategories); // get privacy protecting lock categories functions - router.get('/api/lockCategories/:prefix', getLockCategoriesByHash); + router.get("/api/lockCategories/:prefix", getLockCategoriesByHash); if (config.postgres) { - router.get('/database', (req, res) => dumpDatabase(req, res, true)); - router.get('/database.json', (req, res) => dumpDatabase(req, res, false)); - router.get('/database/*', redirectLink); + router.get("/database", (req, res) => dumpDatabase(req, res, true)); + router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); + router.get("/database/*", redirectLink); } else { - router.get('/database.db', function (req: Request, res: Response) { + router.get("/database.db", function (req: Request, res: Response) { res.sendFile("./databases/sponsorTimes.db", {root: "./"}); }); } diff --git a/src/config.ts b/src/config.ts index 9002df7..a71d3b0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,12 +1,12 @@ -import fs from 'fs'; +import fs from "fs"; import {SBSConfig} from "./types/config.model"; import packageJson from "../package.json"; const isTestMode = process.env.npm_lifecycle_script === packageJson.scripts.test; -const configFile = process.env.TEST_POSTGRES ? 'ci.json' - : isTestMode ? 'test.json' - : 'config.json'; -export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString('utf8')); +const configFile = process.env.TEST_POSTGRES ? "ci.json" + : isTestMode ? "test.json" + : "config.json"; +export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString("utf8")); addDefaults(config, { port: 80, @@ -54,8 +54,8 @@ addDefaults(config, { dumpDatabase: { enabled: false, minTimeBetweenMs: 60000, - appExportPath: './docker/database-export', - postgresExportPath: '/opt/exports', + appExportPath: "./docker/database-export", + postgresExportPath: "/opt/exports", tables: [{ name: "sponsorTimes", order: "timeSubmitted" diff --git a/src/cronjob/downvoteSegmentArchiveJob.ts b/src/cronjob/downvoteSegmentArchiveJob.ts index f5e8e03..a619b47 100644 --- a/src/cronjob/downvoteSegmentArchiveJob.ts +++ b/src/cronjob/downvoteSegmentArchiveJob.ts @@ -8,56 +8,56 @@ import { DBSegment } from "../types/segments.model"; const jobConfig = serverConfig?.crons?.downvoteSegmentArchive; export const archiveDownvoteSegment = async (dayLimit: number, voteLimit: number, runTime?: number): Promise => { - const timeNow = runTime || new Date().getTime(); - const threshold = dayLimit * 86400000; + const timeNow = runTime || new Date().getTime(); + const threshold = dayLimit * 86400000; - Logger.info(`DownvoteSegmentArchiveJob starts at ${timeNow}`); - try { + Logger.info(`DownvoteSegmentArchiveJob starts at ${timeNow}`); + try { // insert into archive sponsorTime await db.prepare( - 'run', - `INSERT INTO "archivedSponsorTimes" + "run", + `INSERT INTO "archivedSponsorTimes" SELECT * FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?`, - [ - voteLimit, - timeNow, - threshold - ] + [ + voteLimit, + timeNow, + threshold + ] ) as DBSegment[]; - } catch (err) { - Logger.error('Execption when insert segment in archivedSponsorTimes'); - Logger.error(err); - return 1; - } + } catch (err) { + Logger.error("Execption when insert segment in archivedSponsorTimes"); + Logger.error(err); + return 1; + } - // remove from sponsorTime - try { + // remove from sponsorTime + try { await db.prepare( - 'run', - 'DELETE FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?', - [ - voteLimit, - timeNow, - threshold - ] + "run", + 'DELETE FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?', + [ + voteLimit, + timeNow, + threshold + ] ) as DBSegment[]; - } catch (err) { - Logger.error('Execption when deleting segment in sponsorTimes'); - Logger.error(err); - return 1; - } + } catch (err) { + Logger.error("Execption when deleting segment in sponsorTimes"); + Logger.error(err); + return 1; + } - Logger.info('DownvoteSegmentArchiveJob finished'); - return 0; + Logger.info("DownvoteSegmentArchiveJob finished"); + return 0; }; const DownvoteSegmentArchiveJob = new CronJob( - jobConfig?.schedule || new Date(1), - () => archiveDownvoteSegment(jobConfig?.timeThresholdInDays, jobConfig?.voteThreshold) + jobConfig?.schedule || new Date(1), + () => archiveDownvoteSegment(jobConfig?.timeThresholdInDays, jobConfig?.voteThreshold) ); export default DownvoteSegmentArchiveJob; diff --git a/src/cronjob/index.ts b/src/cronjob/index.ts index 46aa80c..d459ef5 100644 --- a/src/cronjob/index.ts +++ b/src/cronjob/index.ts @@ -3,11 +3,11 @@ import { config } from "../config"; import DownvoteSegmentArchiveJob from "./downvoteSegmentArchiveJob"; export function startAllCrons (): void { - if (config?.crons?.enabled) { - Logger.info("Crons started"); + if (config?.crons?.enabled) { + Logger.info("Crons started"); - DownvoteSegmentArchiveJob.start(); - } else { - Logger.info("Crons dissabled"); - } + DownvoteSegmentArchiveJob.start(); + } else { + Logger.info("Crons dissabled"); + } } diff --git a/src/databases/IDatabase.ts b/src/databases/IDatabase.ts index 4a93261..86774f4 100644 --- a/src/databases/IDatabase.ts +++ b/src/databases/IDatabase.ts @@ -4,4 +4,4 @@ export interface IDatabase { prepare(type: QueryType, query: string, params?: any[]): Promise; } -export type QueryType = 'get' | 'all' | 'run'; +export type QueryType = "get" | "all" | "run"; diff --git a/src/databases/Mysql.ts b/src/databases/Mysql.ts index 5a76db6..4c6a4fa 100644 --- a/src/databases/Mysql.ts +++ b/src/databases/Mysql.ts @@ -1,8 +1,8 @@ -import {Logger} from '../utils/logger'; -import {IDatabase, QueryType} from './IDatabase'; +import {Logger} from "../utils/logger"; +import {IDatabase, QueryType} from "./IDatabase"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -import MysqlInterface from 'sync-mysql'; +import MysqlInterface from "sync-mysql"; export class Mysql implements IDatabase { private connection: any; @@ -19,15 +19,15 @@ export class Mysql implements IDatabase { const queryResult = this.connection.query(query, params); switch (type) { - case 'get': { - return queryResult[0]; - } - case 'all': { - return queryResult; - } - case 'run': { - break; - } + case "get": { + return queryResult[0]; + } + case "all": { + return queryResult; + } + case "run": { + break; + } } } diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index a98afca..65fe06a 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -1,6 +1,6 @@ -import { Logger } from '../utils/logger'; -import { IDatabase, QueryType } from './IDatabase'; -import { Client, Pool, types } from 'pg'; +import { Logger } from "../utils/logger"; +import { IDatabase, QueryType } from "./IDatabase"; +import { Client, Pool, types } from "pg"; import fs from "fs"; @@ -11,7 +11,7 @@ types.setTypeParser(1700, function(val) { // return int8 (pg_type oid=20) as int types.setTypeParser(20, function(val) { - return parseInt(val, 10); + return parseInt(val, 10); }); export class Postgres implements IDatabase { @@ -47,8 +47,8 @@ export class Postgres implements IDatabase { // Convert query to use numbered parameters let count = 1; for (let char = 0; char < query.length; char++) { - if (query.charAt(char) === '?') { - query = query.slice(0, char) + "$" + count + query.slice(char + 1); + if (query.charAt(char) === "?") { + query = `${query.slice(0, char)}$${count}${query.slice(char + 1)}`; count++; } } @@ -58,19 +58,19 @@ export class Postgres implements IDatabase { const queryResult = await this.pool.query({text: query, values: params}); switch (type) { - case 'get': { - const value = queryResult.rows[0]; - Logger.debug(`result (postgres): ${JSON.stringify(value)}`); - return value; - } - case 'all': { - const values = queryResult.rows; - Logger.debug(`result (postgres): ${values}`); - return values; - } - case 'run': { - break; - } + case "get": { + const value = queryResult.rows[0]; + Logger.debug(`result (postgres): ${JSON.stringify(value)}`); + return value; + } + case "all": { + const values = queryResult.rows; + Logger.debug(`result (postgres): ${values}`); + return values; + } + case "run": { + break; + } } } @@ -81,7 +81,7 @@ export class Postgres implements IDatabase { }); await client.connect(); - + if ((await client.query(`SELECT * FROM pg_database WHERE datname = '${this.config.postgres.database}'`)).rowCount == 0) { await client.query(`CREATE DATABASE "${this.config.postgres.database}" WITH @@ -101,25 +101,25 @@ export class Postgres implements IDatabase { const versionCodeInfo = await this.pool.query("SELECT value FROM config WHERE key = 'version'"); let versionCode = versionCodeInfo.rows[0] ? versionCodeInfo.rows[0].value : 0; - let path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - Logger.debug('db update: trying ' + path); + let path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`; + Logger.debug(`db update: trying ${path}`); while (fs.existsSync(path)) { - Logger.debug('db update: updating ' + path); + Logger.debug(`db update: updating ${path}`); await this.pool.query(this.processUpgradeQuery(fs.readFileSync(path).toString())); versionCode = (await this.pool.query("SELECT value FROM config WHERE key = 'version'"))?.rows[0]?.value; - path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - Logger.debug('db update: trying ' + path); + path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`; + Logger.debug(`db update: trying ${path}`); } - Logger.debug('db update: no file ' + path); + Logger.debug(`db update: no file ${path}`); } private async applyIndexes(fileNamePrefix: string, schemaFolder: string) { - const path = schemaFolder + "/_" + fileNamePrefix + "_indexes.sql"; + const path = `${schemaFolder}/_${fileNamePrefix}_indexes.sql`; if (fs.existsSync(path)) { await this.pool.query(fs.readFileSync(path).toString()); } else { - Logger.debug('failed to apply indexes to ' + fileNamePrefix); + Logger.debug(`failed to apply indexes to ${fileNamePrefix}`); } } diff --git a/src/databases/Sqlite.ts b/src/databases/Sqlite.ts index 1f28258..a0c5113 100644 --- a/src/databases/Sqlite.ts +++ b/src/databases/Sqlite.ts @@ -1,9 +1,9 @@ -import {IDatabase, QueryType} from './IDatabase'; -import Sqlite3, {Database, Database as SQLiteDatabase} from 'better-sqlite3'; +import {IDatabase, QueryType} from "./IDatabase"; +import Sqlite3, {Database, Database as SQLiteDatabase} from "better-sqlite3"; import fs from "fs"; import path from "path"; -import {getHash} from '../utils/getHash'; -import {Logger} from '../utils/logger'; +import {getHash} from "../utils/getHash"; +import {Logger} from "../utils/logger"; export class Sqlite implements IDatabase { private db: SQLiteDatabase; @@ -17,16 +17,16 @@ export class Sqlite implements IDatabase { const preparedQuery = this.db.prepare(query); switch (type) { - case 'get': { - return preparedQuery.get(...params); - } - case 'all': { - return preparedQuery.all(...params); - } - case 'run': { - preparedQuery.run(...params); - break; - } + case "get": { + return preparedQuery.get(...params); + } + case "all": { + return preparedQuery.all(...params); + } + case "run": { + preparedQuery.run(...params); + break; + } } } @@ -69,17 +69,17 @@ export class Sqlite implements IDatabase { const versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version"); let versionCode = versionCodeInfo ? versionCodeInfo.value : 0; - let path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - Logger.debug('db update: trying ' + path); + let path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`; + Logger.debug(`db update: trying ${path}`); while (fs.existsSync(path)) { - Logger.debug('db update: updating ' + path); + Logger.debug(`db update: updating ${path}`); db.exec(this.processUpgradeQuery(fs.readFileSync(path).toString())); versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value; - path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - Logger.debug('db update: trying ' + path); + path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`; + Logger.debug(`db update: trying ${path}`); } - Logger.debug('db update: no file ' + path); + Logger.debug(`db update: no file ${path}`); } private static processUpgradeQuery(query: string): string { diff --git a/src/databases/databases.ts b/src/databases/databases.ts index 1639cca..d65b7ec 100644 --- a/src/databases/databases.ts +++ b/src/databases/databases.ts @@ -1,8 +1,8 @@ -import {config} from '../config'; -import {Sqlite} from './Sqlite'; -import {Mysql} from './Mysql'; -import {Postgres} from './Postgres'; -import {IDatabase} from './IDatabase'; +import {config} from "../config"; +import {Sqlite} from "./Sqlite"; +import {Mysql} from "./Mysql"; +import {Postgres} from "./Postgres"; +import {IDatabase} from "./IDatabase"; let db: IDatabase; @@ -14,7 +14,7 @@ if (config.mysql) { db = new Postgres({ dbSchemaFileName: config.dbSchema, dbSchemaFolder: config.schemaFolder, - fileNamePrefix: 'sponsorTimes', + fileNamePrefix: "sponsorTimes", readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, postgres: { @@ -29,7 +29,7 @@ if (config.mysql) { privateDB = new Postgres({ dbSchemaFileName: config.privateDBSchema, dbSchemaFolder: config.schemaFolder, - fileNamePrefix: 'private', + fileNamePrefix: "private", readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, postgres: { @@ -45,7 +45,7 @@ if (config.mysql) { dbPath: config.db, dbSchemaFileName: config.dbSchema, dbSchemaFolder: config.schemaFolder, - fileNamePrefix: 'sponsorTimes', + fileNamePrefix: "sponsorTimes", readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, enableWalCheckpointNumber: !config.readOnly && config.mode === "production" @@ -54,7 +54,7 @@ if (config.mysql) { dbPath: config.privateDB, dbSchemaFileName: config.privateDBSchema, dbSchemaFolder: config.schemaFolder, - fileNamePrefix: 'private', + fileNamePrefix: "private", readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, enableWalCheckpointNumber: false @@ -66,7 +66,7 @@ async function initDb(): Promise { if (db instanceof Sqlite) { // Attach private db to main db - (db as Sqlite).attachDatabase(config.privateDB, 'privateDB'); + (db as Sqlite).attachDatabase(config.privateDB, "privateDB"); } } diff --git a/src/index.ts b/src/index.ts index babb1a9..bb6c7df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import {config} from "./config"; -import {initDb} from './databases/databases'; +import {initDb} from "./databases/databases"; import {createServer} from "./app"; import {Logger} from "./utils/logger"; import {startAllCrons} from "./cronjob"; @@ -8,7 +8,7 @@ async function init() { await initDb(); createServer(() => { - Logger.info("Server started on port " + config.port + "."); + Logger.info(`Server started on port ${config.port}.`); // ignite cron job after server created startAllCrons(); diff --git a/src/middleware/apiCsp.ts b/src/middleware/apiCsp.ts index 093e75b..1b99c9f 100644 --- a/src/middleware/apiCsp.ts +++ b/src/middleware/apiCsp.ts @@ -1,4 +1,4 @@ -import {NextFunction, Request, Response} from 'express'; +import {NextFunction, Request, Response} from "express"; export function apiCspMiddleware(req: Request, res: Response, next: NextFunction): void { res.header("Content-Security-Policy", "script-src 'none'; object-src 'none'"); diff --git a/src/middleware/cors.ts b/src/middleware/cors.ts index 7d990a1..b661ab1 100644 --- a/src/middleware/cors.ts +++ b/src/middleware/cors.ts @@ -1,4 +1,4 @@ -import {NextFunction, Request, Response} from 'express'; +import {NextFunction, Request, Response} from "express"; export function corsMiddleware(req: Request, res: Response, next: NextFunction): void { res.header("Access-Control-Allow-Origin", "*"); diff --git a/src/middleware/logger.ts b/src/middleware/logger.ts index 3c9c6b0..fa0bb6a 100644 --- a/src/middleware/logger.ts +++ b/src/middleware/logger.ts @@ -1,5 +1,5 @@ -import {Logger} from '../utils/logger'; -import {NextFunction, Request, Response} from 'express'; +import {Logger} from "../utils/logger"; +import {NextFunction, Request, Response} from "express"; export function loggerMiddleware(req: Request, res: Response, next: NextFunction): void { Logger.info(`Request received: ${req.method} ${req.url}`); diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index c99cc5a..0b06a01 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -1,10 +1,10 @@ -import {getIP} from '../utils/getIP'; -import {getHash} from '../utils/getHash'; -import rateLimit from 'express-rate-limit'; -import {RateLimitConfig} from '../types/config.model'; -import {Request} from 'express'; -import { isUserVIP } from '../utils/isUserVIP'; -import { UserID } from '../types/user.model'; +import {getIP} from "../utils/getIP"; +import {getHash} from "../utils/getHash"; +import rateLimit from "express-rate-limit"; +import {RateLimitConfig} from "../types/config.model"; +import {Request} from "express"; +import { isUserVIP } from "../utils/isUserVIP"; +import { UserID } from "../types/user.model"; export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (req: Request) => UserID): rateLimit.RateLimit { return rateLimit({ diff --git a/src/middleware/userCounter.ts b/src/middleware/userCounter.ts index 6b30d83..0818a9e 100644 --- a/src/middleware/userCounter.ts +++ b/src/middleware/userCounter.ts @@ -1,13 +1,13 @@ -import fetch from 'node-fetch'; -import {Logger} from '../utils/logger'; -import {config} from '../config'; -import {getIP} from '../utils/getIP'; -import {getHash} from '../utils/getHash'; -import {NextFunction, Request, Response} from 'express'; +import fetch from "node-fetch"; +import {Logger} from "../utils/logger"; +import {config} from "../config"; +import {getIP} from "../utils/getIP"; +import {getHash} from "../utils/getHash"; +import {NextFunction, Request, Response} from "express"; export function userCounter(req: Request, res: Response, next: NextFunction): void { - fetch(config.userCounterURL + "/api/v1/addIP?hashedIP=" + getHash(getIP(req), 1), {method: "POST"}) - .catch(() => Logger.debug("Failing to connect to user counter at: " + config.userCounterURL)); + fetch(`${config.userCounterURL}/api/v1/addIP?hashedIP=${getHash(getIP(req), 1)}`, {method: "POST"}) + .catch(() => Logger.debug(`Failing to connect to user counter at: ${config.userCounterURL}`)); next(); } diff --git a/src/routes/addUnlistedVideo.ts b/src/routes/addUnlistedVideo.ts index 92d6772..4926658 100644 --- a/src/routes/addUnlistedVideo.ts +++ b/src/routes/addUnlistedVideo.ts @@ -1,11 +1,11 @@ -import {Request, Response} from 'express'; -import { db } from '../databases/databases'; -import { Logger } from '../utils/logger'; +import {Request, Response} from "express"; +import { db } from "../databases/databases"; +import { Logger } from "../utils/logger"; /** * Optional API method that will be used temporarily to help collect * unlisted videos created before 2017 - * + * * https://support.google.com/youtube/answer/9230970 */ @@ -21,7 +21,7 @@ export function addUnlistedVideo(req: Request, res: Response): Response { try { const timeSubmitted = Date.now(); - db.prepare('run', `INSERT INTO "unlistedVideos" ("videoID", "year", "views", "channelID", "timeSubmitted") values (?, ?, ?, ?, ?)`, [videoID, year, views, channelID, timeSubmitted]); + db.prepare("run", `INSERT INTO "unlistedVideos" ("videoID", "year", "views", "channelID", "timeSubmitted") values (?, ?, ?, ?, ?)`, [videoID, year, views, channelID, timeSubmitted]); } catch (err) { Logger.error(err); return res.sendStatus(500); diff --git a/src/routes/addUserAsVIP.ts b/src/routes/addUserAsVIP.ts index 46a6b56..919d308 100644 --- a/src/routes/addUserAsVIP.ts +++ b/src/routes/addUserAsVIP.ts @@ -1,7 +1,7 @@ -import {getHash} from '../utils/getHash'; -import {db} from '../databases/databases'; -import {config} from '../config'; -import {Request, Response} from 'express'; +import {getHash} from "../utils/getHash"; +import {db} from "../databases/databases"; +import {config} from "../config"; +import {Request, Response} from "express"; export async function addUserAsVIP(req: Request, res: Response): Promise { const userID = req.query.userID as string; @@ -9,7 +9,7 @@ export async function addUserAsVIP(req: Request, res: Response): Promise 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]); } return res.sendStatus(200); diff --git a/src/routes/deleteLockCategories.ts b/src/routes/deleteLockCategories.ts index f06c373..6ed7a84 100644 --- a/src/routes/deleteLockCategories.ts +++ b/src/routes/deleteLockCategories.ts @@ -1,9 +1,9 @@ -import {Request, Response} from 'express'; -import {isUserVIP} from '../utils/isUserVIP'; -import {getHash} from '../utils/getHash'; -import {db} from '../databases/databases'; -import { Category, VideoID } from '../types/segments.model'; -import { UserID } from '../types/user.model'; +import {Request, Response} from "express"; +import {isUserVIP} from "../utils/isUserVIP"; +import {getHash} from "../utils/getHash"; +import {db} from "../databases/databases"; +import { Category, VideoID } from "../types/segments.model"; +import { UserID } from "../types/user.model"; export async function deleteLockCategoriesEndpoint(req: Request, res: Response): Promise { // Collect user input data @@ -19,7 +19,7 @@ export async function deleteLockCategoriesEndpoint(req: Request, res: Response): || categories.length === 0 ) { return res.status(400).json({ - message: 'Bad Format', + message: "Bad Format", }); } @@ -29,18 +29,18 @@ export async function deleteLockCategoriesEndpoint(req: Request, res: Response): if (!userIsVIP) { return res.status(403).json({ - message: 'Must be a VIP to mark videos.', + message: "Must be a VIP to mark videos.", }); } - await deleteLockCategories(videoID, categories); + await deleteLockCategories(videoID, categories); - return res.status(200).json({message: 'Removed lock categories entrys for video ' + videoID}); + return res.status(200).json({message: `Removed lock categories entrys for video ${videoID}`}); } /** - * - * @param videoID + * + * @param videoID * @param categories If null, will remove all */ export async function deleteLockCategories(videoID: VideoID, categories: Category[]): Promise { @@ -49,6 +49,6 @@ export async function deleteLockCategories(videoID: VideoID, categories: Categor }); for (const entry of entries) { - await db.prepare('run', 'DELETE FROM "lockCategories" WHERE "videoID" = ? AND "category" = ?', [videoID, entry.category]); + await db.prepare("run", 'DELETE FROM "lockCategories" WHERE "videoID" = ? AND "category" = ?', [videoID, entry.category]); } } diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index 9dc7cd9..fa1d262 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -1,10 +1,10 @@ -import {db} from '../databases/databases'; -import {Logger} from '../utils/logger'; -import {Request, Response} from 'express'; -import { config } from '../config'; -import util from 'util'; -import fs from 'fs'; -import path from 'path'; +import {db} from "../databases/databases"; +import {Logger} from "../utils/logger"; +import {Request, Response} from "express"; +import { config } from "../config"; +import util from "util"; +import fs from "fs"; +import path from "path"; const unlink = util.promisify(fs.unlink); const ONE_MINUTE = 1000 * 60; @@ -31,8 +31,8 @@ const licenseHeader = `

The API and database follow table.name); interface TableDumpList { @@ -47,7 +47,7 @@ interface TableFile { } if (tables.length === 0) { - Logger.warn('[dumpDatabase] No tables configured'); + Logger.warn("[dumpDatabase] No tables configured"); } let lastUpdate = 0; @@ -71,7 +71,7 @@ function removeOutdatedDumps(exportPath: string): Promise { files.forEach(file => { // we only care about files that start with "_" and ends with .csv tableNames.forEach(tableName => { - if (file.startsWith(`${tableName}`) && file.endsWith('.csv')) { + if (file.startsWith(`${tableName}`) && file.endsWith(".csv")) { const filePath = path.join(exportPath, file); tableFiles[tableName].push({ file: filePath, @@ -108,7 +108,7 @@ export default async function dumpDatabase(req: Request, res: Response, showPage updateQueueTime(); res.status(200); - + if (showPage) { res.send(`${styleHeader}

SponsorBlock database dumps

${licenseHeader} @@ -134,9 +134,9 @@ export default async function dumpDatabase(req: Request, res: Response, showPage ${item.tableName}.csv `; - }).join('')} - ${latestDumpFiles.length === 0 ? 'Please wait: Generating files' : ''} - + }).join("")} + ${latestDumpFiles.length === 0 ? 'Please wait: Generating files' : ""} +
${updateQueued ? `Update queued.` : ``} Last updated: ${lastUpdate ? new Date(lastUpdate).toUTCString() : `Unknown`}`); @@ -159,7 +159,7 @@ export default async function dumpDatabase(req: Request, res: Response, showPage } async function getDbVersion(): Promise { - const row = await db.prepare('get', `SELECT "value" FROM "config" WHERE "key" = 'version'`); + const row = await db.prepare("get", `SELECT "value" FROM "config" WHERE "key" = 'version'`); if (row === undefined) return 0; return row.value; } @@ -173,13 +173,13 @@ export async function redirectLink(req: Request, res: Response): Promise { res.status(404).send("Not supported on this instance"); return; } - + const file = latestDumpFiles.find((value) => `/database/${value.tableName}.csv` === req.path); updateQueueTime(); if (file) { - res.redirect("/download/" + file.fileName); + res.redirect(`/download/${file.fileName}`); } else { res.sendStatus(404); } @@ -198,13 +198,13 @@ async function queueDump(): Promise { try { await removeOutdatedDumps(appExportPath); - + const dumpFiles = []; - + for (const table of tables) { const fileName = `${table.name}_${startTime}.csv`; const file = `${postgresExportPath}/${fileName}`; - await db.prepare('run', `COPY (SELECT * FROM "${table.name}"${table.order ? ` ORDER BY "${table.order}"` : ``}) + await db.prepare("run", `COPY (SELECT * FROM "${table.name}"${table.order ? ` ORDER BY "${table.order}"` : ``}) TO '${file}' WITH (FORMAT CSV, HEADER true);`); dumpFiles.push({ fileName, diff --git a/src/routes/getDaysSavedFormatted.ts b/src/routes/getDaysSavedFormatted.ts index 184fba4..03a0ad0 100644 --- a/src/routes/getDaysSavedFormatted.ts +++ b/src/routes/getDaysSavedFormatted.ts @@ -1,8 +1,8 @@ -import {db} from '../databases/databases'; -import {Request, Response} from 'express'; +import {db} from "../databases/databases"; +import {Request, Response} from "express"; export async function getDaysSavedFormatted(req: Request, res: Response): Promise { - const row = await db.prepare('get', 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []); + const 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 diff --git a/src/routes/getIsUserVIP.ts b/src/routes/getIsUserVIP.ts index 81d6eee..050be2e 100644 --- a/src/routes/getIsUserVIP.ts +++ b/src/routes/getIsUserVIP.ts @@ -1,8 +1,8 @@ -import {Logger} from '../utils/logger'; -import {getHash} from '../utils/getHash'; -import {isUserVIP} from '../utils/isUserVIP'; -import {Request, Response} from 'express'; -import { HashedUserID, UserID } from '../types/user.model'; +import {Logger} from "../utils/logger"; +import {getHash} from "../utils/getHash"; +import {isUserVIP} from "../utils/isUserVIP"; +import {Request, Response} from "express"; +import { HashedUserID, UserID } from "../types/user.model"; export async function getIsUserVIP(req: Request, res: Response): Promise { const userID = req.query.userID as UserID; diff --git a/src/routes/getLockCategories.ts b/src/routes/getLockCategories.ts index e27acea..77b8994 100644 --- a/src/routes/getLockCategories.ts +++ b/src/routes/getLockCategories.ts @@ -1,6 +1,6 @@ -import {db} from '../databases/databases'; -import {Logger} from '../utils/logger'; -import {Request, Response} from 'express'; +import {db} from "../databases/databases"; +import {Logger} from "../utils/logger"; +import {Request, Response} from "express"; import { Category, VideoID } from "../types/segments.model"; export async function getLockCategories(req: Request, res: Response): Promise { @@ -13,7 +13,7 @@ export async function getLockCategories(req: Request, res: Response): Promise entry.category); diff --git a/src/routes/getLockCategoriesByHash.ts b/src/routes/getLockCategoriesByHash.ts index 90772b2..defc6ce 100644 --- a/src/routes/getLockCategoriesByHash.ts +++ b/src/routes/getLockCategoriesByHash.ts @@ -1,7 +1,7 @@ -import {db} from '../databases/databases'; -import {Logger} from '../utils/logger'; -import {Request, Response} from 'express'; -import {hashPrefixTester} from '../utils/hashPrefixTester'; +import {db} from "../databases/databases"; +import {Logger} from "../utils/logger"; +import {Request, Response} from "express"; +import {hashPrefixTester} from "../utils/hashPrefixTester"; import { Category, VideoID, VideoIDHash } from "../types/segments.model"; interface LockResultByHash { @@ -45,7 +45,7 @@ export async function getLockCategoriesByHash(req: Request, res: Response): Prom try { // Get existing lock categories markers - const lockedRows = await db.prepare('all', 'SELECT "videoID", "hashedVideoID" as "hash", "category" from "lockCategories" where "hashedVideoID" LIKE ?', [hashPrefix + '%']) as DBLock[]; + const lockedRows = await db.prepare("all", 'SELECT "videoID", "hashedVideoID" as "hash", "category" from "lockCategories" where "hashedVideoID" LIKE ?', [`${hashPrefix}%`]) as DBLock[]; if (lockedRows.length === 0 || !lockedRows[0]) return res.sendStatus(404); // merge all locks return res.send(mergeLocks(lockedRows)); diff --git a/src/routes/getSavedTimeForUser.ts b/src/routes/getSavedTimeForUser.ts index b5511d1..d2ab256 100644 --- a/src/routes/getSavedTimeForUser.ts +++ b/src/routes/getSavedTimeForUser.ts @@ -1,8 +1,8 @@ -import {db} from '../databases/databases'; -import {Request, Response} from 'express'; -import {getHash} from '../utils/getHash'; -import {config} from '../config'; -import { Logger } from '../utils/logger'; +import {db} from "../databases/databases"; +import {Request, Response} from "express"; +import {getHash} from "../utils/getHash"; +import {config} from "../config"; +import { Logger } from "../utils/logger"; const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; @@ -28,7 +28,7 @@ export async function getSavedTimeForUser(req: Request, res: Response): Promise< return res.sendStatus(404); } } catch (err) { - Logger.error("getSavedTimeForUser " + err); + Logger.error(`getSavedTimeForUser ${err}`); return res.sendStatus(500); } } diff --git a/src/routes/getSegmentInfo.ts b/src/routes/getSegmentInfo.ts index 254a6d6..a286992 100644 --- a/src/routes/getSegmentInfo.ts +++ b/src/routes/getSegmentInfo.ts @@ -1,12 +1,12 @@ -import { Request, Response } from 'express'; -import { db } from '../databases/databases'; +import { Request, Response } from "express"; +import { db } from "../databases/databases"; import { DBSegment, SegmentUUID } from "../types/segments.model"; const isValidSegmentUUID = (str: string): boolean => /^([a-f0-9]{64}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/.test(str); async function getSegmentFromDBByUUID(UUID: SegmentUUID): Promise { try { - return await db.prepare('get', + return await db.prepare("get", `SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "service", "videoDuration", "hidden", "reputation", "shadowHidden" FROM "sponsorTimes" diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index af322ad..fc20dd3 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -1,15 +1,15 @@ -import { Request, Response } from 'express'; -import { config } from '../config'; -import { db, privateDB } from '../databases/databases'; -import { skipSegmentsHashKey, skipSegmentsKey } from '../utils/redisKeys'; -import { SBRecord } from '../types/lib.model'; +import { Request, Response } from "express"; +import { config } from "../config"; +import { db, privateDB } from "../databases/databases"; +import { skipSegmentsHashKey, skipSegmentsKey } from "../utils/redisKeys"; +import { SBRecord } from "../types/lib.model"; import { ActionType, Category, CategoryActionType, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, SegmentUUID, Service, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model"; -import { getCategoryActionType } from '../utils/categoryInfo'; -import { getHash } from '../utils/getHash'; -import { getIP } from '../utils/getIP'; -import { Logger } from '../utils/logger'; -import { QueryCacher } from '../utils/queryCacher'; -import { getReputation } from '../utils/reputation'; +import { getCategoryActionType } from "../utils/categoryInfo"; +import { getHash } from "../utils/getHash"; +import { getIP } from "../utils/getIP"; +import { Logger } from "../utils/logger"; +import { QueryCacher } from "../utils/queryCacher"; +import { getReputation } from "../utils/reputation"; async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[],cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise { @@ -25,7 +25,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 @@ -38,7 +38,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category: return shadowHiddenSegment.hashedIP === cache.userHashedIP; }); })); - + const filteredSegments = segments.filter((_, index) => shouldFilter[index]); const maxSegments = getCategoryActionType(category) === CategoryActionType.Skippable ? 32 : 1; @@ -51,8 +51,8 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category: })); } -async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], - actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise { +async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], + actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise { const cache: SegmentCache = {shadowHiddenSegmentIPs: {}}; const segments: Segment[] = []; @@ -84,8 +84,8 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: } } -async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[], - actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise> { +async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[], + actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise> { const cache: SegmentCache = {shadowHiddenSegmentIPs: {}}; const segments: SBRecord = {}; @@ -133,10 +133,10 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise { const fetchFromDB = () => db .prepare( - 'all', + "all", `SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "hashedVideoID" FROM "sponsorTimes" WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, - [hashedVideoIDPrefix + '%', service] + [`${hashedVideoIDPrefix}%`, service] ) as Promise; if (hashedVideoIDPrefix.length === 4) { @@ -149,7 +149,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise { const fetchFromDB = () => db .prepare( - 'all', + "all", `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, [videoID, service] @@ -283,7 +283,7 @@ async function handleGetSegments(req: Request, res: Response): Promise val == service)) { service = Service.YouTube; diff --git a/src/routes/getSkipSegmentsByHash.ts b/src/routes/getSkipSegmentsByHash.ts index eb2c99c..6d01d5f 100644 --- a/src/routes/getSkipSegmentsByHash.ts +++ b/src/routes/getSkipSegmentsByHash.ts @@ -1,7 +1,7 @@ -import {hashPrefixTester} from '../utils/hashPrefixTester'; -import {getSegmentsByHash} from './getSkipSegments'; -import {Request, Response} from 'express'; -import { ActionType, Category, SegmentUUID, Service, VideoIDHash } from '../types/segments.model'; +import {hashPrefixTester} from "../utils/hashPrefixTester"; +import {getSegmentsByHash} from "./getSkipSegments"; +import {Request, Response} from "express"; +import { ActionType, Category, SegmentUUID, Service, VideoIDHash } from "../types/segments.model"; export async function getSkipSegmentsByHash(req: Request, res: Response): Promise { let hashPrefix = req.params.prefix as VideoIDHash; @@ -18,7 +18,7 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis ? Array.isArray(req.query.category) ? req.query.category : [req.query.category] - : ['sponsor']; + : ["sponsor"]; if (!Array.isArray(categories)) { return res.status(400).send("Categories parameter does not match format requirements."); } @@ -45,12 +45,12 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis let requiredSegments: SegmentUUID[] = []; try { requiredSegments = req.query.requiredSegments - ? JSON.parse(req.query.requiredSegments as string) - : req.query.requiredSegment - ? Array.isArray(req.query.requiredSegment) - ? req.query.requiredSegment - : [req.query.requiredSegment] - : []; + ? JSON.parse(req.query.requiredSegments as string) + : req.query.requiredSegment + ? Array.isArray(req.query.requiredSegment) + ? req.query.requiredSegment + : [req.query.requiredSegment] + : []; if (!Array.isArray(requiredSegments)) { return res.status(400).send("requiredSegments parameter does not match format requirements."); } @@ -62,7 +62,7 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis if (!Object.values(Service).some((val) => val == service)) { service = Service.YouTube; } - + // filter out none string elements, only flat array with strings is valid categories = categories.filter((item: any) => typeof item === "string"); diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index 2885ec1..69652c2 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -1,7 +1,7 @@ -import {db} from '../databases/databases'; -import {createMemoryCache} from '../utils/createMemoryCache'; -import {config} from '../config'; -import {Request, Response} from 'express'; +import {db} from "../databases/databases"; +import {createMemoryCache} from "../utils/createMemoryCache"; +import {config} from "../config"; +import {Request, Response} from "express"; const MILLISECONDS_IN_MINUTE = 60000; const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); @@ -14,7 +14,7 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = fals const minutesSaved = []; const categoryStats: any[] = categoryStatsEnabled ? [] : undefined; - let additionalFields = ''; + 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", @@ -25,11 +25,9 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = fals SUM(CASE WHEN category = 'preview' THEN 1 ELSE 0 END) as "categorySumPreview", `; } - const rows = await db.prepare('all', `SELECT COUNT(*) as "totalSubmissions", SUM(views) as "viewCount", + const rows = await db.prepare("all", `SELECT COUNT(*) as "totalSubmissions", SUM(views) as "viewCount", SUM(((CASE WHEN "sponsorTimes"."endTime" - "sponsorTimes"."startTime" > ? THEN ? ELSE "sponsorTimes"."endTime" - "sponsorTimes"."startTime" END) / 60) * "sponsorTimes"."views") as "minutesSaved", - SUM("votes") as "userVotes", ` + - additionalFields + - `COALESCE("userNames"."userName", "sponsorTimes"."userID") as "userName" FROM "sponsorTimes" LEFT JOIN "userNames" ON "sponsorTimes"."userID"="userNames"."userID" + SUM("votes") as "userVotes", ${additionalFields} COALESCE("userNames"."userName", "sponsorTimes"."userID") as "userName" FROM "sponsorTimes" LEFT JOIN "userNames" ON "sponsorTimes"."userID"="userNames"."userID" LEFT JOIN "shadowBannedUsers" ON "sponsorTimes"."userID"="shadowBannedUsers"."userID" WHERE "sponsorTimes"."votes" > -1 AND "sponsorTimes"."shadowHidden" != 1 AND "shadowBannedUsers"."userID" IS NULL GROUP BY COALESCE("userName", "sponsorTimes"."userID") HAVING SUM("votes") > 20 @@ -73,13 +71,13 @@ export async function getTopUsers(req: Request, res: Response): Promise { const userCountQuery = `(SELECT COUNT(*) FROM (SELECT DISTINCT "userID" from "sponsorTimes") t) "userCount",`; - const row = await db.prepare('get', `SELECT ${req.query.countContributingUsers ? userCountQuery : ""} COUNT(*) as "totalSubmissions", + const row = await db.prepare("get", `SELECT ${req.query.countContributingUsers ? userCountQuery : ""} 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) { @@ -44,42 +44,41 @@ export async function getTotalStats(req: Request, res: Response): Promise function updateExtensionUsers() { if (config.userCounterURL) { - fetch(config.userCounterURL + "/api/v1/userCount") - .then(res => res.json()) - .then(data => { - apiUsersCache = Math.max(apiUsersCache, data.userCount); - }) - .catch(() => Logger.debug("Failing to connect to user counter at: " + config.userCounterURL)); + fetch(`${config.userCounterURL}/api/v1/userCount`) + .then(res => res.json()) + .then(data => { + apiUsersCache = Math.max(apiUsersCache, data.userCount); + }) + .catch(() => Logger.debug(`Failing to connect to user counter at: ${config.userCounterURL}`)); } - + const mozillaAddonsUrl = "https://addons.mozilla.org/api/v3/addons/addon/sponsorblock/"; const chromeExtensionUrl = "https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone"; - + fetch(mozillaAddonsUrl) - .then(res => res.json()) - .then(data => { - firefoxUsersCache = data.average_daily_users; - - fetch(chromeExtensionUrl) - .then(res => res.text()) - .then(body => { - // 2021-01-05 - // [...]= 0) { - const closingQuoteIndex = body.indexOf('"', userDownloadsStartIndex + matchingStringLen); - const userDownloadsStr = body.substr(userDownloadsStartIndex + matchingStringLen, closingQuoteIndex - userDownloadsStartIndex).replace(',','').replace('.',''); - chromeUsersCache = parseInt(userDownloadsStr); - } - else { - lastUserCountCheck = 0; - } + .then(res => res.json()) + .then(data => { + firefoxUsersCache = data.average_daily_users; + fetch(chromeExtensionUrl) + .then(res => res.text()) + .then(body => { + // 2021-01-05 + // [...]= 0) { + const closingQuoteIndex = body.indexOf('"', userDownloadsStartIndex + matchingStringLen); + const userDownloadsStr = body.substr(userDownloadsStartIndex + matchingStringLen, closingQuoteIndex - userDownloadsStartIndex).replace(",","").replace(".",""); + chromeUsersCache = parseInt(userDownloadsStr); + } + else { + lastUserCountCheck = 0; + } + }) + .catch(() => Logger.debug(`Failing to connect to ${chromeExtensionUrl}`)); }) - .catch(() => Logger.debug("Failing to connect to " + chromeExtensionUrl)); - }) - .catch(() => { - Logger.debug("Failing to connect to " + mozillaAddonsUrl); - }); + .catch(() => { + Logger.debug(`Failing to connect to ${mozillaAddonsUrl}`); + }); } diff --git a/src/routes/getUserID.ts b/src/routes/getUserID.ts index 0b42588..08bd1d8 100644 --- a/src/routes/getUserID.ts +++ b/src/routes/getUserID.ts @@ -1,16 +1,16 @@ -import {db} from '../databases/databases'; -import {Request, Response} from 'express'; -import {UserID} from '../types/user.model'; +import {db} from "../databases/databases"; +import {Request, Response} from "express"; +import {UserID} from "../types/user.model"; function getFuzzyUserID(userName: string): Promise<{userName: string, userID: UserID }[]> { // escape [_ % \] to avoid ReDOS - userName = userName.replace(/\\/g, '\\\\') - .replace(/_/g, '\\_') - .replace(/%/g, '\\%'); + userName = userName.replace(/\\/g, "\\\\") + .replace(/_/g, "\\_") + .replace(/%/g, "\\%"); userName = `%${userName}%`; // add wildcard to username // LIMIT to reduce overhead | ESCAPE to escape LIKE wildcards try { - return db.prepare('all', `SELECT "userName", "userID" FROM "userNames" WHERE "userName" + return db.prepare("all", `SELECT "userName", "userID" FROM "userNames" WHERE "userName" LIKE ? ESCAPE '\\' LIMIT 10`, [userName]); } catch (err) { return null; @@ -19,7 +19,7 @@ function getFuzzyUserID(userName: string): Promise<{userName: string, userID: Us function getExactUserID(userName: string): Promise<{userName: string, userID: UserID }[]> { try { - return db.prepare('all', `SELECT "userName", "userID" from "userNames" WHERE "userName" = ? LIMIT 10`, [userName]); + return db.prepare("all", `SELECT "userName", "userID" from "userNames" WHERE "userName" = ? LIMIT 10`, [userName]); } catch (err) { return null; } @@ -30,7 +30,7 @@ export async function getUserID(req: Request, res: Response): Promise const exactSearch = req.query.exact ? req.query.exact == "true" : false as boolean; - + // if not exact and length is 1, also skip if (userName == undefined || userName.length > 64 || (!exactSearch && userName.length < 3)) { diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 7fb3bf3..29fc1ab 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -1,10 +1,10 @@ -import {db} from '../databases/databases'; -import {getHash} from '../utils/getHash'; -import {isUserVIP} from '../utils/isUserVIP'; -import {Request, Response} from 'express'; -import {Logger} from '../utils/logger'; -import { HashedUserID, UserID } from '../types/user.model'; -import { getReputation } from '../utils/reputation'; +import {db} from "../databases/databases"; +import {getHash} from "../utils/getHash"; +import {isUserVIP} from "../utils/isUserVIP"; +import {Request, Response} from "express"; +import {Logger} from "../utils/logger"; +import { HashedUserID, UserID } from "../types/user.model"; +import { getReputation } from "../utils/reputation"; import { SegmentUUID } from "../types/segments.model"; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { @@ -39,7 +39,7 @@ async function dbGetIgnoredSegmentCount(userID: HashedUserID): Promise { async function dbGetUsername(userID: HashedUserID) { try { - 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]); if (row !== undefined) { return row.userName; } else { @@ -53,7 +53,7 @@ async function dbGetUsername(userID: HashedUserID) { async function dbGetViewsForUser(userID: HashedUserID) { try { - const row = await db.prepare('get', `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID]); + const row = await db.prepare("get", `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID]); return row?.viewCount ?? 0; } catch (err) { return false; @@ -62,7 +62,7 @@ async function dbGetViewsForUser(userID: HashedUserID) { async function dbGetIgnoredViewsForUser(userID: HashedUserID) { try { - const row = await db.prepare('get', `SELECT SUM("views") as "ignoredViewCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]); + const row = await db.prepare("get", `SELECT SUM("views") as "ignoredViewCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]); return row?.ignoredViewCount ?? 0; } catch (err) { return false; @@ -71,17 +71,17 @@ async function dbGetIgnoredViewsForUser(userID: HashedUserID) { async function dbGetWarningsForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare('get', `SELECT COUNT(*) as total FROM "warnings" WHERE "userID" = ? AND "enabled" = 1`, [userID]); + const row = await db.prepare("get", `SELECT COUNT(*) as total FROM "warnings" WHERE "userID" = ? AND "enabled" = 1`, [userID]); return row?.total ?? 0; } catch (err) { - Logger.error('Couldn\'t get warnings for user ' + userID + '. returning 0'); + Logger.error(`Couldn't get warnings for user ${userID}. returning 0`); return 0; } } async function dbGetLastSegmentForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare('get', `SELECT "UUID" FROM "sponsorTimes" WHERE "userID" = ? ORDER BY "timeSubmitted" DESC LIMIT 1`, [userID]); + const row = await db.prepare("get", `SELECT "UUID" FROM "sponsorTimes" WHERE "userID" = ? ORDER BY "timeSubmitted" DESC LIMIT 1`, [userID]); return row?.UUID ?? null; } catch (err) { return null; @@ -94,7 +94,7 @@ export async function getUserInfo(req: Request, res: Response): Promise { let userID = req.query.userID as string; @@ -15,7 +15,7 @@ export async function getUsername(req: Request, res: Response): Promise { let userID = req.query.userID as string; @@ -15,7 +15,7 @@ export async function getViewsForUser(req: Request, res: Response): Promise { const segments = await handleGetSegments(req, res); diff --git a/src/routes/oldSubmitSponsorTimes.ts b/src/routes/oldSubmitSponsorTimes.ts index 2f04142..0ddc31a 100644 --- a/src/routes/oldSubmitSponsorTimes.ts +++ b/src/routes/oldSubmitSponsorTimes.ts @@ -1,5 +1,5 @@ -import {postSkipSegments} from './postSkipSegments'; -import {Request, Response} from 'express'; +import {postSkipSegments} from "./postSkipSegments"; +import {Request, Response} from "express"; export async function oldSubmitSponsorTimes(req: Request, res: Response): Promise { req.query.category = "sponsor"; diff --git a/src/routes/postClearCache.ts b/src/routes/postClearCache.ts index 75040fe..5a15ee7 100644 --- a/src/routes/postClearCache.ts +++ b/src/routes/postClearCache.ts @@ -1,10 +1,10 @@ -import { Logger } from '../utils/logger'; -import { HashedUserID, UserID } from '../types/user.model'; -import { getHash } from '../utils/getHash'; -import { Request, Response } from 'express'; -import { Service, VideoID } from '../types/segments.model'; -import { QueryCacher } from '../utils/queryCacher'; -import { isUserVIP } from '../utils/isUserVIP'; +import { Logger } from "../utils/logger"; +import { HashedUserID, UserID } from "../types/user.model"; +import { getHash } from "../utils/getHash"; +import { Request, Response } from "express"; +import { Service, VideoID } from "../types/segments.model"; +import { QueryCacher } from "../utils/queryCacher"; +import { isUserVIP } from "../utils/isUserVIP"; import { VideoIDHash } from "../types/segments.model"; export async function postClearCache(req: Request, res: Response): Promise { @@ -13,17 +13,17 @@ export async function postClearCache(req: Request, res: Response): Promise p + (i !== 0 ? ', ' : '') + c, ''); - return res.status(400).send(`No valid ${fields} field(s) provided`); + // invalid request + const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ", " : "") + c, ""); + return res.status(400).send(`No valid ${fields} field(s) provided`); } // hash the userID as early as possible @@ -33,7 +33,7 @@ export async function postClearCache(req: Request, res: Response): Promise { @@ -10,7 +10,7 @@ export async function postLockCategories(req: Request, res: Response): Promise { const userID = req.body.userID as UserID; @@ -22,12 +22,12 @@ export async function postPurgeAllSegments(req: Request, res: Response): Promise const vipState = await isUserVIP(hashedUserID); if (!vipState) { return res.status(403).json({ - message: 'Must be a VIP to perform this action.', + message: "Must be a VIP to perform this action.", }); } - await db.prepare('run', `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "videoID" = ?`, [videoID]); - + await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "videoID" = ?`, [videoID]); + } catch (err) { Logger.error(err); return res.sendStatus(500); diff --git a/src/routes/postSegmentShift.ts b/src/routes/postSegmentShift.ts index 50730ad..20e53ff 100644 --- a/src/routes/postSegmentShift.ts +++ b/src/routes/postSegmentShift.ts @@ -1,12 +1,12 @@ -import {Request, Response} from 'express'; -import {Logger} from '../utils/logger'; -import {isUserVIP} from '../utils/isUserVIP'; -import {getHash} from '../utils/getHash'; -import {db} from '../databases/databases'; +import {Request, Response} from "express"; +import {Logger} from "../utils/logger"; +import {isUserVIP} from "../utils/isUserVIP"; +import {getHash} from "../utils/getHash"; +import {db} from "../databases/databases"; -const ACTION_NONE = Symbol('none'); -const ACTION_UPDATE = Symbol('update'); -const ACTION_REMOVE = Symbol('remove'); +const ACTION_NONE = Symbol("none"); +const ACTION_UPDATE = Symbol("update"); +const ACTION_REMOVE = Symbol("remove"); function shiftSegment(segment: any, shift: { startTime: any; endTime: any }) { if (segment.startTime >= segment.endTime) return {action: ACTION_NONE, segment}; @@ -59,7 +59,7 @@ export async function postSegmentShift(req: Request, res: Response): Promise 1) return; - + fetch(config.discordFirstTimeSubmissionsWebhookURL, { - method: 'POST', + method: "POST", body: JSON.stringify({ "embeds": [{ "title": data?.title, - "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseInt(startTime.toFixed(0)) - 2), + "url": `https://www.youtube.com/watch?v=${videoID}&t=${(parseInt(startTime.toFixed(0)) - 2)}`, "description": "Submission ID: " + UUID + "\n\nTimestamp: " + getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + @@ -85,44 +85,44 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: }], }), headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" } }) - .then(res => { - if (res.status >= 400) { - Logger.error("Error sending first time submission Discord hook"); - Logger.error(JSON.stringify(res)); + .then(res => { + if (res.status >= 400) { + Logger.error("Error sending first time submission Discord hook"); + Logger.error(JSON.stringify(res)); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send first time submission Discord hook."); + Logger.error(JSON.stringify(err)); Logger.error("\n"); - } - }) - .catch(err => { - Logger.error("Failed to send first time submission Discord hook."); - Logger.error(JSON.stringify(err)); - Logger.error("\n"); - }); + }); } } 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 + 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]); + [userID, userID, userID, userID]); let submittedBy: string; // If a userName was created then show both if (submissionInfoRow.userName !== userID) { - submittedBy = submissionInfoRow.userName + "\n " + userID; + submittedBy = `${submissionInfoRow.userName}\n${userID}`; } else { submittedBy = userID; } // Send discord message if (config.discordNeuralBlockRejectWebhookURL === null) return; - + fetch(config.discordNeuralBlockRejectWebhookURL, { - method: 'POST', + method: "POST", body: JSON.stringify({ "embeds": [{ "title": ytData.items[0].snippet.title, @@ -141,21 +141,21 @@ async function sendWebhooksNB(userID: string, videoID: string, UUID: string, sta }], }), headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" } }) - .then(res => { - if (res.status >= 400) { - Logger.error("Error sending NeuralBlock Discord hook"); - Logger.error(JSON.stringify(res)); + .then(res => { + if (res.status >= 400) { + Logger.error("Error sending NeuralBlock Discord hook"); + Logger.error(JSON.stringify(res)); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send NeuralBlock Discord hook."); + Logger.error(JSON.stringify(err)); Logger.error("\n"); - } - }) - .catch(err => { - Logger.error("Failed to send NeuralBlock Discord hook."); - Logger.error(JSON.stringify(err)); - Logger.error("\n"); - }); + }); } // callback: function(reject: "String containing reason the submission was rejected") @@ -164,8 +164,8 @@ async function sendWebhooksNB(userID: string, videoID: string, UUID: string, sta // 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(apiVideoInfo: APIVideoInfo, - submission: { videoID: VideoID; userID: UserID; segments: IncomingSegment[] }) { +async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, + submission: { videoID: VideoID; userID: UserID; segments: IncomingSegment[] }) { if (apiVideoInfo) { const {err, data} = apiVideoInfo; if (err) return false; @@ -180,13 +180,13 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, } else { if (segments[i].category === "sponsor") { //Prepare timestamps to send to NB all at once - nbString = nbString + segments[i].segment[0] + "," + segments[i].segment[1] + ";"; + nbString = `${nbString}${segments[i].segment[0]},${segments[i].segment[1]};`; } } } // Get all submissions for this user - const allSubmittedByUser = await db.prepare('all', `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ? and "votes" > -1`, [submission.userID, submission.videoID]); + const allSubmittedByUser = await db.prepare("all", `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ? and "votes" > -1`, [submission.userID, submission.videoID]); const allSegmentTimes = []; if (allSubmittedByUser !== undefined) { //add segments the user has previously submitted @@ -221,8 +221,8 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, // Check NeuralBlock const neuralBlockURL = config.neuralBlockURL; if (!neuralBlockURL) return false; - const response = await fetch(neuralBlockURL + "/api/checkSponsorSegments?vid=" + submission.videoID + - "&segments=" + nbString.substring(0, nbString.length - 1)); + const response = await fetch(`${neuralBlockURL}/api/checkSponsorSegments?vid=${submission.videoID} + &segments=${nbString.substring(0, nbString.length - 1)}`); if (!response.ok) return false; const nbPredictions = await response.json(); @@ -270,7 +270,7 @@ async function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promi async function checkUserActiveWarning(userID: string): Promise<{ pass: boolean; errorMessage: string; }> { const MILLISECONDS_IN_HOUR = 3600000; const now = Date.now(); - const warnings = await db.prepare('all', + const warnings = await db.prepare("all", `SELECT "reason" FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1 @@ -278,30 +278,30 @@ async function checkUserActiveWarning(userID: string): Promise<{ pass: boolean; LIMIT ?`, [ userID, - Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR)), + Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR)), config.maxNumberOfActiveWarnings ], ) as {reason: string}[]; if (warnings?.length >= config.maxNumberOfActiveWarnings) { - const defaultMessage = '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 defaultMessage = "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?"; return { - pass: false, + pass: false, errorMessage: warnings[0]?.reason?.length > 0 ? warnings[0].reason : defaultMessage }; } - return {pass: true, errorMessage: ''}; + return {pass: true, errorMessage: ""}; } function proxySubmission(req: Request) { - fetch(config.proxySubmission + '/api/skipSegments?userID=' + req.query.userID + '&videoID=' + req.query.videoID, { - method: 'POST', - body: req.body, - }) + fetch(`${config.proxySubmission}/api/skipSegments?userID=${req.query.userID}&videoID=${req.query.videoID}`, { + method: "POST", + body: req.body, + }) .then(async res => { - Logger.debug('Proxy Submission: ' + res.status + ' (' + (await res.text()) + ')'); + Logger.debug(`Proxy Submission: ${res.status} (${(await res.text())})`); }) .catch(() => { Logger.error("Proxy Submission: Failed to make call"); @@ -334,24 +334,24 @@ export async function postSkipSegments(req: Request, res: Response): Promise { if (!Object.values(ActionType).some((val) => val === segment.actionType)){ segment.actionType = ActionType.Skip; - } + } }); const invalidFields = []; - if (typeof videoID !== 'string') { - invalidFields.push('videoID'); + if (typeof videoID !== "string") { + invalidFields.push("videoID"); } - if (typeof userID !== 'string' || userID.length < 30) { - invalidFields.push('userID'); + if (typeof userID !== "string" || userID.length < 30) { + invalidFields.push("userID"); } if (!Array.isArray(segments) || segments.length < 1) { - invalidFields.push('segments'); + invalidFields.push("segments"); } if (invalidFields.length !== 0) { - // invalid request - const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ', ' : '') + c, ''); - return res.status(400).send(`No valid ${fields} field(s) provided`); + // invalid request + const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ", " : "") + c, ""); + return res.status(400).send(`No valid ${fields} field(s) provided`); } //hash the userID @@ -362,14 +362,14 @@ export async function postSkipSegments(req: Request, res: Response): Promise list.category ); + let lockedCategoryList = (await db.prepare("all", 'SELECT category from "lockCategories" where "videoID" = ?', [videoID])).map((list: any) => 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 decreaseVotes = 0; - const previousSubmissions = await db.prepare('all', + const previousSubmissions = await db.prepare("all", `SELECT "videoDuration", "UUID" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ? AND @@ -395,7 +395,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise endTime - || (getCategoryActionType(segments[i].category) === CategoryActionType.Skippable && startTime === endTime) + || (getCategoryActionType(segments[i].category) === CategoryActionType.Skippable && startTime === endTime) || (getCategoryActionType(segments[i].category) === CategoryActionType.POI && startTime !== endTime)) { //invalid request return res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)"); @@ -445,7 +445,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise 0) { return res.sendStatus(409); @@ -463,7 +463,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise ?`, [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 @@ -495,7 +495,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise= 16) { //too many sponsors for the same video from the same user @@ -504,7 +504,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise transforms to combining overlapping segments // [ // [3, 40], -// [50, 80], +// [50, 80], // [100, 150] // ] function mergeTimeSegments(ranges: number[][]) { diff --git a/src/routes/postWarning.ts b/src/routes/postWarning.ts index 015a9f8..6d033dd 100644 --- a/src/routes/postWarning.ts +++ b/src/routes/postWarning.ts @@ -1,9 +1,9 @@ -import {Request, Response} from 'express'; -import {Logger} from '../utils/logger'; -import {db} from '../databases/databases'; -import {isUserVIP} from '../utils/isUserVIP'; -import {getHash} from '../utils/getHash'; -import { HashedUserID, UserID } from '../types/user.model'; +import {Request, Response} from "express"; +import {Logger} from "../utils/logger"; +import {db} from "../databases/databases"; +import {isUserVIP} from "../utils/isUserVIP"; +import {getHash} from "../utils/getHash"; +import { HashedUserID, UserID } from "../types/user.model"; export async function postWarning(req: Request, res: Response): Promise { // Collect user input data @@ -11,22 +11,22 @@ export async function postWarning(req: Request, res: Response): Promise { - return privateDB.prepare('run', + return privateDB.prepare("run", `INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedByAdmin", "updatedAt") VALUES(?, ?, ?, ?, ?)`, [userID, newUserName, oldUserName, + updatedByAdmin, new Date().getTime()] ); @@ -26,11 +26,11 @@ export async function setUsername(req: Request, res: Response): Promise 0) { return res.sendStatus(200); } @@ -58,17 +58,17 @@ export async function setUsername(req: Request, res: Response): Promise 0) { //already exists, update this row oldUserName = row.userName; - await db.prepare('run', `UPDATE "userNames" SET "userName" = ?, "locked" = ? WHERE "userID" = ?`, [userName, locked, userID]); + await db.prepare("run", `UPDATE "userNames" SET "userName" = ?, "locked" = ? WHERE "userID" = ?`, [userName, locked, userID]); } else { //add to the db - await db.prepare('run', `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [userID, userName, locked]); + await db.prepare("run", `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [userID, userName, locked]); } await logUserNameChange(userID, userName, oldUserName, adminUserIDInput !== undefined); diff --git a/src/routes/shadowBanUser.ts b/src/routes/shadowBanUser.ts index 71806be..98ae6e4 100644 --- a/src/routes/shadowBanUser.ts +++ b/src/routes/shadowBanUser.ts @@ -1,10 +1,10 @@ -import {db} from '../databases/databases'; -import {getHash} from '../utils/getHash'; -import {Request, Response} from 'express'; -import { config } from '../config'; -import { Category, Service, VideoID, VideoIDHash } from '../types/segments.model'; -import { UserID } from '../types/user.model'; -import { QueryCacher } from '../utils/queryCacher'; +import {db} from "../databases/databases"; +import {getHash} from "../utils/getHash"; +import {Request, Response} from "express"; +import { config } from "../config"; +import { Category, Service, VideoID, VideoIDHash } from "../types/segments.model"; +import { UserID } from "../types/user.model"; +import { QueryCacher } from "../utils/queryCacher"; export async function shadowBanUser(req: Request, res: Response): Promise { const userID = req.query.userID as string; @@ -13,7 +13,7 @@ export async function shadowBanUser(req: Request, res: Response): Promise `'${c}'`).join(",")}) + await db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = 1 WHERE "userID" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")}) AND NOT EXISTS ( SELECT "videoID", "category" FROM "lockCategories" WHERE "sponsorTimes"."videoID" = "lockCategories"."videoID" AND "sponsorTimes"."category" = "lockCategories"."category")`, [userID]); - + // clear cache for all old videos - (await db.prepare('all', `SELECT "videoID", "hashedVideoID", "service", "votes", "views" FROM "sponsorTimes" WHERE "userID" = ?`, [userID])) + (await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views" FROM "sponsorTimes" WHERE "userID" = ?`, [userID])) .forEach((videoInfo: {category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID}) => { QueryCacher.clearVideoCache(videoInfo); } - ); + ); } } else if (!enabled && row.userCount > 0) { //remove them from the shadow ban list - await db.prepare('run', `DELETE FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]); + await db.prepare("run", `DELETE FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]); //find all previous submissions and unhide them if (unHideOldSubmissions) { - const segmentsToIgnore = (await db.prepare('all', `SELECT "UUID" FROM "sponsorTimes" st - JOIN "lockCategories" ns on "st"."videoID" = "ns"."videoID" AND st.category = ns.category WHERE "st"."userID" = ?` - , [userID])).map((item: {UUID: string}) => item.UUID); - const allSegments = (await db.prepare('all', `SELECT "UUID" FROM "sponsorTimes" st WHERE "st"."userID" = ?`, [userID])) - .map((item: {UUID: string}) => item.UUID); + const segmentsToIgnore = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st + JOIN "lockCategories" ns on "st"."videoID" = "ns"."videoID" AND st.category = ns.category WHERE "st"."userID" = ?` + , [userID])).map((item: {UUID: string}) => item.UUID); + const allSegments = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st WHERE "st"."userID" = ?`, [userID])) + .map((item: {UUID: string}) => item.UUID); await Promise.all(allSegments.filter((item: {uuid: string}) => { return segmentsToIgnore.indexOf(item) === -1; }).map(async (UUID: string) => { // collect list for unshadowbanning - (await db.prepare('all', `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ? AND "shadowHidden" = 1 AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID])) + (await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ? AND "shadowHidden" = 1 AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID])) .forEach((videoInfo: {category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID}) => { - QueryCacher.clearVideoCache(videoInfo); + QueryCacher.clearVideoCache(videoInfo); } - ); + ); - return db.prepare('run', `UPDATE "sponsorTimes" SET "shadowHidden" = 0 WHERE "UUID" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID]); + return db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = 0 WHERE "UUID" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID]); })); } } @@ -98,7 +98,7 @@ export async function shadowBanUser(req: Request, res: Response): Promise { const UUID = req.query.UUID; @@ -10,7 +10,7 @@ export async function viewedVideoSponsorTime(req: Request, res: Response): Promi } //up the view count by one - await db.prepare('run', `UPDATE "sponsorTimes" SET views = views + 1 WHERE "UUID" = ?`, [UUID]); + await db.prepare("run", `UPDATE "sponsorTimes" SET views = views + 1 WHERE "UUID" = ?`, [UUID]); return res.sendStatus(200); } diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 7c9251e..eabc4a4 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -1,18 +1,18 @@ -import {Request, Response} from 'express'; -import {Logger} from '../utils/logger'; -import {isUserVIP} from '../utils/isUserVIP'; -import fetch from 'node-fetch'; -import {getMaxResThumbnail, YouTubeAPI} from '../utils/youtubeApi'; -import {db, privateDB} from '../databases/databases'; -import {dispatchEvent, getVoteAuthor, getVoteAuthorRaw} from '../utils/webhookUtils'; -import {getFormattedTime} from '../utils/getFormattedTime'; -import {getIP} from '../utils/getIP'; -import {getHash} from '../utils/getHash'; -import {config} from '../config'; -import { UserID } from '../types/user.model'; -import { Category, CategoryActionType, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash } from '../types/segments.model'; -import { getCategoryActionType } from '../utils/categoryInfo'; -import { QueryCacher } from '../utils/queryCacher'; +import {Request, Response} from "express"; +import {Logger} from "../utils/logger"; +import {isUserVIP} from "../utils/isUserVIP"; +import fetch from "node-fetch"; +import {getMaxResThumbnail, YouTubeAPI} from "../utils/youtubeApi"; +import {db, privateDB} from "../databases/databases"; +import {dispatchEvent, getVoteAuthor, getVoteAuthorRaw} from "../utils/webhookUtils"; +import {getFormattedTime} from "../utils/getFormattedTime"; +import {getIP} from "../utils/getIP"; +import {getHash} from "../utils/getHash"; +import {config} from "../config"; +import { UserID } from "../types/user.model"; +import { Category, CategoryActionType, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash } from "../types/segments.model"; +import { getCategoryActionType } from "../utils/categoryInfo"; +import { QueryCacher } from "../utils/queryCacher"; const voteTypes = { normal: 0, @@ -49,26 +49,25 @@ interface VoteData { } async function sendWebhooks(voteData: VoteData) { - const submissionInfoRow = await db.prepare('get', `SELECT "s"."videoID", "s"."userID", s."startTime", s."endTime", s."category", u."userName", + const submissionInfoRow = await 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"=?`, - [voteData.UUID]); + [voteData.UUID]); - const userSubmissionCountRow = await db.prepare('get', `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [voteData.nonAnonUserID]); + const userSubmissionCountRow = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [voteData.nonAnonUserID]); if (submissionInfoRow !== undefined && userSubmissionCountRow != undefined) { let webhookURL: string = null; if (voteData.voteTypeEnum === voteTypes.normal) { switch (voteData.finalResponse.webhookType) { - case VoteWebhookType.Normal: - webhookURL = config.discordReportChannelWebhookURL; - break; - case VoteWebhookType.Rejected: - webhookURL = config.discordFailedReportChannelWebhookURL; - break; + case VoteWebhookType.Normal: + webhookURL = config.discordReportChannelWebhookURL; + break; + case VoteWebhookType.Rejected: + webhookURL = config.discordFailedReportChannelWebhookURL; + break; } - } else if (voteData.voteTypeEnum === voteTypes.incorrect) { webhookURL = config.discordCompletelyIncorrectReportWebhookURL; } @@ -86,7 +85,7 @@ async function sendWebhooks(voteData: VoteData) { "video": { "id": submissionInfoRow.videoID, "title": data?.title, - "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID, + "url": `https://www.youtube.com/watch?v=${submissionInfoRow.videoID}`, "thumbnail": getMaxResThumbnail(data) || null, }, "submission": { @@ -113,12 +112,11 @@ async function sendWebhooks(voteData: VoteData) { // Send discord message if (webhookURL !== null && !isUpvote) { fetch(webhookURL, { - method: 'POST', + method: "POST", body: JSON.stringify({ "embeds": [{ "title": data?.title, - "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID - + "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), + "url": `https://www.youtube.com/watch?v=${submissionInfoRow.videoID}&t=${(submissionInfoRow.startTime.toFixed(0) - 2)}`, "description": "**" + voteData.row.votes + " Votes Prior | " + (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount) + " Votes Now | " + voteData.row.views + " Views**\n\n**Submission ID:** " + voteData.UUID @@ -140,38 +138,38 @@ async function sendWebhooks(voteData: VoteData) { }], }), headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" } }) - .then(async res => { - if (res.status >= 400) { - Logger.error("Error sending reported submission Discord hook"); - Logger.error(JSON.stringify((await res.text()))); + .then(async res => { + if (res.status >= 400) { + Logger.error("Error sending reported submission Discord hook"); + Logger.error(JSON.stringify((await res.text()))); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send reported submission Discord hook."); + Logger.error(JSON.stringify(err)); Logger.error("\n"); - } - }) - .catch(err => { - Logger.error("Failed to send reported submission Discord hook."); - Logger.error(JSON.stringify(err)); - Logger.error("\n"); - }); + }); } } } } async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, isOwnSubmission: boolean, category: Category - , hashedIP: HashedIP, finalResponse: FinalResponse, res: Response): Promise { + , hashedIP: HashedIP, finalResponse: FinalResponse, res: Response): Promise { // Check if they've already made a vote - const usersLastVoteInfo = await privateDB.prepare('get', `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID]); + const usersLastVoteInfo = await privateDB.prepare("get", `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID]); if (usersLastVoteInfo?.category === category) { // Double vote, ignore return res.sendStatus(finalResponse.finalStatus); } - const videoInfo = (await db.prepare('get', `SELECT "category", "videoID", "hashedVideoID", "service", "userID" FROM "sponsorTimes" WHERE "UUID" = ?`, - [UUID])) as {category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID}; + const videoInfo = (await db.prepare("get", `SELECT "category", "videoID", "hashedVideoID", "service", "userID" FROM "sponsorTimes" WHERE "UUID" = ?`, + [UUID])) as {category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID}; if (!videoInfo) { // Submission doesn't exist return res.status(400).send("Submission doesn't exist."); @@ -193,22 +191,22 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i if (ableToVote) { // Add the vote - if ((await db.prepare('get', `select count(*) as count from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, category])).count > 0) { + if ((await db.prepare("get", `select count(*) as count from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, category])).count > 0) { // Update the already existing db entry - await db.prepare('run', `update "categoryVotes" set "votes" = "votes" + ? where "UUID" = ? and "category" = ?`, [voteAmount, UUID, category]); + await db.prepare("run", `update "categoryVotes" set "votes" = "votes" + ? where "UUID" = ? and "category" = ?`, [voteAmount, UUID, category]); } else { // Add a db entry - await db.prepare('run', `insert into "categoryVotes" ("UUID", "category", "votes") values (?, ?, ?)`, [UUID, category, voteAmount]); + await db.prepare("run", `insert into "categoryVotes" ("UUID", "category", "votes") values (?, ?, ?)`, [UUID, category, voteAmount]); } // Add the info into the private db if (usersLastVoteInfo?.votes > 0) { // Reverse the previous vote - await db.prepare('run', `update "categoryVotes" set "votes" = "votes" - ? where "UUID" = ? and "category" = ?`, [voteAmount, UUID, usersLastVoteInfo.category]); + await db.prepare("run", `update "categoryVotes" set "votes" = "votes" - ? where "UUID" = ? and "category" = ?`, [voteAmount, UUID, usersLastVoteInfo.category]); - await privateDB.prepare('run', `update "categoryVotes" set "category" = ?, "timeSubmitted" = ?, "hashedIP" = ? where "userID" = ? and "UUID" = ?`, [category, timeSubmitted, hashedIP, userID, UUID]); + await privateDB.prepare("run", `update "categoryVotes" set "category" = ?, "timeSubmitted" = ?, "hashedIP" = ? where "userID" = ? and "UUID" = ?`, [category, timeSubmitted, hashedIP, userID, UUID]); } else { - await privateDB.prepare('run', `insert into "categoryVotes" ("UUID", "userID", "hashedIP", "category", "timeSubmitted") values (?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, category, timeSubmitted]); + await privateDB.prepare("run", `insert into "categoryVotes" ("UUID", "userID", "hashedIP", "category", "timeSubmitted") values (?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, category, timeSubmitted]); } // See if the submissions category is ready to change @@ -235,7 +233,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i // VIPs change it every time if (nextCategoryCount - currentCategoryCount >= Math.max(Math.ceil(submissionInfo?.votes / 2), 2) || isVIP || isOwnSubmission) { // Replace the category - await db.prepare('run', `update "sponsorTimes" set "category" = ? where "UUID" = ?`, [category, UUID]); + await db.prepare("run", `update "sponsorTimes" set "category" = ? where "UUID" = ?`, [category, UUID]); } } @@ -279,7 +277,7 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise 0; + const isVIP = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?`, [nonAnonUserID])).userCount > 0; //check if user voting on own submission const isOwnSubmission = (await db.prepare("get", `SELECT "UUID" as "submissionCount" FROM "sponsorTimes" where "userID" = ? AND "UUID" = ?`, [nonAnonUserID, UUID])) !== undefined; @@ -289,13 +287,13 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise !!(await db.prepare('get', `SELECT "locked" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]))?.locked; - const isVideoLocked = async () => !!(await db.prepare('get', 'SELECT "lockCategories".category from "lockCategories" left join "sponsorTimes"' + - ' on ("lockCategories"."videoID" = "sponsorTimes"."videoID" and "lockCategories".category = "sponsorTimes".category)' + - ' where "UUID" = ?', [UUID])); + const isSegmentLocked = async () => !!(await db.prepare("get", `SELECT "locked" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]))?.locked; + const isVideoLocked = async () => !!(await db.prepare("get", `SELECT "lockCategories".category from "lockCategories" left join "sponsorTimes" + on ("lockCategories"."videoID" = "sponsorTimes"."videoID" and "lockCategories".category = "sponsorTimes".category) + where "UUID" = ?`, [UUID])); if (await isSegmentLocked() || await isVideoLocked()) { finalResponse.blockVote = true; @@ -310,7 +308,7 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise