mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-26 01:18:40 +03:00
Add vote/submission for titles and thumbnails
This commit is contained in:
@@ -186,7 +186,7 @@ export async function getBranding(req: Request, res: Response) {
|
||||
return res.status(status).json(result);
|
||||
}
|
||||
|
||||
export async function getBrandingByHash(req: Request, res: Response) {
|
||||
export async function getBrandingByHashEndpoint(req: Request, res: Response) {
|
||||
let hashPrefix = req.params.prefix as VideoIDHash;
|
||||
if (!req.params.prefix || !hashPrefixTester(req.params.prefix)) {
|
||||
return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
|
||||
|
||||
129
src/routes/postBranding.ts
Normal file
129
src/routes/postBranding.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Request, Response } from "express";
|
||||
import { config } from "../config";
|
||||
import { db, privateDB } from "../databases/databases";
|
||||
|
||||
import { BrandingSubmission, BrandingUUID, TimeThumbnailSubmission } from "../types/branding.model";
|
||||
import { HashedIP, IPAddress, VideoID } from "../types/segments.model";
|
||||
import { HashedUserID } from "../types/user.model";
|
||||
import { getHashCache } from "../utils/getHashCache";
|
||||
import { getIP } from "../utils/getIP";
|
||||
import { getService } from "../utils/getService";
|
||||
import { isUserVIP } from "../utils/isUserVIP";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
enum BrandingType {
|
||||
Title,
|
||||
Thumbnail
|
||||
}
|
||||
|
||||
interface ExistingVote {
|
||||
UUID: BrandingUUID;
|
||||
type: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export async function postBranding(req: Request, res: Response) {
|
||||
const { videoID, userID, title, thumbnail } = req.body as BrandingSubmission;
|
||||
const service = getService(req.body.service);
|
||||
|
||||
if (!videoID || !userID || userID.length < 30 || !service
|
||||
|| ((!title || !title.title)
|
||||
&& (!thumbnail || thumbnail.original == null
|
||||
|| (!thumbnail.original && !(thumbnail as TimeThumbnailSubmission).timestamp)))) {
|
||||
res.status(400).send("Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const hashedUserID = await getHashCache(userID);
|
||||
const isVip = await isUserVIP(hashedUserID);
|
||||
const hashedVideoID = await getHashCache(videoID, 1);
|
||||
const hashedIP = await getHashCache(getIP(req) + config.globalSalt as IPAddress);
|
||||
|
||||
const now = Date.now();
|
||||
const voteType = 1;
|
||||
|
||||
await Promise.all([(async () => {
|
||||
if (title) {
|
||||
const existingUUID = (await db.prepare("get", `SELECT "UUID" from "titles" where "videoID" = ? AND "title" = ?`, [videoID, title.title]))?.UUID;
|
||||
const UUID = existingUUID || crypto.randomUUID();
|
||||
|
||||
const existingVote = await handleExistingVotes(BrandingType.Title, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
if (existingUUID) {
|
||||
await updateVoteTotals(BrandingType.Title, existingVote, UUID, isVip);
|
||||
} else {
|
||||
await db.prepare("run", `INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, 0, ?, 0);`,
|
||||
[videoID, title.title, title.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID, UUID, isVip ? 1 : 0]);
|
||||
}
|
||||
}
|
||||
})(), (async () => {
|
||||
if (thumbnail) {
|
||||
const existingUUID = thumbnail.original
|
||||
? (await db.prepare("get", `SELECT "UUID" from "thumbnails" where "videoID" = ? AND "original" = 1`, [videoID]))?.UUID
|
||||
: (await db.prepare("get", `SELECT "thumbnails"."UUID" from "thumbnailTimestamps" JOIN "thumbnails" ON "thumbnails"."UUID" = "thumbnailTimestamps"."UUID"
|
||||
WHERE "thumbnailTimestamps"."timestamp" = ? AND "thumbnails"."videoID" = ?`, [(thumbnail as TimeThumbnailSubmission).timestamp, videoID]))?.UUID;
|
||||
const UUID = existingUUID || crypto.randomUUID();
|
||||
|
||||
const existingVote = await handleExistingVotes(BrandingType.Thumbnail, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
if (existingUUID) {
|
||||
await updateVoteTotals(BrandingType.Thumbnail, existingVote, UUID, isVip);
|
||||
} else {
|
||||
await db.prepare("run", `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, 0, ?, 0);
|
||||
${thumbnail.original ? "" : `INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)`}`,
|
||||
[videoID, thumbnail.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID, UUID,
|
||||
isVip ? 1 : 0, thumbnail.original ? null : UUID, thumbnail.original ? null : (thumbnail as TimeThumbnailSubmission).timestamp]);
|
||||
}
|
||||
|
||||
}
|
||||
})()]);
|
||||
|
||||
res.status(200).send("OK");
|
||||
} catch (e) {
|
||||
Logger.error(e as string);
|
||||
res.status(500).send("Internal Server Error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an existing vote, if found, and it's for a different submission, it undoes it, and points to the new submission.
|
||||
* If no existing vote, it adds one.
|
||||
*/
|
||||
async function handleExistingVotes(type: BrandingType, videoID: VideoID,
|
||||
hashedUserID: HashedUserID, UUID: BrandingUUID, hashedIP: HashedIP, voteType: number): Promise<ExistingVote> {
|
||||
const table = type === BrandingType.Title ? `"titleVotes"` : `"thumbnailVotes"`;
|
||||
|
||||
const existingVote = await privateDB.prepare("get", `SELECT "id", "UUID", "type" from ${table} where "videoID" = ? AND "userID" = ?`, [videoID, hashedUserID]);
|
||||
if (existingVote && existingVote.UUID !== UUID) {
|
||||
if (existingVote.type === 1) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" - 1 WHERE "UUID" = ?`, [existingVote.UUID]);
|
||||
}
|
||||
|
||||
await privateDB.prepare("run", `UPDATE ${table} SET "type" = ?, "UUID" = ? WHERE "id" = ?`, [voteType, UUID, existingVote.id]);
|
||||
} else if (!existingVote) {
|
||||
await privateDB.prepare("run", `INSERT INTO ${table} ("videoID", "UUID", "userID", "hashedIP", "type") VALUES (?, ?, ?, ?, ?)`,
|
||||
[videoID, UUID, hashedUserID, hashedIP, voteType]);
|
||||
}
|
||||
|
||||
return existingVote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only called if an existing vote exists.
|
||||
* Will update public vote totals and locked status.
|
||||
*/
|
||||
async function updateVoteTotals(type: BrandingType, existingVote: ExistingVote, UUID: BrandingUUID, isVip: boolean): Promise<void> {
|
||||
if (!existingVote) return;
|
||||
|
||||
const table = type === BrandingType.Title ? `"titleVotes"` : `"thumbnailVotes"`;
|
||||
|
||||
// Don't upvote if we vote on the same submission
|
||||
if (!existingVote || existingVote.UUID !== UUID) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" + 1 WHERE "UUID" = ?`, [UUID]);
|
||||
}
|
||||
|
||||
if (isVip) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "locked" = 1 WHERE "UUID" = ?`, [UUID]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user