mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-10 13:37:01 +03:00
Add new user limit per 5 mins
This commit is contained in:
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
|||||||
import { Logger } from "../utils/logger";
|
import { Logger } from "../utils/logger";
|
||||||
import { isUserVIP } from "../utils/isUserVIP";
|
import { isUserVIP } from "../utils/isUserVIP";
|
||||||
import { isUserTempVIP } from "../utils/isUserTempVIP";
|
import { isUserTempVIP } from "../utils/isUserTempVIP";
|
||||||
import { getMaxResThumbnail, YouTubeAPI } from "../utils/youtubeApi";
|
import { getMaxResThumbnail } from "../utils/youtubeApi";
|
||||||
import { db, privateDB } from "../databases/databases";
|
import { db, privateDB } from "../databases/databases";
|
||||||
import { dispatchEvent, getVoteAuthor, getVoteAuthorRaw } from "../utils/webhookUtils";
|
import { dispatchEvent, getVoteAuthor, getVoteAuthorRaw } from "../utils/webhookUtils";
|
||||||
import { getFormattedTime } from "../utils/getFormattedTime";
|
import { getFormattedTime } from "../utils/getFormattedTime";
|
||||||
@@ -17,7 +17,6 @@ import { getVideoDetails, videoDetails } from "../utils/getVideoDetails";
|
|||||||
import { deleteLockCategories } from "./deleteLockCategories";
|
import { deleteLockCategories } from "./deleteLockCategories";
|
||||||
import { acquireLock } from "../utils/redisLock";
|
import { acquireLock } from "../utils/redisLock";
|
||||||
import { checkBanStatus } from "../utils/checkBan";
|
import { checkBanStatus } from "../utils/checkBan";
|
||||||
import { canVote } from "../utils/permissions";
|
|
||||||
|
|
||||||
const voteTypes = {
|
const voteTypes = {
|
||||||
normal: 0,
|
normal: 0,
|
||||||
@@ -343,14 +342,6 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
|
|||||||
const nonAnonUserID = await getHashCache(paramUserID);
|
const nonAnonUserID = await getHashCache(paramUserID);
|
||||||
const userID = await getHashCache(paramUserID + UUID);
|
const userID = await getHashCache(paramUserID + UUID);
|
||||||
|
|
||||||
const permission = await canVote(nonAnonUserID);
|
|
||||||
if (!permission.canSubmit) {
|
|
||||||
return {
|
|
||||||
status: 403,
|
|
||||||
message: permission.reason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//hash the ip 5000 times so no one can get it from the database
|
//hash the ip 5000 times so no one can get it from the database
|
||||||
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);
|
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Feature, HashedUserID } from "../types/user.model";
|
|||||||
import { hasFeature } from "./features";
|
import { hasFeature } from "./features";
|
||||||
import { isUserVIP } from "./isUserVIP";
|
import { isUserVIP } from "./isUserVIP";
|
||||||
import { oneOf } from "./promise";
|
import { oneOf } from "./promise";
|
||||||
|
import redis from "./redis";
|
||||||
import { getReputation } from "./reputation";
|
import { getReputation } from "./reputation";
|
||||||
import { getServerConfig } from "./serverConfig";
|
import { getServerConfig } from "./serverConfig";
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ async function lowDownvotes(userID: HashedUserID): Promise<boolean> {
|
|||||||
return result.submissionCount > 5 && result.downvotedSubmissions / result.submissionCount < 0.10;
|
return result.submissionCount > 5 && result.downvotedSubmissions / result.submissionCount < 0.10;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function oldSubmitter(userID: HashedUserID): Promise<boolean> {
|
const fiveMinutes = 5 * 60 * 1000;
|
||||||
|
async function oldSubmitterOrAllowed(userID: HashedUserID): Promise<boolean> {
|
||||||
const submitterThreshold = await getServerConfig("old-submitter-block-date");
|
const submitterThreshold = await getServerConfig("old-submitter-block-date");
|
||||||
if (!submitterThreshold) {
|
if (!submitterThreshold) {
|
||||||
return true;
|
return true;
|
||||||
@@ -29,10 +31,22 @@ async function oldSubmitter(userID: HashedUserID): Promise<boolean> {
|
|||||||
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND "timeSubmitted" < ?`
|
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND "timeSubmitted" < ?`
|
||||||
, [userID, parseInt(submitterThreshold)], { useReplica: true });
|
, [userID, parseInt(submitterThreshold)], { useReplica: true });
|
||||||
|
|
||||||
return result.submissionCount >= 1;
|
const isOldSubmitter = result.submissionCount >= 1;
|
||||||
|
if (!isOldSubmitter) {
|
||||||
|
await redis.zRemRangeByScore("submitters", "-inf", Date.now() - fiveMinutes);
|
||||||
|
const last5MinUsers = await redis.zCard("submitters");
|
||||||
|
const maxUsers = await getServerConfig("max-users-per-minute");
|
||||||
|
|
||||||
|
if (maxUsers && last5MinUsers < parseInt(maxUsers)) {
|
||||||
|
await redis.zAdd("submitters", { score: Date.now(), value: userID });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function oldDeArrowSubmitter(userID: HashedUserID): Promise<boolean> {
|
return isOldSubmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function oldDeArrowSubmitterOrAllowed(userID: HashedUserID): Promise<boolean> {
|
||||||
const submitterThreshold = await getServerConfig("old-submitter-block-date");
|
const submitterThreshold = await getServerConfig("old-submitter-block-date");
|
||||||
if (!submitterThreshold) {
|
if (!submitterThreshold) {
|
||||||
return true;
|
return true;
|
||||||
@@ -41,7 +55,19 @@ async function oldDeArrowSubmitter(userID: HashedUserID): Promise<boolean> {
|
|||||||
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "titles" WHERE "userID" = ? AND "timeSubmitted" < 1743827196000`
|
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "titles" WHERE "userID" = ? AND "timeSubmitted" < 1743827196000`
|
||||||
, [userID, parseInt(submitterThreshold)], { useReplica: true });
|
, [userID, parseInt(submitterThreshold)], { useReplica: true });
|
||||||
|
|
||||||
return result.submissionCount >= 1;
|
const isOldSubmitter = result.submissionCount >= 1;
|
||||||
|
if (!isOldSubmitter) {
|
||||||
|
await redis.zRemRangeByScore("submittersDeArrow", "-inf", Date.now() - fiveMinutes);
|
||||||
|
const last5MinUsers = await redis.zCard("submittersDeArrow");
|
||||||
|
const maxUsers = await getServerConfig("max-users-per-minute-dearrow");
|
||||||
|
|
||||||
|
if (maxUsers && last5MinUsers < parseInt(maxUsers)) {
|
||||||
|
await redis.zAdd("submittersDeArrow", { score: Date.now(), value: userID });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOldSubmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function canSubmit(userID: HashedUserID, category: Category): Promise<CanSubmitResult> {
|
export async function canSubmit(userID: HashedUserID, category: Category): Promise<CanSubmitResult> {
|
||||||
@@ -66,16 +92,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi
|
|||||||
export async function canSubmitGlobal(userID: HashedUserID): Promise<CanSubmitResult> {
|
export async function canSubmitGlobal(userID: HashedUserID): Promise<CanSubmitResult> {
|
||||||
return {
|
return {
|
||||||
canSubmit: await oneOf([isUserVIP(userID),
|
canSubmit: await oneOf([isUserVIP(userID),
|
||||||
oldSubmitter(userID)
|
oldSubmitterOrAllowed(userID)
|
||||||
]),
|
|
||||||
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function canVote(userID: HashedUserID): Promise<CanSubmitResult> {
|
|
||||||
return {
|
|
||||||
canSubmit: await oneOf([isUserVIP(userID),
|
|
||||||
oldSubmitter(userID)
|
|
||||||
]),
|
]),
|
||||||
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
|
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
|
||||||
};
|
};
|
||||||
@@ -84,7 +101,7 @@ export async function canVote(userID: HashedUserID): Promise<CanSubmitResult> {
|
|||||||
export async function canSubmitDeArrow(userID: HashedUserID): Promise<CanSubmitResult> {
|
export async function canSubmitDeArrow(userID: HashedUserID): Promise<CanSubmitResult> {
|
||||||
return {
|
return {
|
||||||
canSubmit: await oneOf([isUserVIP(userID),
|
canSubmit: await oneOf([isUserVIP(userID),
|
||||||
oldDeArrowSubmitter(userID)
|
oldDeArrowSubmitterOrAllowed(userID)
|
||||||
]),
|
]),
|
||||||
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
|
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Postgres } from "../databases/Postgres";
|
|||||||
import { compress, uncompress } from "lz4-napi";
|
import { compress, uncompress } from "lz4-napi";
|
||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
import { shouldClientCacheKey } from "./redisKeys";
|
import { shouldClientCacheKey } from "./redisKeys";
|
||||||
|
import { ZMember } from "@redis/client/dist/lib/commands/generic-transformers";
|
||||||
|
|
||||||
export interface RedisStats {
|
export interface RedisStats {
|
||||||
activeRequests: number;
|
activeRequests: number;
|
||||||
@@ -35,6 +36,9 @@ interface RedisSB {
|
|||||||
sendCommand(args: RedisCommandArguments, options?: RedisClientOptions): Promise<RedisReply>;
|
sendCommand(args: RedisCommandArguments, options?: RedisClientOptions): Promise<RedisReply>;
|
||||||
ttl(key: RedisCommandArgument): Promise<number>;
|
ttl(key: RedisCommandArgument): Promise<number>;
|
||||||
quit(): Promise<void>;
|
quit(): Promise<void>;
|
||||||
|
zRemRangeByScore(key: string, min: number | RedisCommandArgument, max: number | RedisCommandArgument): Promise<number>;
|
||||||
|
zAdd(key: string, members: ZMember | ZMember[]): Promise<number>;
|
||||||
|
zCard(key: string): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportClient: RedisSB = {
|
let exportClient: RedisSB = {
|
||||||
@@ -49,6 +53,9 @@ let exportClient: RedisSB = {
|
|||||||
sendCommand: () => Promise.resolve(null),
|
sendCommand: () => Promise.resolve(null),
|
||||||
quit: () => Promise.resolve(null),
|
quit: () => Promise.resolve(null),
|
||||||
ttl: () => Promise.resolve(null),
|
ttl: () => Promise.resolve(null),
|
||||||
|
zRemRangeByScore: () => Promise.resolve(null),
|
||||||
|
zAdd: () => Promise.resolve(null),
|
||||||
|
zCard: () => Promise.resolve(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastClientFail = 0;
|
let lastClientFail = 0;
|
||||||
@@ -308,6 +315,9 @@ if (config.redis?.enabled) {
|
|||||||
.then((reply) => resolve(reply))
|
.then((reply) => resolve(reply))
|
||||||
.catch((err) => reject(err))
|
.catch((err) => reject(err))
|
||||||
);
|
);
|
||||||
|
exportClient.zRemRangeByScore = client.zRemRangeByScore.bind(client);
|
||||||
|
exportClient.zAdd = client.zAdd.bind(client);
|
||||||
|
exportClient.zCard = client.zCard.bind(client);
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
client.on("error", function(error) {
|
client.on("error", function(error) {
|
||||||
lastClientFail = Date.now();
|
lastClientFail = Date.now();
|
||||||
|
|||||||
Reference in New Issue
Block a user