diff --git a/src/config.ts b/src/config.ts index 75b4590..072b23f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,6 +47,7 @@ addDefaults(config, { discordMaliciousReportWebhookURL: null, discordDeArrowLockedWebhookURL: null, discordDeArrowWarnedWebhookURL: null, + discordNewUserWebhookURL: null, minReputationToSubmitChapter: 0, minReputationToSubmitFiller: 0, getTopUsersCacheTimeMinutes: 240, diff --git a/src/routes/postBranding.ts b/src/routes/postBranding.ts index 12ae505..83018a5 100644 --- a/src/routes/postBranding.ts +++ b/src/routes/postBranding.ts @@ -60,10 +60,36 @@ export async function postBranding(req: Request, res: Response) { const permission = await canSubmitDeArrow(hashedUserID); if (!permission.canSubmit) { - Logger.warn(`New user trying to submit dearrow: ${userID} ${videoID} ${title} ${req.headers["user-agent"]}`); + Logger.warn(`New user trying to submit dearrow: ${userID} ${videoID} ${videoDuration} ${title} ${req.headers["user-agent"]}`); res.status(403).send(permission.reason); return; + } else if (permission.newUser && config.discordNewUserWebhookURL) { + axios.post(config.discordNewUserWebhookURL, { + "embeds": [{ + "title": userID, + "url": `https://www.youtube.com/watch?v=${videoID}`, + "description": `**User Agent**: ${userAgent}\ + \n\n**Real User Agent**: ${req.headers["user-agent"]}\ + \n**Video Duration**: ${videoDuration}`, + "color": 10813440, + "thumbnail": { + "url": getMaxResThumbnail(videoID), + }, + }], + }) + .then(res => { + if (res.status >= 400) { + Logger.error("Error sending reported submission Discord hook"); + Logger.error(JSON.stringify((res.data))); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send reported submission Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); + }); } if (videoDuration && thumbnail && await checkForWrongVideoDuration(videoID, videoDuration)) { diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 08490df..d473d36 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -513,6 +513,32 @@ export async function postSkipSegments(req: Request, res: Response): Promise { + if (res.status >= 400) { + Logger.error("Error sending reported submission Discord hook"); + Logger.error(JSON.stringify((res.data))); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send reported submission Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); + }); } const invalidCheckResult = await checkInvalidFields(videoID, paramUserID, userID, segments, videoDurationParam, userAgent, service); diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 91bae15..9fbeddb 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -55,6 +55,7 @@ export interface SBSConfig { discordMaliciousReportWebhookURL?: string; discordDeArrowLockedWebhookURL?: string, discordDeArrowWarnedWebhookURL?: string, + discordNewUserWebhookURL?: string; neuralBlockURL?: string; discordNeuralBlockRejectWebhookURL?: string; minReputationToSubmitChapter: number; diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index f4427fb..84d5544 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -9,11 +9,22 @@ import redis from "./redis"; import { getReputation } from "./reputation"; import { getServerConfig } from "./serverConfig"; +interface OldSubmitterResult { + canSubmit: boolean; + newUser: boolean; +} + interface CanSubmitResult { canSubmit: boolean; reason: string; } +interface CanSubmitGlobalResult { + canSubmit: boolean; + newUser: boolean; + reason: string; +} + async function lowDownvotes(userID: HashedUserID): Promise { const result = await db.prepare("get", `SELECT count(*) as "submissionCount", SUM(CASE WHEN "votes" < 0 AND "views" > 5 THEN 1 ELSE 0 END) AS "downvotedSubmissions" FROM "sponsorTimes" WHERE "userID" = ?` , [userID], { useReplica: true }); @@ -22,11 +33,11 @@ async function lowDownvotes(userID: HashedUserID): Promise { } const fiveMinutes = 5 * 60 * 1000; -async function oldSubmitterOrAllowed(userID: HashedUserID): Promise { +async function oldSubmitterOrAllowed(userID: HashedUserID): Promise { const submitterThreshold = await getServerConfig("old-submitter-block-date"); const maxUsers = await getServerConfig("max-users-per-minute"); if (!submitterThreshold && !maxUsers) { - return true; + return { canSubmit: true, newUser: false }; } const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND "shadowHidden" = 0 AND "votes" >= 0 AND "timeSubmitted" < ?` @@ -39,18 +50,18 @@ async function oldSubmitterOrAllowed(userID: HashedUserID): Promise { if (maxUsers && last5MinUsers < parseInt(maxUsers)) { await redis.zAdd("submitters", { score: Date.now(), value: userID }); - return true; + return { canSubmit: true, newUser: true }; } } - return isOldSubmitter; + return { canSubmit: isOldSubmitter, newUser: false }; } -async function oldDeArrowSubmitterOrAllowed(userID: HashedUserID): Promise { +async function oldDeArrowSubmitterOrAllowed(userID: HashedUserID): Promise { const submitterThreshold = await getServerConfig("old-submitter-block-date"); const maxUsers = await getServerConfig("max-users-per-minute-dearrow"); if (!submitterThreshold && !maxUsers) { - return true; + return { canSubmit: true, newUser: false }; } const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" WHERE "userID" = ? AND "shadowHidden" = 0 AND "votes" >= 0 AND "timeSubmitted" < ?` @@ -62,7 +73,7 @@ async function oldDeArrowSubmitterOrAllowed(userID: HashedUserID): Promise { @@ -97,20 +108,26 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi } } -export async function canSubmitGlobal(userID: HashedUserID): Promise { +export async function canSubmitGlobal(userID: HashedUserID): Promise { + const oldSubmitterOrAllowedPromise = oldSubmitterOrAllowed(userID); + return { canSubmit: await oneOf([isUserVIP(userID), - oldSubmitterOrAllowed(userID) + (async () => (await oldSubmitterOrAllowedPromise).canSubmit)() ]), + newUser: (await oldSubmitterOrAllowedPromise).newUser, reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now" }; } -export async function canSubmitDeArrow(userID: HashedUserID): Promise { +export async function canSubmitDeArrow(userID: HashedUserID): Promise { + const oldSubmitterOrAllowedPromise = oldDeArrowSubmitterOrAllowed(userID); + return { canSubmit: await oneOf([isUserVIP(userID), - oldDeArrowSubmitterOrAllowed(userID) + (async () => (await oldSubmitterOrAllowedPromise).canSubmit)() ]), + newUser: (await oldSubmitterOrAllowedPromise).newUser, reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now" }; } \ No newline at end of file