Merge branch 'master' of https://github.com/ajayyy/SponsorBlockServer into getLockCategories

This commit is contained in:
Michael C
2021-07-04 23:33:21 -04:00
73 changed files with 3243 additions and 557 deletions

View File

@@ -1,4 +1,3 @@
import { time } from 'console';
import {Request, Response} from 'express';
import { db } from '../databases/databases';
import { Logger } from '../utils/logger';
@@ -10,15 +9,14 @@ import { Logger } from '../utils/logger';
* https://support.google.com/youtube/answer/9230970
*/
export function addUnlistedVideo(req: Request, res: Response) {
export function addUnlistedVideo(req: Request, res: Response): Response {
const videoID = req.body.videoID;
const year = req.body.year || 0;
const views = req.body.views || 0;
const channelID = req.body.channelID || "Unknown";
if (videoID === undefined || typeof(videoID) !== "string" || videoID.length !== 11) {
res.status(400).send("Invalid parameters");
return;
return res.status(400).send("Invalid parameters");
}
try {
@@ -26,10 +24,8 @@ export function addUnlistedVideo(req: Request, res: Response) {
db.prepare('run', `INSERT INTO "unlistedVideos" ("videoID", "year", "views", "channelID", "timeSubmitted") values (?, ?, ?, ?, ?)`, [videoID, year, views, channelID, timeSubmitted]);
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
res.sendStatus(200);
return res.sendStatus(200);
}

View File

@@ -3,7 +3,7 @@ import {db} from '../databases/databases';
import {config} from '../config';
import {Request, Response} from 'express';
export async function addUserAsVIP(req: Request, res: Response) {
export async function addUserAsVIP(req: Request, res: Response): Promise<Response> {
const userID = req.query.userID as string;
let adminUserIDInput = req.query.adminUserID as string;
@@ -13,8 +13,7 @@ export async function addUserAsVIP(req: Request, res: Response) {
if (userID == undefined || adminUserIDInput == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
@@ -22,8 +21,7 @@ export async function addUserAsVIP(req: Request, res: Response) {
if (adminUserIDInput !== config.adminUserID) {
//not authorized
res.sendStatus(403);
return;
return res.sendStatus(403);
}
//check to see if this user is already a vip
@@ -37,5 +35,5 @@ export async function addUserAsVIP(req: Request, res: Response) {
await db.prepare('run', 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
}
res.sendStatus(200);
return res.sendStatus(200);
}

View File

@@ -5,7 +5,7 @@ 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) {
export async function deleteLockCategoriesEndpoint(req: Request, res: Response): Promise<Response> {
// Collect user input data
const videoID = req.body.videoID as VideoID;
const userID = req.body.userID as UserID;
@@ -18,10 +18,9 @@ export async function deleteLockCategoriesEndpoint(req: Request, res: Response)
|| !Array.isArray(categories)
|| categories.length === 0
) {
res.status(400).json({
return res.status(400).json({
message: 'Bad Format',
});
return;
}
// Check if user is VIP
@@ -29,15 +28,14 @@ export async function deleteLockCategoriesEndpoint(req: Request, res: Response)
const userIsVIP = await isUserVIP(hashedUserID);
if (!userIsVIP) {
res.status(403).json({
return res.status(403).json({
message: 'Must be a VIP to mark videos.',
});
return;
}
await deleteLockCategories(videoID, categories);
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});
}
/**

View File

@@ -23,7 +23,7 @@ const styleHeader = `<style>
table tbody tr:nth-child(odd) {
background: #efefef;
}
</style>`
</style>`;
const licenseHeader = `<p>The API and database follow <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="nofollow">CC BY-NC-SA 4.0</a> unless you have explicit permission.</p>
<p><a href="https://gist.github.com/ajayyy/4b27dfc66e33941a45aeaadccb51de71">Attribution Template</a></p>
@@ -38,13 +38,13 @@ const tableNames = tables.map(table => table.name);
interface TableDumpList {
fileName: string;
tableName: string;
};
}
let latestDumpFiles: TableDumpList[] = [];
interface TableFile {
file: string,
timestamp: number
};
}
if (tables.length === 0) {
Logger.warn('[dumpDatabase] No tables configured');
@@ -55,7 +55,7 @@ let updateQueued = false;
let updateRunning = false;
function removeOutdatedDumps(exportPath: string): Promise<void> {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
// Get list of table names
// Create array for each table
const tableFiles: Record<string, TableFile[]> = tableNames.reduce((obj: any, tableName) => {
@@ -81,7 +81,7 @@ function removeOutdatedDumps(exportPath: string): Promise<void> {
});
});
for (let tableName in tableFiles) {
for (const tableName in tableFiles) {
const files = tableFiles[tableName].sort((a, b) => b.timestamp - a.timestamp);
for (let i = 2; i < files.length; i++) {
// remove old file
@@ -90,13 +90,12 @@ function removeOutdatedDumps(exportPath: string): Promise<void> {
});
}
}
resolve();
});
});
}
export default async function dumpDatabase(req: Request, res: Response, showPage: boolean) {
export default async function dumpDatabase(req: Request, res: Response, showPage: boolean): Promise<void> {
if (!config?.dumpDatabase?.enabled) {
res.status(404).send("Database dump is disabled");
return;
@@ -108,7 +107,7 @@ export default async function dumpDatabase(req: Request, res: Response, showPage
updateQueueTime();
res.status(200)
res.status(200);
if (showPage) {
res.send(`${styleHeader}
@@ -153,7 +152,7 @@ export default async function dumpDatabase(req: Request, res: Response, showPage
size: item.fileSize,
};
}),
})
});
}
await queueDump();
@@ -182,7 +181,7 @@ export async function redirectLink(req: Request, res: Response): Promise<void> {
if (file) {
res.redirect("/download/" + file.fileName);
} else {
res.status(404).send();
res.sendStatus(404);
}
await queueDump();

View File

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

View File

@@ -4,28 +4,25 @@ 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<void> {
export async function getIsUserVIP(req: Request, res: Response): Promise<Response> {
const userID = req.query.userID as UserID;
if (userID == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
const hashedUserID: HashedUserID = getHash(userID);
try {
let vipState = await isUserVIP(hashedUserID);
res.status(200).json({
const vipState = await isUserVIP(hashedUserID);
return res.status(200).json({
hashedUserID: hashedUserID,
vip: vipState,
});
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
}

View File

@@ -6,32 +6,29 @@ import { Logger } from '../utils/logger';
const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400;
export async function getSavedTimeForUser(req: Request, res: Response) {
export async function getSavedTimeForUser(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
if (userID == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
userID = getHash(userID);
try {
let row = await db.prepare("get", 'SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -1 AND "shadowHidden" != 1 ', [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]);
const row = await db.prepare("get", 'SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -1 AND "shadowHidden" != 1 ', [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]);
if (row.minutesSaved != null) {
res.send({
return res.send({
timeSaved: row.minutesSaved,
});
} else {
res.sendStatus(404);
return res.sendStatus(404);
}
} catch (err) {
Logger.error("getSavedTimeForUser " + err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
}

View File

@@ -2,7 +2,7 @@ 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)
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 {
@@ -18,7 +18,7 @@ async function getSegmentFromDBByUUID(UUID: SegmentUUID): Promise<DBSegment> {
async function getSegmentsByUUID(UUIDs: SegmentUUID[]): Promise<DBSegment[]> {
const DBSegments: DBSegment[] = [];
for (let UUID of UUIDs) {
for (const UUID of UUIDs) {
// if UUID is invalid, skip
if (!isValidSegmentUUID(UUID)) continue;
DBSegments.push(await getSegmentFromDBByUUID(UUID as SegmentUUID));
@@ -26,7 +26,7 @@ async function getSegmentsByUUID(UUIDs: SegmentUUID[]): Promise<DBSegment[]> {
return DBSegments;
}
async function handleGetSegmentInfo(req: Request, res: Response) {
async function handleGetSegmentInfo(req: Request, res: Response): Promise<DBSegment[]> {
// If using params instead of JSON, only one UUID can be pulled
let UUIDs = req.query.UUIDs
? JSON.parse(req.query.UUIDs as string)
@@ -41,35 +41,35 @@ async function handleGetSegmentInfo(req: Request, res: Response) {
if (UUIDs.length > 10) UUIDs = UUIDs.slice(0, 10);
if (!Array.isArray(UUIDs) || !UUIDs) {
res.status(400).send("UUIDs parameter does not match format requirements.");
return false;
return;
}
const DBSegments = await getSegmentsByUUID(UUIDs);
// all uuids failed lookup
if (!DBSegments?.length) {
res.sendStatus(400);
return false;
return;
}
// uuids valid but not found
if (DBSegments[0] === null || DBSegments[0] === undefined) {
res.sendStatus(400);
return false;
return;
}
return DBSegments;
}
async function endpoint(req: Request, res: Response): Promise<void> {
async function endpoint(req: Request, res: Response): Promise<Response> {
try {
const DBSegments = await handleGetSegmentInfo(req, res);
// If false, res.send has already been called
if (DBSegments) {
//send result
res.send(DBSegments);
return res.send(DBSegments);
}
} catch (err) {
if (err instanceof SyntaxError) { // catch JSON.parse error
res.status(400).send("UUIDs parameter does not match format requirements.");
} else res.status(500).send();
return res.status(400).send("UUIDs parameter does not match format requirements.");
} else return res.sendStatus(500);
}
}

View File

@@ -8,7 +8,7 @@ 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 { QueryCacher } from '../utils/queryCacher';
import { getReputation } from '../utils/reputation';
@@ -41,7 +41,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category:
const filteredSegments = segments.filter((_, index) => shouldFilter[index]);
const maxSegments = getCategoryActionType(category) === CategoryActionType.Skippable ? 32 : 1
const maxSegments = getCategoryActionType(category) === CategoryActionType.Skippable ? 32 : 1;
return (await chooseSegments(filteredSegments, maxSegments)).map((chosenSegment) => ({
category,
segment: [chosenSegment.startTime, chosenSegment.endTime],
@@ -138,7 +138,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service
) as Promise<DBSegment[]>;
if (hashedVideoIDPrefix.length === 4) {
return await QueryCacher.get(fetchFromDB, skipSegmentsHashKey(hashedVideoIDPrefix, service))
return await QueryCacher.get(fetchFromDB, skipSegmentsHashKey(hashedVideoIDPrefix, service));
}
return await fetchFromDB();
@@ -153,7 +153,7 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P
[videoID, service]
) as Promise<DBSegment[]>;
return await QueryCacher.get(fetchFromDB, skipSegmentsKey(videoID, service))
return await QueryCacher.get(fetchFromDB, skipSegmentsKey(videoID, service));
}
//gets a weighted random choice from the choices array based on their `votes` property.
@@ -171,7 +171,7 @@ function getWeightedRandomChoice<T extends VotableObject>(choices: T[], amountOf
//assign a weight to each choice
let totalWeight = 0;
let choicesWithWeights: TWithWeight[] = choices.map(choice => {
const choicesWithWeights: TWithWeight[] = choices.map(choice => {
const boost = Math.min(choice.reputation, 4);
//The 3 makes -2 the minimum votes before being ignored completely
@@ -241,7 +241,7 @@ async function chooseSegments(segments: DBSegment[], max: number): Promise<DBSeg
}
cursor = Math.max(cursor, segment.endTime);
};
}
overlappingSegmentsGroups.forEach((group) => {
if (group.required) {
@@ -313,26 +313,25 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
if (segments.length === 0) {
res.sendStatus(404);
return false;
}
return segments;
}
async function endpoint(req: Request, res: Response): Promise<void> {
async function endpoint(req: Request, res: Response): Promise<Response> {
try {
const segments = await handleGetSegments(req, res);
// If false, res.send has already been called
if (segments) {
//send result
res.send(segments);
return res.send(segments);
}
} catch (err) {
if (err instanceof SyntaxError) {
res.status(400).send("Categories parameter does not match format requirements.");
} else res.status(500).send();
return res.status(400).send("Categories parameter does not match format requirements.");
} else return res.sendStatus(500);
}
}

View File

@@ -3,11 +3,10 @@ import {getSegmentsByHash} from './getSkipSegments';
import {Request, Response} from 'express';
import { Category, SegmentUUID, Service, VideoIDHash } from '../types/segments.model';
export async function getSkipSegmentsByHash(req: Request, res: Response) {
export async function getSkipSegmentsByHash(req: Request, res: Response): Promise<Response> {
let hashPrefix = req.params.prefix as VideoIDHash;
if (!hashPrefixTester(req.params.prefix)) {
res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
return;
return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
}
hashPrefix = hashPrefix.toLowerCase() as VideoIDHash;
@@ -61,6 +60,5 @@ export async function getSkipSegmentsByHash(req: Request, res: Response) {
hash: data.hash,
segments: data.segments,
}));
res.status(output.length === 0 ? 404 : 200).json(output);
return res.status(output.length === 0 ? 404 : 200).json(output);
}

View File

@@ -7,7 +7,7 @@ const MILLISECONDS_IN_MINUTE = 60000;
const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE);
const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400;
async function generateTopUsersStats(sortBy: string, categoryStatsEnabled: boolean = false) {
async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = false) {
const userNames = [];
const viewCounts = [];
const totalSubmissions = [];
@@ -63,14 +63,13 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled: boole
};
}
export async function getTopUsers(req: Request, res: Response) {
export async function getTopUsers(req: Request, res: Response): Promise<Response> {
const sortType = parseInt(req.query.sortType as string);
const categoryStatsEnabled = req.query.categoryStats;
if (sortType == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//setup which sort type to use
@@ -89,5 +88,5 @@ export async function getTopUsers(req: Request, res: Response) {
const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled);
//send this result
res.send(stats);
return res.send(stats);
}

View File

@@ -13,14 +13,14 @@ let apiUsersCache = 0;
let lastUserCountCheck = 0;
export async function getTotalStats(req: Request, res: Response) {
export async function getTotalStats(req: Request, res: Response): Promise<void> {
const userCountQuery = `(SELECT COUNT(*) FROM (SELECT DISTINCT "userID" from "sponsorTimes") t) "userCount",`;
let 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) {
let extensionUsers = chromeUsersCache + firefoxUsersCache;
const extensionUsers = chromeUsersCache + firefoxUsersCache;
//send this result
res.send({
@@ -33,7 +33,7 @@ export async function getTotalStats(req: Request, res: Response) {
});
// Check if the cache should be updated (every ~14 hours)
let now = Date.now();
const now = Date.now();
if (now - lastUserCountCheck > 5000000) {
lastUserCountCheck = now;
@@ -79,7 +79,7 @@ function updateExtensionUsers() {
})
.catch(() => Logger.debug("Failing to connect to " + chromeExtensionUrl));
})
.catch(err => {
.catch(() => {
Logger.debug("Failing to connect to " + mozillaAddonsUrl);
});
}

View File

@@ -2,7 +2,7 @@ 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 }[]> {
function getFuzzyUserID(userName: string): Promise<{userName: string, userID: UserID }[]> {
// escape [_ % \] to avoid ReDOS
userName = userName.replace(/\\/g, '\\\\')
.replace(/_/g, '\\_')
@@ -11,13 +11,13 @@ function getFuzzyUserID(userName: String): Promise<{userName: String, userID: Us
// LIMIT to reduce overhead | ESCAPE to escape LIKE wildcards
try {
return db.prepare('all', `SELECT "userName", "userID" FROM "userNames" WHERE "userName"
LIKE ? ESCAPE '\\' LIMIT 10`, [userName])
LIKE ? ESCAPE '\\' LIMIT 10`, [userName]);
} catch (err) {
return null;
}
}
function getExactUserID(userName: String): Promise<{userName: String, userID: UserID }[]> {
function getExactUserID(userName: string): Promise<{userName: string, userID: UserID }[]> {
try {
return db.prepare('all', `SELECT "userName", "userID" from "userNames" WHERE "userName" = ? LIMIT 10`, [userName]);
} catch (err) {
@@ -25,31 +25,27 @@ function getExactUserID(userName: String): Promise<{userName: String, userID: Us
}
}
export async function getUserID(req: Request, res: Response) {
let userName = req.query.username as string;
export async function getUserID(req: Request, res: Response): Promise<Response> {
const userName = req.query.username as string;
const exactSearch = req.query.exact
? req.query.exact == "true"
: false as Boolean;
: false as boolean;
// if not exact and length is 1, also skip
if (userName == undefined || userName.length > 64 ||
(!exactSearch && userName.length < 3)) {
// invalid request
res.sendStatus(400);
return false;
return res.sendStatus(400);
}
const results = exactSearch
? await getExactUserID(userName)
: await getFuzzyUserID(userName);
if (results === undefined || results === null) {
res.sendStatus(500);
return false;
return res.sendStatus(500);
} else if (results.length === 0) {
res.sendStatus(404);
return false;
return res.sendStatus(404);
} else {
res.send(results);
return false;
return res.send(results);
}
}

View File

@@ -9,7 +9,7 @@ import { SegmentUUID } from "../types/segments.model";
async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> {
try {
let row = await db.prepare("get", `SELECT SUM((("endTime" - "startTime") / 60) * "views") as "minutesSaved",
const row = await db.prepare("get", `SELECT SUM((("endTime" - "startTime") / 60) * "views") as "minutesSaved",
count(*) as "segmentCount" FROM "sponsorTimes"
WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID]);
if (row.minutesSaved != null) {
@@ -30,8 +30,8 @@ async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ min
async function dbGetIgnoredSegmentCount(userID: HashedUserID): Promise<number> {
try {
let row = await db.prepare("get", `SELECT COUNT(*) as "ignoredSegmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]);
return row?.ignoredSegmentCount ?? 0
const row = await db.prepare("get", `SELECT COUNT(*) as "ignoredSegmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]);
return row?.ignoredSegmentCount ?? 0;
} catch (err) {
return null;
}
@@ -39,7 +39,7 @@ async function dbGetIgnoredSegmentCount(userID: HashedUserID): Promise<number> {
async function dbGetUsername(userID: HashedUserID) {
try {
let 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 {
let 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 {
let 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,7 +71,7 @@ async function dbGetIgnoredViewsForUser(userID: HashedUserID) {
async function dbGetWarningsForUser(userID: HashedUserID): Promise<number> {
try {
let 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');
@@ -81,26 +81,25 @@ async function dbGetWarningsForUser(userID: HashedUserID): Promise<number> {
async function dbGetLastSegmentForUser(userID: HashedUserID): Promise<SegmentUUID> {
try {
let 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;
}
}
export async function getUserInfo(req: Request, res: Response) {
export async function getUserInfo(req: Request, res: Response): Promise<Response> {
const userID = req.query.userID as UserID;
const hashedUserID: HashedUserID = userID ? getHash(userID) : req.query.publicUserID as HashedUserID;
if (hashedUserID == undefined) {
//invalid request
res.status(400).send('Parameters are not valid');
return;
return res.status(400).send('Parameters are not valid');
}
const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID);
if (segmentsSummary) {
res.send({
return res.send({
userID: hashedUserID,
userName: await dbGetUsername(hashedUserID),
minutesSaved: segmentsSummary.minutesSaved,
@@ -114,6 +113,6 @@ export async function getUserInfo(req: Request, res: Response) {
lastSegmentID: await dbGetLastSegmentForUser(hashedUserID),
});
} else {
res.status(400).send();
return res.sendStatus(400);
}
}

View File

@@ -3,35 +3,32 @@ import {getHash} from '../utils/getHash';
import {Logger} from '../utils/logger';
import {Request, Response} from 'express';
export async function getUsername(req: Request, res: Response) {
export async function getUsername(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
if (userID == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
userID = getHash(userID);
try {
let 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) {
res.send({
return res.send({
userName: row.userName,
});
} else {
//no username yet, just send back the userID
res.send({
return res.send({
userName: userID,
});
}
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
}

View File

@@ -3,33 +3,30 @@ import {Request, Response} from 'express';
import {getHash} from '../utils/getHash';
import {Logger} from '../utils/logger';
export async function getViewsForUser(req: Request, res: Response) {
export async function getViewsForUser(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
if (userID == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
userID = getHash(userID);
try {
let 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) {
res.send({
return res.send({
viewCount: row.viewCount,
});
} else {
res.sendStatus(404);
return res.sendStatus(404);
}
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
}

View File

@@ -1,20 +1,20 @@
import {handleGetSegments} from './getSkipSegments';
import {Request, Response} from 'express';
export async function oldGetVideoSponsorTimes(req: Request, res: Response): Promise<void> {
let segments = await handleGetSegments(req, res);
export async function oldGetVideoSponsorTimes(req: Request, res: Response): Promise<Response> {
const segments = await handleGetSegments(req, res);
if (segments) {
// Convert to old outputs
let sponsorTimes = [];
let UUIDs = [];
const sponsorTimes = [];
const UUIDs = [];
for (const segment of segments) {
sponsorTimes.push(segment.segment);
UUIDs.push(segment.UUID);
}
res.send({
return res.send({
sponsorTimes,
UUIDs,
});

View File

@@ -1,8 +1,7 @@
import {postSkipSegments} from './postSkipSegments';
import {Request, Response} from 'express';
export async function oldSubmitSponsorTimes(req: Request, res: Response) {
export async function oldSubmitSponsorTimes(req: Request, res: Response): Promise<Response> {
req.query.category = "sponsor";
return postSkipSegments(req, res);
}

View File

@@ -7,9 +7,9 @@ import { QueryCacher } from '../utils/queryCacher';
import { isUserVIP } from '../utils/isUserVIP';
import { VideoIDHash } from "../types/segments.model";
export async function postClearCache(req: Request, res: Response) {
export async function postClearCache(req: Request, res: Response): Promise<Response> {
const videoID = req.query.videoID as VideoID;
let userID = req.query.userID as UserID;
const userID = req.query.userID as UserID;
const service = req.query.service as Service ?? Service.YouTube;
const invalidFields = [];
@@ -23,8 +23,7 @@ export async function postClearCache(req: Request, res: Response) {
if (invalidFields.length !== 0) {
// invalid request
const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ', ' : '') + c, '');
res.status(400).send(`No valid ${fields} field(s) provided`);
return false;
return res.status(400).send(`No valid ${fields} field(s) provided`);
}
// hash the userID as early as possible
@@ -35,8 +34,7 @@ export async function postClearCache(req: Request, res: Response) {
// Ensure user is a VIP
if (!(await isUserVIP(hashedUserID))){
Logger.warn("Permission violation: User " + hashedUserID + " attempted to clear cache for video " + videoID + ".");
res.status(403).json({"message": "Not a VIP"});
return false;
return res.status(403).json({"message": "Not a VIP"});
}
try {
@@ -45,11 +43,10 @@ export async function postClearCache(req: Request, res: Response) {
hashedVideoID,
service
});
res.status(200).json({
return res.status(200).json({
message: "Cache cleared on video " + videoID
});
} catch(err) {
res.status(500).send()
return false;
return res.sendStatus(500);
}
}

View File

@@ -4,11 +4,11 @@ import {isUserVIP} from '../utils/isUserVIP';
import {db} from '../databases/databases';
import {Request, Response} from 'express';
export async function postLockCategories(req: Request, res: Response) {
export async function postLockCategories(req: Request, res: Response): Promise<string[]> {
// Collect user input data
let videoID = req.body.videoID;
const videoID = req.body.videoID;
let userID = req.body.userID;
let categories = req.body.categories;
const categories = req.body.categories;
// Check input data is valid
if (!videoID
@@ -25,7 +25,7 @@ export async function postLockCategories(req: Request, res: Response) {
// Check if user is VIP
userID = getHash(userID);
let userIsVIP = await isUserVIP(userID);
const userIsVIP = await isUserVIP(userID);
if (!userIsVIP) {
res.status(403).json({
@@ -67,7 +67,7 @@ export async function postLockCategories(req: Request, res: Response) {
message: "Internal Server Error: Could not write marker to the database.",
});
}
};
}
res.status(200).json({
submitted: categoriesToMark,

View File

@@ -6,36 +6,31 @@ import {HashedUserID, UserID} from '../types/user.model';
import {VideoID} from "../types/segments.model";
import {db} from '../databases/databases';
export async function postPurgeAllSegments(req: Request, res: Response): Promise<void> {
export async function postPurgeAllSegments(req: Request, res: Response): Promise<Response> {
const userID = req.body.userID as UserID;
const videoID = req.body.videoID as VideoID;
if (userID == undefined) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
const hashedUserID: HashedUserID = getHash(userID);
try {
let vipState = await isUserVIP(hashedUserID);
const vipState = await isUserVIP(hashedUserID);
if (!vipState) {
res.status(403).json({
return res.status(403).json({
message: 'Must be a VIP to perform this action.',
});
return;
}
await db.prepare('run', `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "videoID" = ?`, [videoID]);
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
res.sendStatus(200);
return res.sendStatus(200);
}

View File

@@ -58,10 +58,9 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
|| !startTime
|| !endTime
) {
res.status(400).json({
return res.status(400).json({
message: 'Bad Format',
});
return;
}
// Check if user is VIP
@@ -69,10 +68,9 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
const userIsVIP = await isUserVIP(userID);
if (!userIsVIP) {
res.status(403).json({
return res.status(403).json({
message: 'Must be a VIP to perform this action.',
});
return;
}
try {
@@ -92,11 +90,11 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
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) {
Logger.error(err);
res.sendStatus(500);
return res.sendStatus(500);
}
res.sendStatus(200);
return res.sendStatus(200);
}

View File

@@ -10,9 +10,7 @@ import {getFormattedTime} from '../utils/getFormattedTime';
import {isUserTrustworthy} from '../utils/isUserTrustworthy';
import {dispatchEvent} from '../utils/webhookUtils';
import {Request, Response} from 'express';
import { skipSegmentsHashKey, skipSegmentsKey } from '../utils/redisKeys';
import redis from '../utils/redis';
import { Category, CategoryActionType, IncomingSegment, Segment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model';
import { 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';
@@ -175,9 +173,6 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo,
const segments = submission.segments;
let nbString = "";
for (let i = 0; i < segments.length; i++) {
const startTime = parseFloat(segments[i].segment[0]);
const endTime = parseFloat(segments[i].segment[1]);
if (duration == 0) {
// Allow submission if the duration is 0 (bug in youtube api)
return false;
@@ -201,8 +196,8 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo,
//add segments they are trying to add in this submission
for (let i = 0; i < segments.length; i++) {
let startTime = parseFloat(segments[i].segment[0]);
let endTime = parseFloat(segments[i].segment[1]);
const startTime = parseFloat(segments[i].segment[0]);
const endTime = parseFloat(segments[i].segment[1]);
allSegmentTimes.push([startTime, endTime]);
}
@@ -285,7 +280,7 @@ async function checkUserActiveWarning(userID: string): Promise<{ pass: boolean;
Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR)),
config.maxNumberOfActiveWarnings
],
) as {reason: string}[]
) 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?';
@@ -307,12 +302,12 @@ function proxySubmission(req: Request) {
.then(async res => {
Logger.debug('Proxy Submission: ' + res.status + ' (' + (await res.text()) + ')');
})
.catch(err => {
.catch(() => {
Logger.error("Proxy Submission: Failed to make call");
});
}
export async function postSkipSegments(req: Request, res: Response) {
export async function postSkipSegments(req: Request, res: Response): Promise<Response> {
if (config.proxySubmission) {
proxySubmission(req);
}
@@ -346,10 +341,9 @@ export async function postSkipSegments(req: Request, res: Response) {
}
if (invalidFields.length !== 0) {
// invalid request
const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ', ' : '') + c, '');
res.status(400).send(`No valid ${fields} field(s) provided`);
return;
// 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
@@ -360,9 +354,7 @@ export async function postSkipSegments(req: Request, res: Response) {
return res.status(403).send(warningResult.errorMessage);
}
let lockedCategoryList = (await db.prepare('all', 'SELECT category from "lockCategories" where "videoID" = ?', [videoID])).map((list: any) => {
return 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;
@@ -407,54 +399,48 @@ export async function postSkipSegments(req: Request, res: Response) {
for (let i = 0; i < segments.length; i++) {
if (segments[i] === undefined || segments[i].segment === undefined || segments[i].category === undefined) {
//invalid request
res.status(400).send("One of your segments are invalid");
return;
return res.status(400).send("One of your segments are invalid");
}
if (!config.categoryList.includes(segments[i].category)) {
res.status(400).send("Category doesn't exist.");
return;
return res.status(400).send("Category doesn't exist.");
}
// 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 + "'");
res.status(403).send(
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 "
+ (segments[i].category === "sponsor" ? "Maybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " +
"Categories that aren't sponsor, such as self-promotion can be enabled in the options.\n\n" : "")
+ "If you believe this is incorrect, please contact someone on discord.gg/SponsorBlock or matrix.to/#/+sponsorblock:ajay.app",
);
return;
}
let startTime = parseFloat(segments[i].segment[0]);
let endTime = parseFloat(segments[i].segment[1]);
const startTime = parseFloat(segments[i].segment[0]);
const endTime = parseFloat(segments[i].segment[1]);
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.POI && startTime !== endTime)) {
//invalid request
res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)");
return;
return res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)");
}
if (!isVIP && segments[i].category === "sponsor" && Math.abs(startTime - endTime) < 1) {
// Too short
res.status(400).send("Sponsors must be longer than 1 second long");
return;
return res.status(400).send("Sponsors must be longer than 1 second long");
}
//check if this info has already been submitted before
const duplicateCheck2Row = await db.prepare('get', `SELECT COUNT(*) as count FROM "sponsorTimes" WHERE "startTime" = ?
and "endTime" = ? and "category" = ? and "videoID" = ? and "service" = ?`, [startTime, endTime, segments[i].category, videoID, service]);
if (duplicateCheck2Row.count > 0) {
res.sendStatus(409);
return;
return res.sendStatus(409);
}
}
@@ -469,8 +455,7 @@ export async function postSkipSegments(req: Request, res: Response) {
//decreaseVotes = -2; //Disable for now
} else if (autoModerateResult) {
//Normal automod behavior
res.status(403).send("Request rejected by auto moderator: " + autoModerateResult + " If this is an issue, send a message on Discord.");
return;
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
@@ -487,28 +472,26 @@ export async function postSkipSegments(req: Request, res: Response) {
const yesterday = timeSubmitted - 86400000;
// Disable IP ratelimiting for now
// 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]);
if (rateLimitCheckRow.count >= 10) {
//too many sponsors for the same video from the same ip address
res.sendStatus(429);
return;
return res.sendStatus(429);
}
}
// Disable max submissions for now
// 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]);
if (duplicateCheckRow.count >= 16) {
//too many sponsors for the same video from the same user
res.sendStatus(429);
return;
return res.sendStatus(429);
}
}
@@ -522,7 +505,7 @@ export async function postSkipSegments(req: Request, res: Response) {
shadowBanned = 1;
}
let startingVotes = 0 + decreaseVotes;
const startingVotes = 0 + decreaseVotes;
const reputation = await getReputation(userID);
for (const segmentInfo of segments) {
@@ -553,11 +536,9 @@ export async function postSkipSegments(req: Request, res: Response) {
});
} catch (err) {
//a DB change probably occurred
res.sendStatus(500);
Logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
return;
return res.sendStatus(500);
}
UUIDs.push(UUID);
@@ -569,17 +550,13 @@ export async function postSkipSegments(req: Request, res: Response) {
}
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
res.json(newSegments);
for (let i = 0; i < segments.length; i++) {
sendWebhooks(apiVideoInfo, userID, videoID, UUIDs[i], segments[i], service);
}
return res.json(newSegments);
}
// Takes an array of arrays:

View File

@@ -5,7 +5,7 @@ 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) {
export async function postWarning(req: Request, res: Response): Promise<Response> {
// Collect user input data
const issuerUserID: HashedUserID = getHash(<UserID> req.body.issuerUserID);
const userID: UserID = req.body.userID;
@@ -16,14 +16,13 @@ export async function postWarning(req: Request, res: Response) {
// Ensure user is a VIP
if (!await isUserVIP(issuerUserID)) {
Logger.warn("Permission violation: User " + issuerUserID + " attempted to warn user " + userID + ".");
res.status(403).json({"message": "Not a VIP"});
return;
return res.status(403).json({"message": "Not a VIP"});
}
let resultStatus = "";
if (enabled) {
let 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(
@@ -33,15 +32,14 @@ export async function postWarning(req: Request, res: Response) {
);
resultStatus = "issued to";
} else {
res.status(409).send();
return;
return res.sendStatus(409);
}
} else {
await db.prepare('run', 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ?', [userID]);
resultStatus = "removed from";
}
res.status(200).json({
return res.status(200).json({
message: "Warning " + resultStatus + " user '" + userID + "'.",
});
}

View File

@@ -4,14 +4,14 @@ 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<void> {
async function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
return privateDB.prepare('run',
`INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedByAdmin", "updatedAt") VALUES(?, ?, ?, ?, ?)`,
[userID, newUserName, oldUserName, + updatedByAdmin, new Date().getTime()]
);
}
export async function setUsername(req: Request, res: Response) {
export async function setUsername(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string;
let userName = req.query.username as string;
@@ -19,18 +19,17 @@ export async function setUsername(req: Request, res: Response) {
if (userID == undefined || userName == undefined || userID === "undefined" || userName.length > 64) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
if (userName.includes("discord")) {
// Don't allow
res.sendStatus(200);
return;
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, '');
if (adminUserIDInput != undefined) {
@@ -39,8 +38,7 @@ export async function setUsername(req: Request, res: Response) {
if (adminUserIDInput != config.adminUserID) {
//they aren't the admin
res.sendStatus(403);
return;
return res.sendStatus(403);
}
} else {
//hash the userID
@@ -50,14 +48,12 @@ export async function setUsername(req: Request, res: Response) {
try {
const row = await db.prepare('get', `SELECT count(*) as count FROM "userNames" WHERE "userID" = ? AND "locked" = '1'`, [userID]);
if (adminUserIDInput === undefined && row.count > 0) {
res.sendStatus(200);
return;
return res.sendStatus(200);
}
}
catch (error) {
Logger.error(error);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
try {
@@ -77,11 +73,9 @@ export async function setUsername(req: Request, res: Response) {
await logUserNameChange(userID, userName, oldUserName, adminUserIDInput !== undefined);
res.sendStatus(200);
return res.sendStatus(200);
} catch (err) {
Logger.error(err);
res.sendStatus(500);
return;
return res.sendStatus(500);
}
}

View File

@@ -6,7 +6,7 @@ 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) {
export async function shadowBanUser(req: Request, res: Response): Promise<Response> {
const userID = req.query.userID as string;
const hashedIP = req.query.hashedIP as string;
let adminUserIDInput = req.query.adminUserID as string;
@@ -23,8 +23,7 @@ export async function shadowBanUser(req: Request, res: Response) {
if (adminUserIDInput == undefined || (userID == undefined && hashedIP == undefined)) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
@@ -33,8 +32,7 @@ export async function shadowBanUser(req: Request, res: Response) {
const isVIP = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ?`, [adminUserIDInput])).userCount > 0;
if (!isVIP) {
//not authorized
res.sendStatus(403);
return;
return res.sendStatus(403);
}
if (userID) {
@@ -66,10 +64,10 @@ export async function shadowBanUser(req: Request, res: Response) {
//find all previous submissions and unhide them
if (unHideOldSubmissions) {
let segmentsToIgnore = (await db.prepare('all', `SELECT "UUID" FROM "sponsorTimes" st
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);
let allSegments = (await db.prepare('all', `SELECT "UUID" FROM "sponsorTimes" st WHERE "st"."userID" = ?`, [userID]))
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}) => {
@@ -114,6 +112,5 @@ export async function shadowBanUser(req: Request, res: Response) {
// }
}*/
}
res.sendStatus(200);
return res.sendStatus(200);
}

View File

@@ -2,7 +2,7 @@ import {db} from '../databases/databases';
import {Request, Response} from 'express';
export async function viewedVideoSponsorTime(req: Request, res: Response): Promise<Response> {
let UUID = req.query.UUID;
const UUID = req.query.UUID;
if (UUID == undefined) {
//invalid request

View File

@@ -161,31 +161,27 @@ async function sendWebhooks(voteData: VoteData) {
}
async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, isOwnSubmission: boolean, category: Category
, hashedIP: HashedIP, finalResponse: FinalResponse, res: 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]);
if (usersLastVoteInfo?.category === category) {
// Double vote, ignore
res.sendStatus(finalResponse.finalStatus);
return;
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};
if (!videoInfo) {
// Submission doesn't exist
res.status(400).send("Submission doesn't exist.");
return;
return res.status(400).send("Submission doesn't exist.");
}
if (!config.categoryList.includes(category)) {
res.status(400).send("Category doesn't exist.");
return;
return res.status(400).send("Category doesn't exist.");
}
if (getCategoryActionType(category) !== CategoryActionType.Skippable) {
res.status(400).send("Cannot vote for this category");
return;
return res.status(400).send("Cannot vote for this category");
}
const nextCategoryInfo = await db.prepare("get", `select votes from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, category]);
@@ -245,14 +241,14 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i
QueryCacher.clearVideoCache(videoInfo);
res.sendStatus(finalResponse.finalStatus);
return res.sendStatus(finalResponse.finalStatus);
}
export function getUserID(req: Request): UserID {
return req.query.userID as UserID;
}
export async function voteOnSponsorTime(req: Request, res: Response) {
export async function voteOnSponsorTime(req: Request, res: Response): Promise<Response> {
const UUID = req.query.UUID as SegmentUUID;
const paramUserID = getUserID(req);
let type = req.query.type !== undefined ? parseInt(req.query.type as string) : undefined;
@@ -260,8 +256,7 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
if (UUID === undefined || paramUserID === undefined || (type === undefined && category === undefined)) {
//invalid request
res.sendStatus(400);
return;
return res.sendStatus(400);
}
//hash the userID
@@ -269,13 +264,13 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
const userID = getHash(paramUserID + UUID);
// To force a non 200, change this early
let finalResponse: FinalResponse = {
const finalResponse: FinalResponse = {
blockVote: false,
finalStatus: 200,
finalMessage: null,
webhookType: VoteWebhookType.Normal,
webhookMessage: null
}
};
//x-forwarded-for if this server is behind a proxy
const ip = getIP(req);
@@ -292,8 +287,7 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
// disallow vote types 10/11
if (type === 10 || type === 11) {
// no longer allow type 10/11 alternative votes
res.sendStatus(400)
return;
return res.sendStatus(400);
}
// If not upvote
@@ -305,8 +299,8 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
if (await isSegmentLocked() || await isVideoLocked()) {
finalResponse.blockVote = true;
finalResponse.webhookType = VoteWebhookType.Rejected
finalResponse.webhookMessage = "Vote rejected: A moderator has decided that this segment is correct"
finalResponse.webhookType = VoteWebhookType.Rejected;
finalResponse.webhookMessage = "Vote rejected: A moderator has decided that this segment is correct";
}
}
@@ -320,12 +314,10 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
if (voteInfo && voteInfo.votes <= -2) {
if (type == 1) {
res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.");
return;
return res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.");
} else if (type == 0) {
// Already downvoted enough, ignore
res.status(200).send();
return;
return res.sendStatus(200);
}
}
}
@@ -361,8 +353,7 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
incrementAmount = 0;
} else {
//unrecongnised type of vote
res.sendStatus(400);
return;
return res.sendStatus(400);
}
if (votesRow != undefined) {
if (votesRow.type === 1) {
@@ -444,9 +435,6 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
QueryCacher.clearVideoCache(videoInfo);
}
res.status(finalResponse.finalStatus).send(finalResponse.finalMessage ?? undefined);
if (incrementAmount - oldIncrementAmount !== 0) {
sendWebhooks({
UUID,
@@ -461,9 +449,9 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
finalResponse
});
}
return res.status(finalResponse.finalStatus).send(finalResponse.finalMessage ?? undefined);
} catch (err) {
Logger.error(err);
res.status(500).json({error: 'Internal error creating segment vote'});
return res.status(500).json({error: 'Internal error creating segment vote'});
}
}