mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 01:02:30 +01:00
Handle exceptions, and prevent crashing from unhandled exceptions
This commit is contained in:
parent
4e93a007c2
commit
f63fa09605
17 changed files with 358 additions and 275 deletions
|
@ -10,7 +10,11 @@ async function init() {
|
|||
process.on("unhandledRejection", (error: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.dir(error?.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on("uncaughtExceptions", (error: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.dir(error?.stack);
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { config } from "../config";
|
|||
import { Request, Response } from "express";
|
||||
import { isUserVIP } from "../utils/isUserVIP";
|
||||
import { HashedUserID } from "../types/user.model";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
interface AddUserAsVIPRequest extends Request {
|
||||
query: {
|
||||
|
@ -34,15 +35,21 @@ export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Pro
|
|||
// check to see if this user is already a vip
|
||||
const userIsVIP = await isUserVIP(userID);
|
||||
|
||||
if (enabled && !userIsVIP) {
|
||||
// add them to the vip list
|
||||
await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
|
||||
try {
|
||||
if (enabled && !userIsVIP) {
|
||||
// add them to the vip list
|
||||
await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
|
||||
}
|
||||
|
||||
if (!enabled && userIsVIP) {
|
||||
//remove them from the shadow ban list
|
||||
await db.prepare("run", 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
|
||||
}
|
||||
|
||||
return res.sendStatus(200);
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
if (!enabled && userIsVIP) {
|
||||
//remove them from the shadow ban list
|
||||
await db.prepare("run", 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]);
|
||||
}
|
||||
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ActionType, Category, Service, VideoID } from "../types/segments.model"
|
|||
import { UserID } from "../types/user.model";
|
||||
import { getService } from "../utils/getService";
|
||||
import { config } from "../config";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
interface DeleteLockCategoriesRequest extends Request {
|
||||
body: {
|
||||
|
@ -53,7 +54,12 @@ export async function deleteLockCategoriesEndpoint(req: DeleteLockCategoriesRequ
|
|||
});
|
||||
}
|
||||
|
||||
await deleteLockCategories(videoID, categories, actionTypes, getService(service));
|
||||
try {
|
||||
await deleteLockCategories(videoID, categories, actionTypes, getService(service));
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.status(500);
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: `Removed lock categories entries for video ${videoID}` });
|
||||
}
|
||||
|
|
|
@ -164,18 +164,23 @@ export default async function dumpDatabase(req: Request, res: Response, showPage
|
|||
<hr/>
|
||||
${updateQueued ? `Update queued.` : ``} Last updated: ${lastUpdate ? new Date(lastUpdate).toUTCString() : `Unknown`}`);
|
||||
} else {
|
||||
res.send({
|
||||
dbVersion: await getDbVersion(),
|
||||
lastUpdated: lastUpdate,
|
||||
updateQueued,
|
||||
links: latestDumpFiles.map((item:any) => {
|
||||
return {
|
||||
table: item.tableName,
|
||||
url: `/database/${item.tableName}.csv`,
|
||||
size: item.fileSize,
|
||||
};
|
||||
}),
|
||||
});
|
||||
try {
|
||||
res.send({
|
||||
dbVersion: await getDbVersion(),
|
||||
lastUpdated: lastUpdate,
|
||||
updateQueued,
|
||||
links: latestDumpFiles.map((item:any) => {
|
||||
return {
|
||||
table: item.tableName,
|
||||
url: `/database/${item.tableName}.csv`,
|
||||
size: item.fileSize,
|
||||
};
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
await queueDump();
|
||||
|
|
|
@ -25,20 +25,25 @@ let lastFetch: DBStatsData = {
|
|||
updateExtensionUsers();
|
||||
|
||||
export async function getBrandingStats(req: Request, res: Response): Promise<void> {
|
||||
const row = await getStats();
|
||||
lastFetch = row;
|
||||
try {
|
||||
const row = await getStats();
|
||||
lastFetch = row;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (!row) res.sendStatus(500);
|
||||
const extensionUsers = chromeUsersCache + firefoxUsersCache;
|
||||
/* istanbul ignore if */
|
||||
if (!row) res.sendStatus(500);
|
||||
const extensionUsers = chromeUsersCache + firefoxUsersCache;
|
||||
|
||||
//send this result
|
||||
res.send({
|
||||
userCount: row.userCount ?? 0,
|
||||
activeUsers: extensionUsers,
|
||||
titles: row.titles,
|
||||
thumbnails: row.thumbnails,
|
||||
});
|
||||
//send this result
|
||||
res.send({
|
||||
userCount: row.userCount ?? 0,
|
||||
activeUsers: extensionUsers,
|
||||
titles: row.titles,
|
||||
thumbnails: row.thumbnails,
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
async function getStats(): Promise<DBStatsData> {
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import { db } from "../databases/databases";
|
||||
import { Request, Response } from "express";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
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', []);
|
||||
try {
|
||||
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
|
||||
return res.send({
|
||||
daysSaved: row.daysSaved?.toFixed(2) ?? "0",
|
||||
});
|
||||
} else {
|
||||
return res.send({
|
||||
daysSaved: 0
|
||||
});
|
||||
if (row !== undefined) {
|
||||
//send this result
|
||||
return res.send({
|
||||
daysSaved: row.daysSaved?.toFixed(2) ?? "0",
|
||||
});
|
||||
} else {
|
||||
return res.send({
|
||||
daysSaved: 0
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { db } from "../databases/databases";
|
||||
import { Request, Response } from "express";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
async function generateTopUsersStats(sortBy: string) {
|
||||
const rows = await db.prepare("all", `SELECT COUNT(distinct "titles"."UUID") as "titleCount", COUNT(distinct "thumbnails"."UUID") as "thumbnailCount", COALESCE("userName", "titles"."userID") as "userName"
|
||||
|
@ -36,8 +37,13 @@ export async function getTopBrandingUsers(req: Request, res: Response): Promise<
|
|||
return res.status(503).send("Disabled for load reasons");
|
||||
}
|
||||
|
||||
const stats = await generateTopUsersStats(sortBy);
|
||||
try {
|
||||
const stats = await generateTopUsersStats(sortBy);
|
||||
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { createMemoryCache } from "../utils/createMemoryCache";
|
|||
import { config } from "../config";
|
||||
import { Request, Response } from "express";
|
||||
import { validateCategories } from "../utils/parseParams";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
const MILLISECONDS_IN_MINUTE = 60000;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
|
@ -74,8 +75,13 @@ export async function getTopCategoryUsers(req: Request, res: Response): Promise<
|
|||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
const stats = await getTopCategoryUsersWithCache(sortBy, category);
|
||||
try {
|
||||
const stats = await getTopCategoryUsersWithCache(sortBy, category);
|
||||
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { db } from "../databases/databases";
|
|||
import { createMemoryCache } from "../utils/createMemoryCache";
|
||||
import { config } from "../config";
|
||||
import { Request, Response } from "express";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
const MILLISECONDS_IN_MINUTE = 60000;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
|
@ -92,8 +93,13 @@ export async function getTopUsers(req: Request, res: Response): Promise<Response
|
|||
return res.status(503).send("Disabled for load reasons");
|
||||
}
|
||||
|
||||
const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled);
|
||||
try {
|
||||
const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled);
|
||||
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
//send this result
|
||||
return res.send(stats);
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,30 +30,35 @@ let lastFetch: DBStatsData = {
|
|||
updateExtensionUsers();
|
||||
|
||||
export async function getTotalStats(req: Request, res: Response): Promise<void> {
|
||||
const countContributingUsers = Boolean(req.query?.countContributingUsers == "true");
|
||||
const row = await getStats(countContributingUsers);
|
||||
lastFetch = row;
|
||||
try {
|
||||
const countContributingUsers = Boolean(req.query?.countContributingUsers == "true");
|
||||
const row = await getStats(countContributingUsers);
|
||||
lastFetch = row;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (!row) res.sendStatus(500);
|
||||
const extensionUsers = chromeUsersCache + firefoxUsersCache;
|
||||
/* istanbul ignore if */
|
||||
if (!row) res.sendStatus(500);
|
||||
const extensionUsers = chromeUsersCache + firefoxUsersCache;
|
||||
|
||||
//send this result
|
||||
res.send({
|
||||
userCount: row.userCount ?? 0,
|
||||
activeUsers: extensionUsers,
|
||||
apiUsers: Math.max(apiUsersCache, extensionUsers),
|
||||
viewCount: row.viewCount,
|
||||
totalSubmissions: row.totalSubmissions,
|
||||
minutesSaved: row.minutesSaved,
|
||||
});
|
||||
//send this result
|
||||
res.send({
|
||||
userCount: row.userCount ?? 0,
|
||||
activeUsers: extensionUsers,
|
||||
apiUsers: Math.max(apiUsersCache, extensionUsers),
|
||||
viewCount: row.viewCount,
|
||||
totalSubmissions: row.totalSubmissions,
|
||||
minutesSaved: row.minutesSaved,
|
||||
});
|
||||
|
||||
// Check if the cache should be updated (every ~14 hours)
|
||||
const now = Date.now();
|
||||
if (now - lastUserCountCheck > 5000000) {
|
||||
lastUserCountCheck = now;
|
||||
// Check if the cache should be updated (every ~14 hours)
|
||||
const now = Date.now();
|
||||
if (now - lastUserCountCheck > 5000000) {
|
||||
lastUserCountCheck = now;
|
||||
|
||||
updateExtensionUsers();
|
||||
updateExtensionUsers();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { db } from "../databases/databases";
|
||||
import { Request, Response } from "express";
|
||||
import { UserID } from "../types/user.model";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
function getFuzzyUserID(userName: string): Promise<{userName: string, userID: UserID }[]> {
|
||||
// escape [_ % \] to avoid ReDOS
|
||||
|
@ -37,16 +38,22 @@ export async function getUserID(req: Request, res: Response): Promise<Response>
|
|||
// invalid request
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
const results = exactSearch
|
||||
? await getExactUserID(userName)
|
||||
: await getFuzzyUserID(userName);
|
||||
|
||||
if (results === undefined || results === null) {
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
const results = exactSearch
|
||||
? await getExactUserID(userName)
|
||||
: await getFuzzyUserID(userName);
|
||||
|
||||
if (results === undefined || results === null) {
|
||||
/* istanbul ignore next */
|
||||
return res.sendStatus(500);
|
||||
} else if (results.length === 0) {
|
||||
return res.sendStatus(404);
|
||||
} else {
|
||||
return res.send(results);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
} else if (results.length === 0) {
|
||||
return res.sendStatus(404);
|
||||
} else {
|
||||
return res.send(results);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,19 +205,24 @@ async function getUserInfo(req: Request, res: Response): Promise<Response> {
|
|||
return res.status(400).send("Invalid userID or publicUserID parameter");
|
||||
}
|
||||
|
||||
const responseObj = {} as Record<string, string|SegmentUUID|number>;
|
||||
for (const property of paramValues) {
|
||||
responseObj[property] = await dbGetValue(hashedUserID, property);
|
||||
}
|
||||
try {
|
||||
const responseObj = {} as Record<string, string|SegmentUUID|number>;
|
||||
for (const property of paramValues) {
|
||||
responseObj[property] = await dbGetValue(hashedUserID, property);
|
||||
}
|
||||
|
||||
// add minutesSaved and segmentCount after to avoid getting overwritten
|
||||
if (paramValues.includes("minutesSaved") || paramValues.includes("segmentCount")) {
|
||||
const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID);
|
||||
responseObj["minutesSaved"] = segmentsSummary.minutesSaved;
|
||||
responseObj["segmentCount"] = segmentsSummary.segmentCount;
|
||||
}
|
||||
// add minutesSaved and segmentCount after to avoid getting overwritten
|
||||
if (paramValues.includes("minutesSaved") || paramValues.includes("segmentCount")) {
|
||||
const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID);
|
||||
responseObj["minutesSaved"] = segmentsSummary.minutesSaved;
|
||||
responseObj["segmentCount"] = segmentsSummary.segmentCount;
|
||||
}
|
||||
|
||||
return res.send(responseObj);
|
||||
return res.send(responseObj);
|
||||
} catch (err) {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
export async function endpoint(req: Request, res: Response): Promise<Response> {
|
||||
|
|
|
@ -515,38 +515,37 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
|||
return;
|
||||
}
|
||||
|
||||
const isVIP = (await isUserVIP(userID));
|
||||
const isTempVIP = (await isUserTempVIP(userID, videoID));
|
||||
const rawIP = getIP(req);
|
||||
|
||||
const newData = await updateDataIfVideoDurationChange(videoID, service, videoDuration, videoDurationParam);
|
||||
videoDuration = newData.videoDuration;
|
||||
const { lockedCategoryList, apiVideoDetails } = newData;
|
||||
|
||||
// Check if all submissions are correct
|
||||
const segmentCheckResult = await checkEachSegmentValid(rawIP, paramUserID, userID, videoID, segments, service, isVIP, isTempVIP, lockedCategoryList);
|
||||
if (!segmentCheckResult.pass) {
|
||||
lock.unlock();
|
||||
return res.status(segmentCheckResult.errorCode).send(segmentCheckResult.errorMessage);
|
||||
}
|
||||
|
||||
if (!(isVIP || isTempVIP)) {
|
||||
const autoModerateCheckResult = await checkByAutoModerator(videoID, userID, segments, service, apiVideoDetails, videoDurationParam);
|
||||
if (!autoModerateCheckResult.pass) {
|
||||
lock.unlock();
|
||||
return res.status(autoModerateCheckResult.errorCode).send(autoModerateCheckResult.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Will be filled when submitting
|
||||
const UUIDs = [];
|
||||
const newSegments = [];
|
||||
|
||||
//hash the ip 5000 times so no one can get it from the database
|
||||
const hashedIP = await getHashCache(rawIP + config.globalSalt);
|
||||
|
||||
try {
|
||||
//get current time
|
||||
const isVIP = (await isUserVIP(userID));
|
||||
const isTempVIP = (await isUserTempVIP(userID, videoID));
|
||||
const rawIP = getIP(req);
|
||||
|
||||
const newData = await updateDataIfVideoDurationChange(videoID, service, videoDuration, videoDurationParam);
|
||||
videoDuration = newData.videoDuration;
|
||||
const { lockedCategoryList, apiVideoDetails } = newData;
|
||||
|
||||
// Check if all submissions are correct
|
||||
const segmentCheckResult = await checkEachSegmentValid(rawIP, paramUserID, userID, videoID, segments, service, isVIP, isTempVIP, lockedCategoryList);
|
||||
if (!segmentCheckResult.pass) {
|
||||
lock.unlock();
|
||||
return res.status(segmentCheckResult.errorCode).send(segmentCheckResult.errorMessage);
|
||||
}
|
||||
|
||||
if (!(isVIP || isTempVIP)) {
|
||||
const autoModerateCheckResult = await checkByAutoModerator(videoID, userID, segments, service, apiVideoDetails, videoDurationParam);
|
||||
if (!autoModerateCheckResult.pass) {
|
||||
lock.unlock();
|
||||
return res.status(autoModerateCheckResult.errorCode).send(autoModerateCheckResult.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Will be filled when submitting
|
||||
const UUIDs = [];
|
||||
const newSegments = [];
|
||||
|
||||
//hash the ip 5000 times so no one can get it from the database
|
||||
const hashedIP = await getHashCache(rawIP + config.globalSalt);
|
||||
|
||||
const timeSubmitted = Date.now();
|
||||
|
||||
// const rateLimitCheckResult = checkRateLimit(userID, videoID, service, timeSubmitted, hashedIP);
|
||||
|
@ -620,18 +619,18 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
|||
segment: segmentInfo.segment,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
sendWebhooks(apiVideoDetails, userID, videoID, UUIDs[i], segments[i], service).catch((e) => Logger.error(`call send webhooks ${e}`));
|
||||
}
|
||||
|
||||
return res.json(newSegments);
|
||||
} catch (err) {
|
||||
Logger.error(err as string);
|
||||
lock.unlock();
|
||||
return res.sendStatus(500);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
sendWebhooks(apiVideoDetails, userID, videoID, UUIDs[i], segments[i], service).catch((e) => Logger.error(`call send webhooks ${e}`));
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
return res.json(newSegments);
|
||||
}
|
||||
|
||||
// Takes an array of arrays:
|
||||
|
|
|
@ -42,53 +42,58 @@ export async function postWarning(req: Request, res: Response): Promise<Response
|
|||
|
||||
let resultStatus = "";
|
||||
|
||||
if (enabled) {
|
||||
const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "issuerUserID" = ? AND "type" = ?', [userID, issuerUserID, type]) as warningEntry;
|
||||
|
||||
if (!previousWarning) {
|
||||
await db.prepare(
|
||||
"run",
|
||||
'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled", "reason", "type") VALUES (?, ?, ?, 1, ?, ?)',
|
||||
[userID, issueTime, issuerUserID, reason, type]
|
||||
);
|
||||
resultStatus = "issued to";
|
||||
// check if warning is still within issue time and warning is not enabled
|
||||
} else if (checkExpiredWarning(previousWarning) ) {
|
||||
await db.prepare(
|
||||
"run", 'UPDATE "warnings" SET "enabled" = 1, "reason" = ? WHERE "userID" = ? AND "issueTime" = ?',
|
||||
[reason, userID, previousWarning.issueTime]
|
||||
);
|
||||
resultStatus = "re-enabled";
|
||||
} else {
|
||||
return res.sendStatus(409);
|
||||
}
|
||||
} else {
|
||||
await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ? AND "type" = ?', [userID, type]);
|
||||
resultStatus = "removed from";
|
||||
}
|
||||
|
||||
const targetUsername = await getUsername(userID) ?? null;
|
||||
const issuerUsername = await getUsername(issuerUserID) ?? null;
|
||||
const webhookData = {
|
||||
target: {
|
||||
userID,
|
||||
username: targetUsername
|
||||
},
|
||||
issuer: {
|
||||
userID: issuerUserID,
|
||||
username: issuerUsername
|
||||
},
|
||||
reason
|
||||
} as warningData;
|
||||
|
||||
try {
|
||||
const warning = generateWarningDiscord(webhookData);
|
||||
dispatchEvent("warning", warning);
|
||||
} catch /* istanbul ignore next */ (err) {
|
||||
Logger.error(`Error sending warning to Discord ${err}`);
|
||||
}
|
||||
if (enabled) {
|
||||
const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "issuerUserID" = ? AND "type" = ?', [userID, issuerUserID, type]) as warningEntry;
|
||||
|
||||
return res.status(200).json({
|
||||
message: `Warning ${resultStatus} user '${userID}'.`,
|
||||
});
|
||||
if (!previousWarning) {
|
||||
await db.prepare(
|
||||
"run",
|
||||
'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled", "reason", "type") VALUES (?, ?, ?, 1, ?, ?)',
|
||||
[userID, issueTime, issuerUserID, reason, type]
|
||||
);
|
||||
resultStatus = "issued to";
|
||||
// check if warning is still within issue time and warning is not enabled
|
||||
} else if (checkExpiredWarning(previousWarning) ) {
|
||||
await db.prepare(
|
||||
"run", 'UPDATE "warnings" SET "enabled" = 1, "reason" = ? WHERE "userID" = ? AND "issueTime" = ?',
|
||||
[reason, userID, previousWarning.issueTime]
|
||||
);
|
||||
resultStatus = "re-enabled";
|
||||
} else {
|
||||
return res.sendStatus(409);
|
||||
}
|
||||
} else {
|
||||
await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ? AND "type" = ?', [userID, type]);
|
||||
resultStatus = "removed from";
|
||||
}
|
||||
|
||||
const targetUsername = await getUsername(userID) ?? null;
|
||||
const issuerUsername = await getUsername(issuerUserID) ?? null;
|
||||
const webhookData = {
|
||||
target: {
|
||||
userID,
|
||||
username: targetUsername
|
||||
},
|
||||
issuer: {
|
||||
userID: issuerUserID,
|
||||
username: issuerUsername
|
||||
},
|
||||
reason
|
||||
} as warningData;
|
||||
|
||||
try {
|
||||
const warning = generateWarningDiscord(webhookData);
|
||||
dispatchEvent("warning", warning);
|
||||
} catch /* istanbul ignore next */ (err) {
|
||||
Logger.error(`Error sending warning to Discord ${err}`);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
message: `Warning ${resultStatus} user '${userID}'.`,
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,29 +34,29 @@ export async function setUsername(req: Request, res: Response): Promise<Response
|
|||
// eslint-disable-next-line no-control-regex
|
||||
userName = userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
||||
|
||||
// check privateID against publicID
|
||||
if (!await checkPrivateUsername(userName, userID)) {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
timings.push(Date.now());
|
||||
|
||||
if (adminUserIDInput != undefined) {
|
||||
//this is the admin controlling the other users account, don't hash the controling account's ID
|
||||
adminUserIDInput = await getHashCache(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput != config.adminUserID) {
|
||||
//they aren't the admin
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
} else {
|
||||
//hash the userID
|
||||
userID = await getHashCache(userID);
|
||||
}
|
||||
|
||||
timings.push(Date.now());
|
||||
|
||||
try {
|
||||
// check privateID against publicID
|
||||
if (!await checkPrivateUsername(userName, userID)) {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
timings.push(Date.now());
|
||||
|
||||
if (adminUserIDInput != undefined) {
|
||||
//this is the admin controlling the other users account, don't hash the controling account's ID
|
||||
adminUserIDInput = await getHashCache(adminUserIDInput);
|
||||
|
||||
if (adminUserIDInput != config.adminUserID) {
|
||||
//they aren't the admin
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
} else {
|
||||
//hash the userID
|
||||
userID = await getHashCache(userID);
|
||||
}
|
||||
|
||||
timings.push(Date.now());
|
||||
|
||||
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "userNames" WHERE "userID" = ? AND "locked" = 1`, [userID]);
|
||||
if (adminUserIDInput === undefined && row.userCount > 0) {
|
||||
return res.sendStatus(200);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { UserID } from "../types/user.model";
|
|||
import { QueryCacher } from "../utils/queryCacher";
|
||||
import { isUserVIP } from "../utils/isUserVIP";
|
||||
import { parseCategories, parseDeArrowTypes } from "../utils/parseParams";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
export async function shadowBanUser(req: Request, res: Response): Promise<Response> {
|
||||
const userID = req.query.userID as UserID;
|
||||
|
@ -36,42 +37,47 @@ export async function shadowBanUser(req: Request, res: Response): Promise<Respon
|
|||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
//hash the userID
|
||||
const adminUserID = await getHashCache(adminUserIDInput);
|
||||
try {
|
||||
//hash the userID
|
||||
const adminUserID = await getHashCache(adminUserIDInput);
|
||||
|
||||
const isVIP = await isUserVIP(adminUserID);
|
||||
if (!isVIP) {
|
||||
//not authorized
|
||||
return res.sendStatus(403);
|
||||
const isVIP = await isUserVIP(adminUserID);
|
||||
if (!isVIP) {
|
||||
//not authorized
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
if (userID) {
|
||||
const result = await banUser(userID, enabled, unHideOldSubmissions, type, categories, deArrowTypes);
|
||||
|
||||
if (enabled && lookForIPs) {
|
||||
const ipLoggingFixedTime = 1675295716000;
|
||||
const timeSubmitted = (await db.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" > ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[];
|
||||
const ips = (await Promise.all(timeSubmitted.map((s) => {
|
||||
return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>;
|
||||
}))).flat();
|
||||
|
||||
await Promise.all([...new Set(ips.map((ip) => ip.hashedIP))].map((ip) => {
|
||||
return banIP(ip, enabled, unHideOldSubmissions, type, categories, deArrowTypes, true);
|
||||
}));
|
||||
}
|
||||
|
||||
if (result) {
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
} else if (hashedIP) {
|
||||
const result = await banIP(hashedIP, enabled, unHideOldSubmissions, type, categories, deArrowTypes, banUsers);
|
||||
if (result) {
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return res.sendStatus(200);
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
if (userID) {
|
||||
const result = await banUser(userID, enabled, unHideOldSubmissions, type, categories, deArrowTypes);
|
||||
|
||||
if (enabled && lookForIPs) {
|
||||
const ipLoggingFixedTime = 1675295716000;
|
||||
const timeSubmitted = (await db.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" > ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[];
|
||||
const ips = (await Promise.all(timeSubmitted.map((s) => {
|
||||
return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>;
|
||||
}))).flat();
|
||||
|
||||
await Promise.all([...new Set(ips.map((ip) => ip.hashedIP))].map((ip) => {
|
||||
return banIP(ip, enabled, unHideOldSubmissions, type, categories, deArrowTypes, true);
|
||||
}));
|
||||
}
|
||||
|
||||
if (result) {
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
} else if (hashedIP) {
|
||||
const result = await banIP(hashedIP, enabled, unHideOldSubmissions, type, categories, deArrowTypes, banUsers);
|
||||
if (result) {
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
||||
export async function banUser(userID: UserID, enabled: boolean, unHideOldSubmissions: boolean,
|
||||
|
|
|
@ -17,50 +17,55 @@ export const validateLicenseKeyRegex = (token: string) =>
|
|||
export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise<Response> {
|
||||
const { query: { licenseKey } } = req;
|
||||
|
||||
if (!licenseKey) {
|
||||
return res.status(400).send("Invalid request");
|
||||
} else if (!validateLicenseKeyRegex(licenseKey)) {
|
||||
// fast check for invalid licence key
|
||||
return res.status(200).send({
|
||||
allowed: false
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?`
|
||||
, [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number};
|
||||
if (tokens) {
|
||||
const identity = await getPatreonIdentity(tokens.accessToken);
|
||||
|
||||
if (tokens.expiresIn < 15 * 24 * 60 * 60) {
|
||||
refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch((e) => Logger.error(`refresh token: ${e}`));
|
||||
try {
|
||||
if (!licenseKey) {
|
||||
return res.status(400).send("Invalid request");
|
||||
} else if (!validateLicenseKeyRegex(licenseKey)) {
|
||||
// fast check for invalid licence key
|
||||
return res.status(200).send({
|
||||
allowed: false
|
||||
});
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (identity) {
|
||||
const membership = identity.included?.[0]?.attributes;
|
||||
const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0)
|
||||
|| (membership.patron_status === PatronStatus.former && membership.campaign_lifetime_support_cents > 300));
|
||||
const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?`
|
||||
, [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number};
|
||||
if (tokens) {
|
||||
const identity = await getPatreonIdentity(tokens.accessToken);
|
||||
|
||||
return res.status(200).send({
|
||||
allowed
|
||||
});
|
||||
if (tokens.expiresIn < 15 * 24 * 60 * 60) {
|
||||
refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch((e) => Logger.error(`refresh token: ${e}`));
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (identity) {
|
||||
const membership = identity.included?.[0]?.attributes;
|
||||
const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0)
|
||||
|| (membership.patron_status === PatronStatus.former && membership.campaign_lifetime_support_cents > 300));
|
||||
|
||||
return res.status(200).send({
|
||||
allowed
|
||||
});
|
||||
} else {
|
||||
return res.status(500);
|
||||
}
|
||||
} else {
|
||||
return res.status(500);
|
||||
}
|
||||
} else {
|
||||
// Check Local
|
||||
const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]);
|
||||
if (result) {
|
||||
return res.status(200).send({
|
||||
allowed: true
|
||||
});
|
||||
} else {
|
||||
// Gumroad
|
||||
return res.status(200).send({
|
||||
allowed: await checkAllGumroadProducts(licenseKey)
|
||||
});
|
||||
}
|
||||
// Check Local
|
||||
const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]);
|
||||
if (result) {
|
||||
return res.status(200).send({
|
||||
allowed: true
|
||||
});
|
||||
} else {
|
||||
// Gumroad
|
||||
return res.status(200).send({
|
||||
allowed: await checkAllGumroadProducts(licenseKey)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
return res.status(500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue