diff --git a/src/routes/postBranding.ts b/src/routes/postBranding.ts index 072e51d..003311a 100644 --- a/src/routes/postBranding.ts +++ b/src/routes/postBranding.ts @@ -120,7 +120,7 @@ export async function postBranding(req: Request, res: Response) { })(), (async () => { if (thumbnail) { // ignore original submissions from banned users - hiding those would cause issues - if (thumbnail.original && isBanned) return; + if (thumbnail.original && (isBanned || !await canSubmitOriginal(hashedUserID, isVip))) return; const existingUUID = thumbnail.original ? (await db.prepare("get", `SELECT "UUID" from "thumbnails" where "videoID" = ? AND "original" = 1`, [videoID]))?.UUID @@ -299,6 +299,14 @@ export async function verifyOldSubmissions(hashedUserID: HashedUserID, verificat } } +async function canSubmitOriginal(hashedUserID: HashedUserID, isVip: boolean): Promise { + const upvotedThumbs = (await db.prepare("get", `SELECT count(*) as upvotedThumbs FROM "thumbnails" JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" WHERE "thumbnailVotes"."votes" > 0 AND "thumbnails"."original" = 0 AND "thumbnails"."userID" = ?`, [hashedUserID])).upvotedThumbs; + const customThumbs = (await db.prepare("get", `SELECT count(*) as customThumbs FROM "thumbnails" JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" WHERE "thumbnailVotes"."votes" >= 0 AND "thumbnails"."original" = 0 AND "thumbnails"."userID" = ?`, [hashedUserID])).customThumbs; + const originalThumbs = (await db.prepare("get", `SELECT count(*) as originalThumbs FROM "thumbnails" JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" WHERE "thumbnailVotes"."votes" >= 0 AND "thumbnails"."original" = 1 AND "thumbnails"."userID" = ?`, [hashedUserID])).originalThumbs; + + return isVip || (upvotedThumbs > 1 && customThumbs > 1 && originalThumbs / customThumbs < 0.4); +} + async function sendWebhooks(videoID: VideoID, UUID: BrandingUUID, voteType: BrandingVoteType) { const currentSubmission = await db.prepare( "get", diff --git a/test/cases/postBranding.ts b/test/cases/postBranding.ts index c665cfc..7d3d985 100644 --- a/test/cases/postBranding.ts +++ b/test/cases/postBranding.ts @@ -49,10 +49,23 @@ describe("postBranding", () => { const insertThumbnailQuery = 'INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)'; await db.prepare("run", insertThumbnailQuery, ["postBrandLocked1", 0, getHash(userID3), Service.YouTube, getHash("postBrandLocked1"), Date.now(), "postBrandLocked1"]); await db.prepare("run", insertThumbnailQuery, ["postBrandLocked2", 1, getHash(userID4), Service.YouTube, getHash("postBrandLocked2"), Date.now(), "postBrandLocked2"]); + const insertThumbnailVotesQuery = 'INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, ?, ?, ?);'; await db.prepare("run", insertThumbnailVotesQuery, ["postBrandLocked1", 0, 1, 0]); await db.prepare("run", insertThumbnailVotesQuery, ["postBrandLocked2", 0, 1, 0]); + // Approved original thumbnail submitter + await db.prepare("run", insertThumbnailQuery, ["postBrandOriginThumb", 0, getHash(userID4), Service.YouTube, getHash("postBrandOriginThumb"), Date.now(), "postBrandOriginThumb"]); + await db.prepare("run", insertThumbnailQuery, ["postBrandOriginThumb2", 0, getHash(userID4), Service.YouTube, getHash("postBrandOriginThumb2"), Date.now(), "postBrandOriginThumb2"]); + await db.prepare("run", insertThumbnailQuery, ["postBrandOriginThumb3", 0, getHash(userID4), Service.YouTube, getHash("postBrandOriginThumb3"), Date.now(), "postBrandOriginThumb3"]); + await db.prepare("run", insertThumbnailQuery, ["postBrandOriginThumb4", 0, getHash(userID4), Service.YouTube, getHash("postBrandOriginThumb4"), Date.now(), "postBrandOriginThumb4"]); + await db.prepare("run", insertThumbnailQuery, ["postBrandOriginThumb5", 0, getHash(userID4), Service.YouTube, getHash("postBrandOriginThumb5"), Date.now(), "postBrandOriginThumb5"]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandOriginThumb", 4, 0, 0]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandOriginThumb2", 1, 0, 0]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandOriginThumb3", 0, 0, 0]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandOriginThumb4", 0, 0, 0]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandOriginThumb5", 0, 0, 0]); + // Testing vip submission removal await db.prepare("run", insertTitleQuery, ["postBrandRemoved1", "Some title", 0, getHash(userID1), Service.YouTube, getHash("postBrandRemoved1"), Date.now(), "postBrandRemoved1"]); await db.prepare("run", insertTitleVotesQuery, ["postBrandRemoved1", 0, 1, 0, 0]); @@ -139,7 +152,7 @@ describe("postBranding", () => { assert.strictEqual(dbVotes.shadowHidden, 0); }); - it("Submit only original thumbnail", async () => { + it("Submit only original thumbnail without permission", async () => { const videoID = "postBrand3"; const thumbnail = { original: true @@ -152,6 +165,25 @@ describe("postBranding", () => { videoID }); + assert.strictEqual(res.status, 200); + const dbThumbnail = await queryThumbnailByVideo(videoID); + + assert.strictEqual(dbThumbnail, undefined); + }); + + it("Submit only original thumbnail with permission", async () => { + const videoID = "postBrand3"; + const thumbnail = { + original: true + }; + + const res = await postBranding({ + thumbnail, + userID: userID4, + service: Service.YouTube, + videoID + }); + assert.strictEqual(res.status, 200); const dbThumbnail = await queryThumbnailByVideo(videoID); const dbVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); @@ -163,6 +195,30 @@ describe("postBranding", () => { assert.strictEqual(dbVotes.shadowHidden, 0); }); + it("Submit only original thumbnail as VIP", async () => { + const videoID = "postBrandV3"; + const thumbnail = { + original: true + }; + + const res = await postBranding({ + thumbnail, + userID: vipUser, + service: Service.YouTube, + videoID + }); + + assert.strictEqual(res.status, 200); + const dbThumbnail = await queryThumbnailByVideo(videoID); + const dbVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); + + assert.strictEqual(dbThumbnail.original, thumbnail.original ? 1 : 0); + + assert.strictEqual(dbVotes.votes, 0); + assert.strictEqual(dbVotes.locked, 1); + assert.strictEqual(dbVotes.shadowHidden, 0); + }); + it("Submit only custom thumbnail", async () => { const videoID = "postBrand4"; const thumbnail = { @@ -873,7 +929,7 @@ describe("postBranding", () => { const res = await postBranding({ thumbnail, - userID: userID3, + userID: userID4, service: Service.YouTube, videoID });