mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-24 08:28:27 +03:00
DeArrow downvotes
This commit is contained in:
@@ -24,18 +24,18 @@ enum BrandingSubmissionType {
|
||||
export async function getVideoBranding(res: Response, videoID: VideoID, service: Service, ip: IPAddress, returnUserID: boolean): Promise<BrandingResult> {
|
||||
const getTitles = () => db.prepare(
|
||||
"all",
|
||||
`SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification", "titles"."userID"
|
||||
`SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."downvotes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification", "titles"."userID"
|
||||
FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID"
|
||||
WHERE "titles"."videoID" = ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1`,
|
||||
WHERE "titles"."videoID" = ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1 AND "titleVotes"."votes" - "titleVotes"."downvotes" > -2 AND "titleVotes"."removed" = 0`,
|
||||
[videoID, service],
|
||||
{ useReplica: true }
|
||||
) as Promise<TitleDBResult[]>;
|
||||
|
||||
const getThumbnails = () => db.prepare(
|
||||
"all",
|
||||
`SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID", "thumbnails"."userID"
|
||||
`SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."downvotes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID", "thumbnails"."userID"
|
||||
FROM "thumbnails" LEFT JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" LEFT JOIN "thumbnailTimestamps" ON "thumbnails"."UUID" = "thumbnailTimestamps"."UUID"
|
||||
WHERE "thumbnails"."videoID" = ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" > -2
|
||||
WHERE "thumbnails"."videoID" = ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" - "thumbnailVotes"."downvotes" > -2 AND "thumbnailVotes"."removed" = 0
|
||||
ORDER BY "thumbnails"."timeSubmitted" ASC`,
|
||||
[videoID, service],
|
||||
{ useReplica: true }
|
||||
@@ -89,18 +89,18 @@ export async function getVideoBranding(res: Response, videoID: VideoID, service:
|
||||
export async function getVideoBrandingByHash(videoHashPrefix: VideoIDHash, service: Service, ip: IPAddress, returnUserID: boolean): Promise<Record<VideoID, BrandingResult>> {
|
||||
const getTitles = () => db.prepare(
|
||||
"all",
|
||||
`SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification"
|
||||
`SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."downvotes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification"
|
||||
FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID"
|
||||
WHERE "titles"."hashedVideoID" LIKE ? AND "titles"."service" = ? AND "titleVotes"."votes" > -2`,
|
||||
WHERE "titles"."hashedVideoID" LIKE ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1 AND "titleVotes"."votes" - "titleVotes"."downvotes" > -2 AND "titleVotes"."removed" = 0`,
|
||||
[`${videoHashPrefix}%`, service],
|
||||
{ useReplica: true }
|
||||
) as Promise<TitleDBResult[]>;
|
||||
|
||||
const getThumbnails = () => db.prepare(
|
||||
"all",
|
||||
`SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID"
|
||||
`SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."downvotes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID"
|
||||
FROM "thumbnails" LEFT JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" LEFT JOIN "thumbnailTimestamps" ON "thumbnails"."UUID" = "thumbnailTimestamps"."UUID"
|
||||
WHERE "thumbnails"."hashedVideoID" LIKE ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" > -2
|
||||
WHERE "thumbnails"."hashedVideoID" LIKE ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" - "thumbnailVotes"."downvotes" > -2 AND "thumbnailVotes"."removed" = 0
|
||||
ORDER BY "thumbnails"."timeSubmitted" ASC`,
|
||||
[`${videoHashPrefix}%`, service],
|
||||
{ useReplica: true }
|
||||
@@ -176,7 +176,7 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, db
|
||||
.map((r) => ({
|
||||
title: r.title,
|
||||
original: r.original === 1,
|
||||
votes: r.votes + r.verification,
|
||||
votes: r.votes + r.verification - r.downvotes,
|
||||
locked: r.locked === 1,
|
||||
UUID: r.UUID,
|
||||
userID: returnUserID ? r.userID : undefined
|
||||
@@ -191,7 +191,7 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, db
|
||||
.map((r) => ({
|
||||
timestamp: r.timestamp,
|
||||
original: r.original === 1,
|
||||
votes: r.votes,
|
||||
votes: r.votes - r.downvotes,
|
||||
locked: r.locked === 1,
|
||||
UUID: r.UUID,
|
||||
userID: returnUserID ? r.userID : undefined
|
||||
|
||||
@@ -24,6 +24,11 @@ enum BrandingType {
|
||||
Thumbnail
|
||||
}
|
||||
|
||||
enum BrandingVoteType {
|
||||
Upvote = 1,
|
||||
Downvote = 2
|
||||
}
|
||||
|
||||
interface ExistingVote {
|
||||
UUID: BrandingUUID;
|
||||
type: number;
|
||||
@@ -31,7 +36,7 @@ interface ExistingVote {
|
||||
}
|
||||
|
||||
export async function postBranding(req: Request, res: Response) {
|
||||
const { videoID, userID, title, thumbnail, autoLock } = req.body as BrandingSubmission;
|
||||
const { videoID, userID, title, thumbnail, autoLock, downvote } = req.body as BrandingSubmission;
|
||||
const service = getService(req.body.service);
|
||||
|
||||
if (!videoID || !userID || userID.length < 30 || !service
|
||||
@@ -57,7 +62,7 @@ export async function postBranding(req: Request, res: Response) {
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const voteType = 1;
|
||||
const voteType: BrandingVoteType = downvote ? BrandingVoteType.Downvote : BrandingVoteType.Upvote;
|
||||
|
||||
if (title && !isVip && title.title.length > config.maxTitleLength) {
|
||||
lock.unlock();
|
||||
@@ -74,10 +79,14 @@ export async function postBranding(req: Request, res: Response) {
|
||||
if (existingUUID != undefined && isBanned) return; // ignore votes on existing details from banned users
|
||||
const UUID = existingUUID || crypto.randomUUID();
|
||||
|
||||
const existingVote = await handleExistingVotes(BrandingType.Title, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
await handleExistingVotes(BrandingType.Title, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
if (existingUUID) {
|
||||
await updateVoteTotals(BrandingType.Title, existingVote, UUID, shouldLock);
|
||||
await updateVoteTotals(BrandingType.Title, UUID, shouldLock, !!downvote);
|
||||
} else {
|
||||
if (downvote) {
|
||||
throw new Error("Title submission doesn't exist");
|
||||
}
|
||||
|
||||
await db.prepare("run", `INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[videoID, title.title, title.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]);
|
||||
|
||||
@@ -111,10 +120,14 @@ export async function postBranding(req: Request, res: Response) {
|
||||
if (existingUUID != undefined && isBanned) return; // ignore votes on existing details from banned users
|
||||
const UUID = existingUUID || crypto.randomUUID();
|
||||
|
||||
const existingVote = await handleExistingVotes(BrandingType.Thumbnail, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
await handleExistingVotes(BrandingType.Thumbnail, videoID, hashedUserID, UUID, hashedIP, voteType);
|
||||
if (existingUUID) {
|
||||
await updateVoteTotals(BrandingType.Thumbnail, existingVote, UUID, shouldLock);
|
||||
await updateVoteTotals(BrandingType.Thumbnail, UUID, shouldLock, !!downvote);
|
||||
} else {
|
||||
if (downvote) {
|
||||
throw new Error("Thumbnail submission doesn't exist");
|
||||
}
|
||||
|
||||
await db.prepare("run", `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[videoID, thumbnail.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]);
|
||||
|
||||
@@ -152,38 +165,54 @@ export async function postBranding(req: Request, res: Response) {
|
||||
* If no existing vote, it adds one.
|
||||
*/
|
||||
async function handleExistingVotes(type: BrandingType, videoID: VideoID,
|
||||
hashedUserID: HashedUserID, UUID: BrandingUUID, hashedIP: HashedIP, voteType: number): Promise<ExistingVote> {
|
||||
hashedUserID: HashedUserID, UUID: BrandingUUID, hashedIP: HashedIP, voteType: BrandingVoteType) {
|
||||
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]);
|
||||
// Either votes of the same type, or on the same submission (undo a downvote)
|
||||
const existingVotes = await privateDB.prepare("all", `SELECT "id", "UUID", "type" from ${table} where "videoID" = ? AND "userID" = ? AND ("type" = ? OR "UUID" = ?)`, [videoID, hashedUserID, voteType, UUID]) as ExistingVote[];
|
||||
if (existingVotes.length > 0) {
|
||||
// Only one upvote per video
|
||||
if (voteType === BrandingVoteType.Upvote) {
|
||||
for (const existingVote of existingVotes) {
|
||||
switch (existingVote.type) {
|
||||
case BrandingVoteType.Upvote:
|
||||
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]);
|
||||
break;
|
||||
case BrandingVoteType.Downvote:
|
||||
// Undoing a downvote now that it is being upvoted
|
||||
await db.prepare("run", `UPDATE ${table} SET "downvotes" = "downvotes" - 1 WHERE "UUID" = ?`, [existingVote.UUID]);
|
||||
await privateDB.prepare("run", `DELETE FROM ${table} WHERE "id" = ?`, [existingVote.id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await privateDB.prepare("run", `UPDATE ${table} SET "type" = ?, "UUID" = ? WHERE "id" = ?`, [voteType, UUID, existingVote.id]);
|
||||
} else if (!existingVote) {
|
||||
} else {
|
||||
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, shouldLock: boolean): Promise<void> {
|
||||
async function updateVoteTotals(type: BrandingType, UUID: BrandingUUID, shouldLock: boolean, downvote: boolean): Promise<void> {
|
||||
const table = type === BrandingType.Title ? `"titleVotes"` : `"thumbnailVotes"`;
|
||||
|
||||
// Don't upvote if we vote on the same submission
|
||||
if (!existingVote || existingVote.UUID !== UUID) {
|
||||
if (downvote) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "downvotes" = "downvotes" + 1 WHERE "UUID" = ?`, [UUID]);
|
||||
} else {
|
||||
await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" + 1 WHERE "UUID" = ?`, [UUID]);
|
||||
}
|
||||
|
||||
if (shouldLock) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "locked" = 1 WHERE "UUID" = ?`, [UUID]);
|
||||
if (downvote) {
|
||||
await db.prepare("run", `UPDATE ${table} SET "removed" = 1 WHERE "UUID" = ?`, [UUID]);
|
||||
} else {
|
||||
await db.prepare("run", `UPDATE ${table} SET "locked" = 1, "removed" = 0 WHERE "UUID" = ?`, [UUID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface TitleDBResult extends BrandingDBSubmission {
|
||||
title: string,
|
||||
original: number,
|
||||
votes: number,
|
||||
downvotes: number,
|
||||
locked: number,
|
||||
verification: number,
|
||||
userID: UserID
|
||||
@@ -35,6 +36,7 @@ export interface ThumbnailDBResult extends BrandingDBSubmission {
|
||||
timestamp?: number,
|
||||
original: number,
|
||||
votes: number,
|
||||
downvotes: number,
|
||||
locked: number,
|
||||
userID: UserID
|
||||
}
|
||||
@@ -84,6 +86,7 @@ export interface BrandingSubmission {
|
||||
userID: UserID;
|
||||
service: Service;
|
||||
autoLock: boolean | undefined;
|
||||
downvote: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface BrandingSegmentDBResult {
|
||||
|
||||
Reference in New Issue
Block a user