mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-10 21:47:02 +03:00
Remove ratings code
This commit is contained in:
11
src/app.ts
11
src/app.ts
@@ -43,9 +43,6 @@ import ExpressPromiseRouter from "express-promise-router";
|
|||||||
import { Server } from "http";
|
import { Server } from "http";
|
||||||
import { youtubeApiProxy } from "./routes/youtubeApiProxy";
|
import { youtubeApiProxy } from "./routes/youtubeApiProxy";
|
||||||
import { getChapterNames } from "./routes/getChapterNames";
|
import { getChapterNames } from "./routes/getChapterNames";
|
||||||
import { postRating } from "./routes/ratings/postRating";
|
|
||||||
import { getRating } from "./routes/ratings/getRating";
|
|
||||||
import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache";
|
|
||||||
import { getTopCategoryUsers } from "./routes/getTopCategoryUsers";
|
import { getTopCategoryUsers } from "./routes/getTopCategoryUsers";
|
||||||
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
|
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
|
||||||
import { addFeature } from "./routes/addFeature";
|
import { addFeature } from "./routes/addFeature";
|
||||||
@@ -80,11 +77,9 @@ function setupRoutes(router: Router) {
|
|||||||
// Rate limit endpoint lists
|
// Rate limit endpoint lists
|
||||||
const voteEndpoints: RequestHandler[] = [voteOnSponsorTime];
|
const voteEndpoints: RequestHandler[] = [voteOnSponsorTime];
|
||||||
const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime];
|
const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime];
|
||||||
const postRateEndpoints: RequestHandler[] = [postRating];
|
|
||||||
if (config.rateLimit) {
|
if (config.rateLimit) {
|
||||||
if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote, voteGetUserID));
|
if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote, voteGetUserID));
|
||||||
if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view));
|
if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view));
|
||||||
if (config.rateLimit.rate) postRateEndpoints.unshift(rateLimitMiddleware(config.rateLimit.rate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//add the get function
|
//add the get function
|
||||||
@@ -199,12 +194,6 @@ function setupRoutes(router: Router) {
|
|||||||
|
|
||||||
router.post("/api/feature", addFeature);
|
router.post("/api/feature", addFeature);
|
||||||
|
|
||||||
// ratings
|
|
||||||
router.get("/api/ratings/rate/:prefix", getRating);
|
|
||||||
router.get("/api/ratings/rate", getRating);
|
|
||||||
router.post("/api/ratings/rate", postRateEndpoints);
|
|
||||||
router.post("/api/ratings/clearCache", ratingPostClearCache);
|
|
||||||
|
|
||||||
if (config.postgres?.enabled) {
|
if (config.postgres?.enabled) {
|
||||||
router.get("/database", (req, res) => dumpDatabase(req, res, true));
|
router.get("/database", (req, res) => dumpDatabase(req, res, true));
|
||||||
router.get("/database.json", (req, res) => dumpDatabase(req, res, false));
|
router.get("/database.json", (req, res) => dumpDatabase(req, res, false));
|
||||||
|
|||||||
@@ -62,12 +62,6 @@ addDefaults(config, {
|
|||||||
max: 10,
|
max: 10,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
message: "OK",
|
message: "OK",
|
||||||
},
|
|
||||||
rate: {
|
|
||||||
windowMs: 900000,
|
|
||||||
max: 20,
|
|
||||||
statusCode: 200,
|
|
||||||
message: "Success",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userCounterURL: null,
|
userCounterURL: null,
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import { db } from "../../databases/databases";
|
|
||||||
import { RatingType } from "../../types/ratings.model";
|
|
||||||
import { Service, VideoID, VideoIDHash } from "../../types/segments.model";
|
|
||||||
import { getService } from "../../utils/getService";
|
|
||||||
import { hashPrefixTester } from "../../utils/hashPrefixTester";
|
|
||||||
import { Logger } from "../../utils/logger";
|
|
||||||
import { QueryCacher } from "../../utils/queryCacher";
|
|
||||||
import { ratingHashKey } from "../../utils/redisKeys";
|
|
||||||
|
|
||||||
interface DBRating {
|
|
||||||
videoID: VideoID,
|
|
||||||
hashedVideoID: VideoIDHash,
|
|
||||||
service: Service,
|
|
||||||
type: RatingType,
|
|
||||||
count: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRating(req: Request, res: Response): Promise<Response> {
|
|
||||||
let hashPrefixes: VideoIDHash[] = [];
|
|
||||||
try {
|
|
||||||
hashPrefixes = req.query.hashPrefixes
|
|
||||||
? JSON.parse(req.query.hashPrefixes as string)
|
|
||||||
: Array.isArray(req.query.prefix)
|
|
||||||
? req.query.prefix
|
|
||||||
: [req.query.prefix ?? req.params.prefix];
|
|
||||||
if (!Array.isArray(hashPrefixes)) {
|
|
||||||
return res.status(400).send("hashPrefixes parameter does not match format requirements.");
|
|
||||||
}
|
|
||||||
|
|
||||||
hashPrefixes.map((hashPrefix) => hashPrefix?.toLowerCase());
|
|
||||||
} catch(error) {
|
|
||||||
return res.status(400).send("Bad parameter: hashPrefixes (invalid JSON)");
|
|
||||||
}
|
|
||||||
if (hashPrefixes.length === 0 || hashPrefixes.length > 75
|
|
||||||
|| hashPrefixes.some((hashPrefix) => !hashPrefix || !hashPrefixTester(hashPrefix))) {
|
|
||||||
return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
let types: RatingType[] = [];
|
|
||||||
try {
|
|
||||||
types = req.query.types
|
|
||||||
? JSON.parse(req.query.types as string)
|
|
||||||
: req.query.type
|
|
||||||
? Array.isArray(req.query.type)
|
|
||||||
? req.query.type
|
|
||||||
: [req.query.type]
|
|
||||||
: [RatingType.Upvote, RatingType.Downvote];
|
|
||||||
if (!Array.isArray(types)) {
|
|
||||||
return res.status(400).send("Types parameter does not match format requirements.");
|
|
||||||
}
|
|
||||||
|
|
||||||
types = types.map((type) => parseInt(type as unknown as string, 10));
|
|
||||||
} catch(error) {
|
|
||||||
return res.status(400).send("Bad parameter: types (invalid JSON)");
|
|
||||||
}
|
|
||||||
|
|
||||||
const service: Service = getService(req.query.service, req.body.service);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ratings = (await getRatings(hashPrefixes, service))
|
|
||||||
.filter((rating) => types.includes(rating.type))
|
|
||||||
.map((rating) => ({
|
|
||||||
videoID: rating.videoID,
|
|
||||||
hash: rating.hashedVideoID,
|
|
||||||
service: rating.service,
|
|
||||||
type: rating.type,
|
|
||||||
count: rating.count
|
|
||||||
}));
|
|
||||||
|
|
||||||
return res.status((ratings.length) ? 200 : 404)
|
|
||||||
.send(ratings ?? []);
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(err as string);
|
|
||||||
|
|
||||||
return res.sendStatus(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRatings(hashPrefixes: VideoIDHash[], service: Service): Promise<DBRating[]> {
|
|
||||||
const fetchFromDB = (hashPrefixes: VideoIDHash[]) => db
|
|
||||||
.prepare(
|
|
||||||
"all",
|
|
||||||
`SELECT "videoID", "hashedVideoID", "type", "count" FROM "ratings" WHERE "hashedVideoID" ~* ? AND "service" = ? ORDER BY "hashedVideoID"`,
|
|
||||||
[`^(?:${hashPrefixes.join("|")})`, service]
|
|
||||||
) as Promise<DBRating[]>;
|
|
||||||
|
|
||||||
return (hashPrefixes.every((hashPrefix) => hashPrefix.length === 4))
|
|
||||||
? QueryCacher.getAndSplit(fetchFromDB, (prefix) => ratingHashKey(prefix, service), "hashedVideoID", hashPrefixes)
|
|
||||||
: fetchFromDB(hashPrefixes);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Logger } from "../../utils/logger";
|
|
||||||
import { HashedUserID, UserID } from "../../types/user.model";
|
|
||||||
import { getHash } from "../../utils/getHash";
|
|
||||||
import { getHashCache } from "../../utils/getHashCache";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import { Service, VideoID } from "../../types/segments.model";
|
|
||||||
import { QueryCacher } from "../../utils/queryCacher";
|
|
||||||
import { isUserVIP } from "../../utils/isUserVIP";
|
|
||||||
import { VideoIDHash } from "../../types/segments.model";
|
|
||||||
import { getService } from "../..//utils/getService";
|
|
||||||
|
|
||||||
export async function postClearCache(req: Request, res: Response): Promise<Response> {
|
|
||||||
const videoID = req.query.videoID as VideoID;
|
|
||||||
const userID = req.query.userID as UserID;
|
|
||||||
const service = getService(req.query.service as Service);
|
|
||||||
|
|
||||||
const invalidFields = [];
|
|
||||||
if (typeof videoID !== "string") {
|
|
||||||
invalidFields.push("videoID");
|
|
||||||
}
|
|
||||||
if (typeof userID !== "string") {
|
|
||||||
invalidFields.push("userID");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalidFields.length !== 0) {
|
|
||||||
// invalid request
|
|
||||||
const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ", " : "") + c, "");
|
|
||||||
return res.status(400).send(`No valid ${fields} field(s) provided`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash the userID as early as possible
|
|
||||||
const hashedUserID: HashedUserID = await getHashCache(userID);
|
|
||||||
// hash videoID
|
|
||||||
const hashedVideoID: VideoIDHash = getHash(videoID, 1);
|
|
||||||
|
|
||||||
// Ensure user is a VIP
|
|
||||||
if (!(await isUserVIP(hashedUserID))){
|
|
||||||
Logger.warn(`Permission violation: User ${hashedUserID} attempted to clear cache for video ${videoID}.`);
|
|
||||||
return res.status(403).json({ "message": "Not a VIP" });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
QueryCacher.clearRatingCache({
|
|
||||||
hashedVideoID,
|
|
||||||
service
|
|
||||||
});
|
|
||||||
return res.status(200).json({
|
|
||||||
message: `Cache cleared on video ${videoID}`
|
|
||||||
});
|
|
||||||
} catch(err) {
|
|
||||||
return res.sendStatus(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { db, privateDB } from "../../databases/databases";
|
|
||||||
import { getHash } from "../../utils/getHash";
|
|
||||||
import { getHashCache } from "../../utils/getHashCache";
|
|
||||||
import { Logger } from "../../utils/logger";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import { HashedUserID, UserID } from "../../types/user.model";
|
|
||||||
import { HashedIP, IPAddress, VideoID } from "../../types/segments.model";
|
|
||||||
import { getIP } from "../../utils/getIP";
|
|
||||||
import { getService } from "../../utils/getService";
|
|
||||||
import { RatingType, RatingTypes } from "../../types/ratings.model";
|
|
||||||
import { config } from "../../config";
|
|
||||||
import { QueryCacher } from "../../utils/queryCacher";
|
|
||||||
|
|
||||||
export async function postRating(req: Request, res: Response): Promise<Response> {
|
|
||||||
const privateUserID = req.body.userID as UserID;
|
|
||||||
const videoID = req.body.videoID as VideoID;
|
|
||||||
const service = getService(req.query.service, req.body.service);
|
|
||||||
const type = req.body.type as RatingType;
|
|
||||||
const enabled = req.body.enabled ?? true;
|
|
||||||
|
|
||||||
if (privateUserID == undefined || videoID == undefined || service == undefined || type == undefined
|
|
||||||
|| (typeof privateUserID !== "string") || (typeof videoID !== "string") || (typeof service !== "string")
|
|
||||||
|| (typeof type !== "number") || (enabled && (typeof enabled !== "boolean")) || !RatingTypes.includes(type)) {
|
|
||||||
//invalid request
|
|
||||||
return res.sendStatus(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedIP: HashedIP = getHash(getIP(req) + config.globalSalt as IPAddress, 1);
|
|
||||||
const hashedUserID: HashedUserID = await getHashCache(privateUserID);
|
|
||||||
const hashedVideoID = getHash(videoID, 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if this user has voted before
|
|
||||||
const existingVote = await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]);
|
|
||||||
if (existingVote.count > 0 && !enabled) {
|
|
||||||
// Undo the vote
|
|
||||||
await privateDB.prepare("run", `DELETE FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]);
|
|
||||||
await db.prepare("run", `UPDATE "ratings" SET "count" = "count" - 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]);
|
|
||||||
} else if (existingVote.count === 0 && enabled) {
|
|
||||||
// Make sure there hasn't been another vote from this IP
|
|
||||||
const existingIPVote = (await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "hashedIP" = ?`, [videoID, service, type, hashedIP]))
|
|
||||||
.count > 0;
|
|
||||||
if (existingIPVote) { // if exisiting vote, exit early instead
|
|
||||||
return res.sendStatus(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create entry in privateDB
|
|
||||||
await privateDB.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "userID", "timeSubmitted", "hashedIP") VALUES (?, ?, ?, ?, ?, ?)`, [videoID, service, type, hashedUserID, Date.now(), hashedIP]);
|
|
||||||
|
|
||||||
// Check if general rating already exists, if so increase it
|
|
||||||
const rating = await db.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]);
|
|
||||||
if (rating.count > 0) {
|
|
||||||
await db.prepare("run", `UPDATE "ratings" SET "count" = "count" + 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]);
|
|
||||||
} else {
|
|
||||||
await db.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "count", "hashedVideoID") VALUES (?, ?, ?, 1, ?)`, [videoID, service, type, hashedVideoID]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// clear rating cache
|
|
||||||
QueryCacher.clearRatingCache({ hashedVideoID, service });
|
|
||||||
return res.sendStatus(200);
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(err as string);
|
|
||||||
return res.sendStatus(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,7 +52,6 @@ export interface SBSConfig {
|
|||||||
rateLimit: {
|
rateLimit: {
|
||||||
vote: RateLimitConfig;
|
vote: RateLimitConfig;
|
||||||
view: RateLimitConfig;
|
view: RateLimitConfig;
|
||||||
rate: RateLimitConfig;
|
|
||||||
};
|
};
|
||||||
mysql?: any;
|
mysql?: any;
|
||||||
privateMysql?: any;
|
privateMysql?: any;
|
||||||
|
|||||||
Reference in New Issue
Block a user