fix non-format eslint in src/

This commit is contained in:
Michael C
2021-07-12 02:12:22 -04:00
parent 9445a06871
commit c0b1d201ad
66 changed files with 829 additions and 834 deletions

View File

@@ -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);

View File

@@ -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<Response> {
const userID = req.query.userID as string;
@@ -9,7 +9,7 @@ export async function addUserAsVIP(req: Request, res: Response): Promise<Respons
const enabled = req.query.enabled === undefined
? false
: req.query.enabled === 'true';
: req.query.enabled === "true";
if (userID == undefined || adminUserIDInput == undefined) {
//invalid request
@@ -25,14 +25,14 @@ export async function addUserAsVIP(req: Request, res: Response): Promise<Respons
}
//check to see if this user is already a vip
const row = await db.prepare('get', 'SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?', [userID]);
const row = await db.prepare("get", 'SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?', [userID]);
if (enabled && row.userCount == 0) {
//add them to the vip list
await db.prepare('run', 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
} else if (!enabled && row.userCount > 0) {
//remove them from the shadow ban list
await db.prepare('run', 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
await db.prepare("run", 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
}
return res.sendStatus(200);

View File

@@ -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<Response> {
// 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<void> {
@@ -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]);
}
}

View File

@@ -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 = `<p>The API and database follow <a href="https://creativec
const tables = config?.dumpDatabase?.tables ?? [];
const MILLISECONDS_BETWEEN_DUMPS = config?.dumpDatabase?.minTimeBetweenMs ?? ONE_MINUTE;
const appExportPath = config?.dumpDatabase?.appExportPath ?? './docker/database-export';
const postgresExportPath = config?.dumpDatabase?.postgresExportPath ?? '/opt/exports';
const appExportPath = config?.dumpDatabase?.appExportPath ?? "./docker/database-export";
const postgresExportPath = config?.dumpDatabase?.postgresExportPath ?? "/opt/exports";
const tableNames = tables.map(table => 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<void> {
files.forEach(file => {
// we only care about files that start with "<tablename>_" 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}
<h1>SponsorBlock database dumps</h1>${licenseHeader}
@@ -134,9 +134,9 @@ export default async function dumpDatabase(req: Request, res: Response, showPage
<td><a href="/database/${item.tableName}.csv">${item.tableName}.csv</a></td>
</tr>
`;
}).join('')}
${latestDumpFiles.length === 0 ? '<tr><td colspan="2">Please wait: Generating files</td></tr>' : ''}
</tbody>
}).join("")}
${latestDumpFiles.length === 0 ? '<tr><td colspan="2">Please wait: Generating files</td></tr>' : ""}
</tbody>
</table>
<hr/>
${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<number> {
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<void> {
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<void> {
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,

View File

@@ -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<Response> {
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

View File

@@ -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<Response> {
const userID = req.query.userID as UserID;

View File

@@ -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<Response> {
@@ -13,7 +13,7 @@ export async function getLockCategories(req: Request, res: Response): Promise<Re
try {
// Get existing lock categories markers
const lockedCategories = await db.prepare('all', 'SELECT "category" from "lockCategories" where "videoID" = ?', [videoID]) as {category: Category}[];
const lockedCategories = await db.prepare("all", 'SELECT "category" from "lockCategories" where "videoID" = ?', [videoID]) as {category: Category}[];
if (lockedCategories.length === 0 || !lockedCategories[0]) return res.sendStatus(404);
// map to array in JS becaues of SQL incompatibilities
const categories = Object.values(lockedCategories).map((entry) => entry.category);

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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<DBSegment> {
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"

View File

@@ -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<Segment[]> {
@@ -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<Segment[]> {
async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[],
actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise<Segment[]> {
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<SBRecord<VideoID, VideoData>> {
async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[],
actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise<SBRecord<VideoID, VideoData>> {
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
const segments: SBRecord<VideoID, VideoData> = {};
@@ -133,10 +133,10 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise<DBSegment[]> {
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<DBSegment[]>;
if (hashedVideoIDPrefix.length === 4) {
@@ -149,7 +149,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service
async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise<DBSegment[]> {
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<Segment[]
? Array.isArray(req.query.category)
? req.query.category
: [req.query.category]
: ['sponsor'];
: ["sponsor"];
if (!Array.isArray(categories)) {
res.status(400).send("Categories parameter does not match format requirements.");
return false;
@@ -312,7 +312,7 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
res.status(400).send("requiredSegments parameter does not match format requirements.");
return false;
}
let service: Service = req.query.service ?? req.body.service ?? Service.YouTube;
if (!Object.values(Service).some((val) => val == service)) {
service = Service.YouTube;

View File

@@ -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<Response> {
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");

View File

@@ -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<Response
}
//setup which sort type to use
let sortBy = '';
let sortBy = "";
if (sortType == 0) {
sortBy = 'minutesSaved';
sortBy = "minutesSaved";
} else if (sortType == 1) {
sortBy = 'viewCount';
sortBy = "viewCount";
} else if (sortType == 2) {
sortBy = 'totalSubmissions';
sortBy = "totalSubmissions";
} else {
//invalid request
return res.sendStatus(400);

View File

@@ -1,8 +1,8 @@
import {db} from '../databases/databases';
import {config} from '../config';
import {Request, Response} from 'express';
import fetch from 'node-fetch';
import {Logger} from '../utils/logger';
import {db} from "../databases/databases";
import {config} from "../config";
import {Request, Response} from "express";
import fetch from "node-fetch";
import {Logger} from "../utils/logger";
// A cache of the number of chrome web store users
let chromeUsersCache = 0;
@@ -16,7 +16,7 @@ let lastUserCountCheck = 0;
export async function getTotalStats(req: Request, res: Response): Promise<void> {
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<void>
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
// [...]<span><meta itemprop="interactionCount" content="UserDownloads:100.000+"/><meta itemprop="opera[...]
const matchingString = '"UserDownloads:';
const matchingStringLen = matchingString.length;
const userDownloadsStartIndex = body.indexOf(matchingString);
if (userDownloadsStartIndex >= 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
// [...]<span><meta itemprop="interactionCount" content="UserDownloads:100.000+"/><meta itemprop="opera[...]
const matchingString = '"UserDownloads:';
const matchingStringLen = matchingString.length;
const userDownloadsStartIndex = body.indexOf(matchingString);
if (userDownloadsStartIndex >= 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}`);
});
}

View File

@@ -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<Response>
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)) {

View File

@@ -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<number> {
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<number> {
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<SegmentUUID> {
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<Response
if (hashedUserID == undefined) {
//invalid request
return res.status(400).send('Parameters are not valid');
return res.status(400).send("Parameters are not valid");
}
const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID);

View File

@@ -1,7 +1,7 @@
import {db} from '../databases/databases';
import {getHash} from '../utils/getHash';
import {Logger} from '../utils/logger';
import {Request, Response} from 'express';
import {db} from "../databases/databases";
import {getHash} from "../utils/getHash";
import {Logger} from "../utils/logger";
import {Request, Response} from "express";
export async function getUsername(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
@@ -15,7 +15,7 @@ export async function getUsername(req: Request, res: Response): Promise<Response
userID = getHash(userID);
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 res.send({

View File

@@ -1,7 +1,7 @@
import {db} from '../databases/databases';
import {Request, Response} from 'express';
import {getHash} from '../utils/getHash';
import {Logger} from '../utils/logger';
import {db} from "../databases/databases";
import {Request, Response} from "express";
import {getHash} from "../utils/getHash";
import {Logger} from "../utils/logger";
export async function getViewsForUser(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
@@ -15,7 +15,7 @@ export async function getViewsForUser(req: Request, res: Response): Promise<Resp
userID = getHash(userID);
try {
const row = await db.prepare('get', `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
const row = await db.prepare("get", `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
//increase the view count by one
if (row.viewCount != null) {

View File

@@ -1,5 +1,5 @@
import {handleGetSegments} from './getSkipSegments';
import {Request, Response} from 'express';
import {handleGetSegments} from "./getSkipSegments";
import {Request, Response} from "express";
export async function oldGetVideoSponsorTimes(req: Request, res: Response): Promise<Response> {
const segments = await handleGetSegments(req, res);

View File

@@ -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<Response> {
req.query.category = "sponsor";

View File

@@ -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<Response> {
@@ -13,17 +13,17 @@ export async function postClearCache(req: Request, res: Response): Promise<Respo
const service = req.query.service as Service ?? Service.YouTube;
const invalidFields = [];
if (typeof videoID !== 'string') {
invalidFields.push('videoID');
if (typeof videoID !== "string") {
invalidFields.push("videoID");
}
if (typeof userID !== 'string') {
invalidFields.push('userID');
if (typeof userID !== "string") {
invalidFields.push("userID");
}
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 as early as possible
@@ -33,7 +33,7 @@ export async function postClearCache(req: Request, res: Response): Promise<Respo
// Ensure user is a VIP
if (!(await isUserVIP(hashedUserID))){
Logger.warn("Permission violation: User " + hashedUserID + " attempted to clear cache for video " + videoID + ".");
Logger.warn(`Permission violation: User ${hashedUserID} attempted to clear cache for video ${videoID}.`);
return res.status(403).json({"message": "Not a VIP"});
}
@@ -44,7 +44,7 @@ export async function postClearCache(req: Request, res: Response): Promise<Respo
service
});
return res.status(200).json({
message: "Cache cleared on video " + videoID
message: `Cache cleared on video ${videoID}`
});
} catch(err) {
return res.sendStatus(500);

View File

@@ -1,8 +1,8 @@
import {Logger} from '../utils/logger';
import {getHash} from '../utils/getHash';
import {isUserVIP} from '../utils/isUserVIP';
import {db} from '../databases/databases';
import {Request, Response} from 'express';
import {Logger} from "../utils/logger";
import {getHash} from "../utils/getHash";
import {isUserVIP} from "../utils/isUserVIP";
import {db} from "../databases/databases";
import {Request, Response} from "express";
import { VideoIDHash } from "../types/segments.model";
export async function postLockCategories(req: Request, res: Response): Promise<string[]> {
@@ -10,7 +10,7 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
const videoID = req.body.videoID;
let userID = req.body.userID;
const categories = req.body.categories;
const reason: string = req.body.reason ?? '';
const reason: string = req.body.reason ?? "";
// Check input data is valid
if (!videoID
@@ -20,7 +20,7 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
|| categories.length === 0
) {
res.status(400).json({
message: 'Bad Format',
message: "Bad Format",
});
return;
}
@@ -31,13 +31,13 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
if (!userIsVIP) {
res.status(403).json({
message: 'Must be a VIP to mark videos.',
message: "Must be a VIP to mark videos.",
});
return;
}
// Get existing lock categories markers
let noCategoryList = await db.prepare('all', 'SELECT "category" from "lockCategories" where "videoID" = ?', [videoID]);
let noCategoryList = await db.prepare("all", 'SELECT "category" from "lockCategories" where "videoID" = ?', [videoID]);
if (!noCategoryList || noCategoryList.length === 0) {
noCategoryList = [];
} else {
@@ -65,9 +65,9 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
// create database entry
for (const category of categoriesToMark) {
try {
await db.prepare('run', `INSERT INTO "lockCategories" ("videoID", "userID", "category", "hashedVideoID", "reason") VALUES(?, ?, ?, ?, ?)`, [videoID, userID, category, hashedVideoID, reason]);
await db.prepare("run", `INSERT INTO "lockCategories" ("videoID", "userID", "category", "hashedVideoID", "reason") VALUES(?, ?, ?, ?, ?)`, [videoID, userID, category, hashedVideoID, reason]);
} catch (err) {
Logger.error("Error submitting 'lockCategories' marker for category '" + category + "' for video '" + videoID + "'");
Logger.error(`Error submitting 'lockCategories' marker for category '${category}' for video '${videoID}'`);
Logger.error(err);
res.status(500).json({
message: "Internal Server Error: Could not write marker to the database.",
@@ -84,11 +84,11 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
for (const category of overlapCategories) {
try {
await db.prepare('run',
await db.prepare("run",
'UPDATE "lockCategories" SET "reason" = ?, "userID" = ? WHERE "videoID" = ? AND "category" = ?',
[reason, userID, videoID, category]);
} catch (err) {
Logger.error("Error submitting 'lockCategories' marker for category '" + category + "' for video '" + videoID + "'");
Logger.error(`Error submitting 'lockCategories' marker for category '${category}' for video '${videoID}'`);
Logger.error(err);
res.status(500).json({
message: "Internal Server Error: Could not write marker to the database.",

View File

@@ -1,10 +1,10 @@
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";
import {VideoID} from "../types/segments.model";
import {db} from '../databases/databases';
import {db} from "../databases/databases";
export async function postPurgeAllSegments(req: Request, res: Response): Promise<Response> {
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);

View File

@@ -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<Res
|| !endTime
) {
return res.status(400).json({
message: 'Bad Format',
message: "Bad Format",
});
}
@@ -69,12 +69,12 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
if (!userIsVIP) {
return res.status(403).json({
message: 'Must be a VIP to perform this action.',
message: "Must be a VIP to perform this action.",
});
}
try {
const segments = await db.prepare('all', 'SELECT "startTime", "endTime", "UUID" FROM "sponsorTimes" WHERE "videoID" = ?', [videoID]);
const segments = await db.prepare("all", 'SELECT "startTime", "endTime", "UUID" FROM "sponsorTimes" WHERE "videoID" = ?', [videoID]);
const shift = {
startTime,
endTime,
@@ -83,12 +83,12 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
for (const segment of segments) {
const result = shiftSegment(segment, shift);
switch (result.action) {
case ACTION_UPDATE:
await db.prepare('run', 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ? WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
case ACTION_REMOVE:
await db.prepare('run', 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ?, "votes" = -2 WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
case ACTION_UPDATE:
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ? WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
case ACTION_REMOVE:
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ?, "votes" = -2 WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
break;
}
}
} catch (err) {

View File

@@ -1,25 +1,25 @@
import {config} from '../config';
import {Logger} from '../utils/logger';
import {db, privateDB} from '../databases/databases';
import {getMaxResThumbnail, YouTubeAPI} from '../utils/youtubeApi';
import {getSubmissionUUID} from '../utils/getSubmissionUUID';
import fetch from 'node-fetch';
import {getHash} from '../utils/getHash';
import {getIP} from '../utils/getIP';
import {getFormattedTime} from '../utils/getFormattedTime';
import {isUserTrustworthy} from '../utils/isUserTrustworthy';
import {dispatchEvent} from '../utils/webhookUtils';
import {Request, Response} from 'express';
import { ActionType, Category, CategoryActionType, IncomingSegment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model';
import { deleteLockCategories } from './deleteLockCategories';
import { getCategoryActionType } from '../utils/categoryInfo';
import { QueryCacher } from '../utils/queryCacher';
import { getReputation } from '../utils/reputation';
import { APIVideoData, APIVideoInfo } from '../types/youtubeApi.model';
import { UserID } from '../types/user.model';
import {config} from "../config";
import {Logger} from "../utils/logger";
import {db, privateDB} from "../databases/databases";
import {getMaxResThumbnail, YouTubeAPI} from "../utils/youtubeApi";
import {getSubmissionUUID} from "../utils/getSubmissionUUID";
import fetch from "node-fetch";
import {getHash} from "../utils/getHash";
import {getIP} from "../utils/getIP";
import {getFormattedTime} from "../utils/getFormattedTime";
import {isUserTrustworthy} from "../utils/isUserTrustworthy";
import {dispatchEvent} from "../utils/webhookUtils";
import {Request, Response} from "express";
import { ActionType, Category, CategoryActionType, IncomingSegment, SegmentUUID, Service, VideoDuration, VideoID } from "../types/segments.model";
import { deleteLockCategories } from "./deleteLockCategories";
import { getCategoryActionType } from "../utils/categoryInfo";
import { QueryCacher } from "../utils/queryCacher";
import { getReputation } from "../utils/reputation";
import { APIVideoData, APIVideoInfo } from "../types/youtubeApi.model";
import { UserID } from "../types/user.model";
async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: APIVideoData, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) {
const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
const userName = row !== undefined ? row.userName : null;
let scopeName = "submissions.other";
@@ -32,7 +32,7 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st
"id": videoID,
"title": youtubeData?.title,
"thumbnail": getMaxResThumbnail(youtubeData) || null,
"url": "https://www.youtube.com/watch?v=" + videoID,
"url": `https://www.youtube.com/watch?v=${videoID}`,
},
"submission": {
"UUID": UUID,
@@ -49,7 +49,7 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st
async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: string, UUID: string, segmentInfo: any, service: Service) {
if (apiVideoInfo && service == Service.YouTube) {
const userSubmissionCountRow = await db.prepare('get', `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
const userSubmissionCountRow = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
const {data, err} = apiVideoInfo;
if (err) return;
@@ -64,13 +64,13 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID:
// If it is a first time submission
// Then send a notification to discord
if (config.discordFirstTimeSubmissionsWebhookURL === null || userSubmissionCountRow.submissionCount > 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<Res
segments.forEach((segment) => {
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<Res
return res.status(403).send(warningResult.errorMessage);
}
let lockedCategoryList = (await db.prepare('all', 'SELECT category from "lockCategories" where "videoID" = ?', [videoID])).map((list: any) => 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<Res
if (videoDurationChanged(videoDuration)) {
// Hide all previous submissions
for (const submission of previousSubmissions) {
await db.prepare('run', `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "UUID" = ?`, [submission.UUID]);
await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "UUID" = ?`, [submission.UUID]);
}
// Reset lock categories
@@ -417,7 +417,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
// Reject segment if it's in the locked categories list
if (!isVIP && lockedCategoryList.indexOf(segments[i].category) !== -1) {
// TODO: Do something about the fradulent submission
Logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'");
Logger.warn(`Caught a no-segment submission. userID: '${userID}', videoID: '${videoID}', category: '${segments[i].category}'`);
return res.status(403).send(
"New submissions are not allowed for the following category: '"
+ segments[i].category + "'. A moderator has decided that no new segments are needed and that all current segments of this category are timed perfectly.\n\n "
@@ -433,7 +433,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
if (isNaN(startTime) || isNaN(endTime)
|| startTime === Infinity || endTime === Infinity || startTime < 0 || startTime > 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<Res
}
//check if this info has already been submitted before
const duplicateCheck2Row = await db.prepare('get', `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "startTime" = ?
const duplicateCheck2Row = await db.prepare("get", `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "startTime" = ?
and "endTime" = ? and "category" = ? and "videoID" = ? and "service" = ?`, [startTime, endTime, segments[i].category, videoID, service]);
if (duplicateCheck2Row.count > 0) {
return res.sendStatus(409);
@@ -463,7 +463,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
//decreaseVotes = -2; //Disable for now
} else if (autoModerateResult) {
//Normal automod behavior
return res.status(403).send("Request rejected by auto moderator: " + autoModerateResult + " If this is an issue, send a message on Discord.");
return res.status(403).send(`Request rejected by auto moderator: ${autoModerateResult} If this is an issue, send a message on Discord.`);
}
}
// Will be filled when submitting
@@ -483,7 +483,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
// eslint-disable-next-line no-constant-condition
if (false) {
//check to see if this ip has submitted too many sponsors today
const rateLimitCheckRow = await privateDB.prepare('get', `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "hashedIP" = ? AND "videoID" = ? AND "timeSubmitted" > ?`, [hashedIP, videoID, yesterday]);
const rateLimitCheckRow = await privateDB.prepare("get", `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "hashedIP" = ? AND "videoID" = ? AND "timeSubmitted" > ?`, [hashedIP, videoID, yesterday]);
if (rateLimitCheckRow.count >= 10) {
//too many sponsors for the same video from the same ip address
@@ -495,7 +495,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
// eslint-disable-next-line no-constant-condition
if (false) {
//check to see if the user has already submitted sponsors for this video
const duplicateCheckRow = await db.prepare('get', `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ?`, [userID, videoID]);
const duplicateCheckRow = await db.prepare("get", `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ?`, [userID, videoID]);
if (duplicateCheckRow.count >= 16) {
//too many sponsors for the same video from the same user
@@ -504,7 +504,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
}
//check to see if this user is shadowbanned
const shadowBanRow = await db.prepare('get', `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]);
const shadowBanRow = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]);
let shadowBanned = shadowBanRow.userCount;
@@ -525,16 +525,16 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
const startingLocked = isVIP ? 1 : 0;
try {
await db.prepare('run', `INSERT INTO "sponsorTimes"
await db.prepare("run", `INSERT INTO "sponsorTimes"
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID")
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanned, hashedVideoID,
],
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanned, hashedVideoID,
],
);
//add to private db as well
await privateDB.prepare('run', `INSERT INTO "sponsorTimes" VALUES(?, ?, ?)`, [videoID, hashedIP, timeSubmitted]);
await privateDB.prepare("run", `INSERT INTO "sponsorTimes" VALUES(?, ?, ?)`, [videoID, hashedIP, timeSubmitted]);
// Clear redis cache for this video
QueryCacher.clearVideoCache({
videoID,
@@ -544,8 +544,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
});
} catch (err) {
//a DB change probably occurred
Logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
Logger.error(`Error when putting sponsorTime in the DB: ${videoID}, ${segmentInfo.segment[0]}, ${segmentInfo.segment[1]}, ${userID}, ${segmentInfo.category}. ${err}`);
return res.sendStatus(500);
}
@@ -567,18 +566,18 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
return res.json(newSegments);
}
// Takes an array of arrays:
// ex)
// Takes an array of arrays:
// ex)
// [
// [3, 40],
// [50, 70],
// [60, 80],
// [3, 40],
// [50, 70],
// [60, 80],
// [100, 150]
// ]
// ]
// => transforms to combining overlapping segments
// [
// [3, 40],
// [50, 80],
// [50, 80],
// [100, 150]
// ]
function mergeTimeSegments(ranges: number[][]) {

View File

@@ -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<Response> {
// Collect user input data
@@ -11,22 +11,22 @@ export async function postWarning(req: Request, res: Response): Promise<Response
const userID: UserID = req.body.userID;
const issueTime = new Date().getTime();
const enabled: boolean = req.body.enabled ?? true;
const reason: string = req.body.reason ?? '';
const reason: string = req.body.reason ?? "";
// Ensure user is a VIP
if (!await isUserVIP(issuerUserID)) {
Logger.warn("Permission violation: User " + issuerUserID + " attempted to warn user " + userID + ".");
Logger.warn(`Permission violation: User ${issuerUserID} attempted to warn user ${userID}.`);
return res.status(403).json({"message": "Not a VIP"});
}
let resultStatus = "";
if (enabled) {
const previousWarning = await db.prepare('get', 'SELECT * FROM "warnings" WHERE "userID" = ? AND "issuerUserID" = ?', [userID, issuerUserID]);
const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "issuerUserID" = ?', [userID, issuerUserID]);
if (!previousWarning) {
await db.prepare(
'run',
"run",
'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled", "reason") VALUES (?, ?, ?, 1, ?)',
[userID, issueTime, issuerUserID, reason]
);
@@ -35,11 +35,11 @@ export async function postWarning(req: Request, res: Response): Promise<Response
return res.sendStatus(409);
}
} else {
await db.prepare('run', 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ?', [userID]);
await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ?', [userID]);
resultStatus = "removed from";
}
return res.status(200).json({
message: "Warning " + resultStatus + " user '" + userID + "'.",
message: `Warning ${resultStatus} user '${userID}'.`,
});
}

View File

@@ -1,11 +1,11 @@
import {config} from '../config';
import {Logger} from '../utils/logger';
import {db, privateDB} from '../databases/databases';
import {getHash} from '../utils/getHash';
import {Request, Response} from 'express';
import {config} from "../config";
import {Logger} from "../utils/logger";
import {db, privateDB} from "../databases/databases";
import {getHash} from "../utils/getHash";
import {Request, Response} from "express";
async function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
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<Response
// Don't allow
return res.sendStatus(200);
}
// remove unicode control characters from username (example: \n, \r, \t etc.)
// source: https://en.wikipedia.org/wiki/Control_character#In_Unicode
// eslint-disable-next-line no-control-regex
userName = userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
userName = userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
if (adminUserIDInput != undefined) {
//this is the admin controlling the other users account, don't hash the controling account's ID
@@ -46,7 +46,7 @@ export async function setUsername(req: Request, res: Response): Promise<Response
}
try {
const row = await db.prepare('get', `SELECT count(*) as count FROM "userNames" WHERE "userID" = ? AND "locked" = '1'`, [userID]);
const row = await db.prepare("get", `SELECT count(*) as count FROM "userNames" WHERE "userID" = ? AND "locked" = '1'`, [userID]);
if (adminUserIDInput === undefined && row.count > 0) {
return res.sendStatus(200);
}
@@ -58,17 +58,17 @@ export async function setUsername(req: Request, res: Response): Promise<Response
try {
//check if username is already set
const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [userID]);
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [userID]);
const locked = adminUserIDInput === undefined ? 0 : 1;
let oldUserName = '';
let oldUserName = "";
if (row?.userName?.length > 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);

View File

@@ -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<Response> {
const userID = req.query.userID as string;
@@ -13,7 +13,7 @@ export async function shadowBanUser(req: Request, res: Response): Promise<Respon
const enabled = req.query.enabled === undefined
? true
: req.query.enabled === 'true';
: req.query.enabled === "true";
//if enabled is false and the old submissions should be made visible again
const unHideOldSubmissions = req.query.unHideOldSubmissions !== "false";
@@ -37,50 +37,50 @@ export async function shadowBanUser(req: Request, res: Response): Promise<Respon
if (userID) {
//check to see if this user is already shadowbanned
const row = await db.prepare('get', `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]);
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]);
if (enabled && row.userCount == 0) {
//add them to the shadow ban list
//add it to the table
await db.prepare('run', `INSERT INTO "shadowBannedUsers" VALUES(?)`, [userID]);
await db.prepare("run", `INSERT INTO "shadowBannedUsers" VALUES(?)`, [userID]);
//find all previous submissions and hide them
if (unHideOldSubmissions) {
await db.prepare('run', `UPDATE "sponsorTimes" SET "shadowHidden" = 1 WHERE "userID" = ? AND "category" in (${categories.map((c) => `'${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<Respon
//find all previous submissions and hide them
if (unHideOldSubmissions) {
await db.prepare('run', `UPDATE "sponsorTimes" SET "shadowHidden" = 1 WHERE "timeSubmitted" IN
await db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = 1 WHERE "timeSubmitted" IN
(SELECT "privateDB"."timeSubmitted" FROM "sponsorTimes" LEFT JOIN "privateDB"."sponsorTimes" as "privateDB" ON "sponsorTimes"."timeSubmitted"="privateDB"."timeSubmitted"
WHERE "privateDB"."hashedIP" = ?)`, [hashedIP]);
}

View File

@@ -1,5 +1,5 @@
import {db} from '../databases/databases';
import {Request, Response} from 'express';
import {db} from "../databases/databases";
import {Request, Response} from "express";
export async function viewedVideoSponsorTime(req: Request, res: Response): Promise<Response> {
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);
}

View File

@@ -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<Response> {
, hashedIP: HashedIP, finalResponse: FinalResponse, res: Response): Promise<Response> {
// 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<Re
const hashedIP: HashedIP = getHash((ip + config.globalSalt) as IPAddress);
//check if this user is on the vip list
const isVIP = (await db.prepare('get', `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?`, [nonAnonUserID])).userCount > 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<Re
// no longer allow type 10/11 alternative votes
return res.sendStatus(400);
}
// If not upvote
if (!isVIP && type !== 1) {
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]));
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<Re
if (type !== undefined && !isVIP && !isOwnSubmission) {
// Check if upvoting hidden segment
const voteInfo = await db.prepare('get', `SELECT votes FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
const voteInfo = await db.prepare("get", `SELECT votes FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
if (voteInfo && voteInfo.votes <= -2) {
if (type == 1) {
@@ -324,19 +322,19 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
const warningsCount = (await db.prepare('get', `SELECT count(*) as count FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1`,
const warningsCount = (await db.prepare("get", `SELECT count(*) as count FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1`,
[nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))],
)).count;
if (warningsCount >= config.maxNumberOfActiveWarnings) {
return res.status(403).send('Vote 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 res.status(403).send("Vote 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 voteTypeEnum = (type == 0 || type == 1 || type == 20) ? voteTypes.normal : voteTypes.incorrect;
try {
//check if vote has already happened
const votesRow = await privateDB.prepare('get', `SELECT "type" FROM "votes" WHERE "userID" = ? AND "UUID" = ?`, [userID, UUID]);
const votesRow = await privateDB.prepare("get", `SELECT "type" FROM "votes" WHERE "userID" = ? AND "UUID" = ?`, [userID, UUID]);
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future
let incrementAmount = 0;
@@ -381,7 +379,7 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
}
//check if the increment amount should be multiplied (downvotes have more power if there have been many views)
const videoInfo = await db.prepare('get', `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]) as
const videoInfo = await db.prepare("get", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]) as
{videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, votes: number, views: number, userID: UserID};
if (voteTypeEnum === voteTypes.normal) {
@@ -410,9 +408,9 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
if (ableToVote) {
//update the votes table
if (votesRow != undefined) {
await privateDB.prepare('run', `UPDATE "votes" SET "type" = ? WHERE "userID" = ? AND "UUID" = ?`, [type, userID, UUID]);
await privateDB.prepare("run", `UPDATE "votes" SET "type" = ? WHERE "userID" = ? AND "UUID" = ?`, [type, userID, UUID]);
} else {
await privateDB.prepare('run', `INSERT INTO "votes" VALUES(?, ?, ?, ?)`, [UUID, userID, hashedIP, type]);
await privateDB.prepare("run", `INSERT INTO "votes" VALUES(?, ?, ?, ?)`, [UUID, userID, hashedIP, type]);
}
let columnName = "";
@@ -424,13 +422,13 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
//update the vote count on this sponsorTime
//oldIncrementAmount will be zero is row is null
await db.prepare('run', 'UPDATE "sponsorTimes" SET "' + columnName + '" = "' + columnName + '" + ? WHERE "UUID" = ?', [incrementAmount - oldIncrementAmount, UUID]);
await db.prepare("run", `UPDATE "sponsorTimes" SET "${columnName}" = "${columnName}" + ? WHERE "UUID" = ?`, [incrementAmount - oldIncrementAmount, UUID]);
if (isVIP && incrementAmount > 0 && voteTypeEnum === voteTypes.normal) {
// Unide and Lock this submission
await db.prepare('run', 'UPDATE "sponsorTimes" SET locked = 1, hidden = 0 WHERE "UUID" = ?', [UUID]);
await db.prepare("run", 'UPDATE "sponsorTimes" SET locked = 1, hidden = 0 WHERE "UUID" = ?', [UUID]);
} else if (isVIP && incrementAmount < 0 && voteTypeEnum === voteTypes.normal) {
// Unlock if a VIP downvotes it
await db.prepare('run', 'UPDATE "sponsorTimes" SET locked = 0 WHERE "UUID" = ?', [UUID]);
// Unlock if a VIP downvotes it
await db.prepare("run", 'UPDATE "sponsorTimes" SET locked = 0 WHERE "UUID" = ?', [UUID]);
}
QueryCacher.clearVideoCache(videoInfo);
@@ -452,6 +450,6 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
return res.status(finalResponse.finalStatus).send(finalResponse.finalMessage ?? undefined);
} catch (err) {
Logger.error(err);
return res.status(500).json({error: 'Internal error creating segment vote'});
return res.status(500).json({error: "Internal error creating segment vote"});
}
}