diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1e5a11f..8407bf6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,7 @@ jobs: env: PG_USER: ci_db_user PG_PASS: ci_db_pass - run: docker-compose -f docker/docker-compose-ci.yml up -d + run: docker compose -f docker/docker-compose-ci.yml up -d - name: Check running containers run: docker ps - uses: actions/setup-node@v3 diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 1b69da4..adb1923 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -241,7 +241,7 @@ | title | TEXT | not null | | original | INTEGER | default 0 | | userID | TEXT | not null -| service | TEXT not null | +| service | TEXT | not null | | hashedVideoID | TEXT | not null | | timeSubmitted | INTEGER | not null | | UUID | TEXT | not null, primary key @@ -277,7 +277,6 @@ | Name | Type | | | -- | :--: | -- | -| UUID | TEXT | not null | | original | INTEGER | default 0 | | userID | TEXT | not null | | service | TEXT | not null | @@ -305,8 +304,8 @@ ### thumbnailVotes -| index | field | -| -- | :--: | +| Name | Type | | +| -- | :--: | -- | | UUID | TEXT | not null, primary key | | votes | INTEGER | not null, default 0 | | locked | INTEGER |not null, default 0 | diff --git a/src/routes/getChapterNames.ts b/src/routes/getChapterNames.ts index c40030a..e78cc84 100644 --- a/src/routes/getChapterNames.ts +++ b/src/routes/getChapterNames.ts @@ -27,10 +27,11 @@ export async function getChapterNames(req: Request, res: Response): Promise= 0.1 GROUP BY "description" ORDER BY SUM("votes"), similarity("description", ?) DESC LIMIT 5;` - , [channelID, description]) as { description: string }[]; + , [channelID, description, description]) as { description: string }[]; if (descriptions?.length > 0) { return res.status(200).json(descriptions.map(d => ({ diff --git a/src/routes/postBranding.ts b/src/routes/postBranding.ts index 33128e7..d45d6d0 100644 --- a/src/routes/postBranding.ts +++ b/src/routes/postBranding.ts @@ -85,6 +85,7 @@ export async function postBranding(req: Request, res: Response) { const existingIsLocked = !!existingUUID && (await db.prepare("get", `SELECT "locked" from "titleVotes" where "UUID" = ?`, [existingUUID]))?.locked; if (existingUUID != undefined && isBanned) return; // ignore votes on existing details from banned users if (downvote && existingIsLocked && !isVip) { + sendWebhooks(videoID, existingUUID, voteType).catch((e) => Logger.error(e)); errorCode = 403; return; } @@ -113,7 +114,7 @@ export async function postBranding(req: Request, res: Response) { await db.prepare("run", `UPDATE "titleVotes" as tv SET "locked" = 0 FROM "titles" t WHERE tv."UUID" = t."UUID" AND tv."UUID" != ? AND t."videoID" = ?`, [UUID, videoID]); } - sendWebhooks(videoID, UUID).catch((e) => Logger.error(e)); + sendWebhooks(videoID, UUID, voteType).catch((e) => Logger.error(e)); } })(), (async () => { if (thumbnail) { @@ -290,14 +291,34 @@ export async function verifyOldSubmissions(hashedUserID: HashedUserID, verificat } } -async function sendWebhooks(videoID: VideoID, UUID: BrandingUUID) { - const lockedSubmission = await db.prepare("get", `SELECT "titleVotes"."votes", "titles"."title", "titles"."userID" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" WHERE "titles"."videoID" = ? AND "titles"."UUID" != ? AND "titleVotes"."locked" = 1`, [videoID, UUID]); +async function sendWebhooks(videoID: VideoID, UUID: BrandingUUID, voteType: BrandingVoteType) { + const currentSubmission = await db.prepare( + "get", + `SELECT + "titles"."title", + "titleVotes"."locked", + "titles"."userID", + "titleVotes"."votes"-"titleVotes"."downvotes"+"titleVotes"."verification" AS "score" + FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" + WHERE "titles"."UUID" = ?`, + [UUID]); - if (lockedSubmission) { - const currentSubmission = await db.prepare("get", `SELECT "titleVotes"."votes", "titles"."title" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" WHERE "titles"."UUID" = ?`, [UUID]); + // Unlocked title getting more upvotes than the locked one + if (voteType === BrandingVoteType.Upvote) { + const lockedSubmission = await db.prepare( + "get", + `SELECT + "titles"."title", + "titles"."userID", + "titleVotes"."votes"-"titleVotes"."downvotes"+"titleVotes"."verification" AS "score" + FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" + WHERE "titles"."videoID" = ? + AND "titles"."UUID" != ? + AND "titleVotes"."locked" = 1`, + [videoID, UUID]); // Time to warn that there may be an issue - if (currentSubmission.votes - lockedSubmission.votes > 2) { + if (lockedSubmission && currentSubmission.score - lockedSubmission.score > 2) { const usernameRow = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [lockedSubmission.userID]); const data = await getVideoDetails(videoID); @@ -305,7 +326,7 @@ async function sendWebhooks(videoID: VideoID, UUID: BrandingUUID) { "embeds": [{ "title": data?.title, "url": `https://www.youtube.com/watch?v=${videoID}`, - "description": `**${lockedSubmission.votes}** Votes vs **${currentSubmission.votes}**\ + "description": `**${lockedSubmission.score}** score vs **${currentSubmission.score}**\ \n\n**Locked title:** ${lockedSubmission.title}\ \n**New title:** ${currentSubmission.title}\ \n\n**Submitted by:** ${usernameRow?.userName ?? ""}\n${lockedSubmission.userID}`, @@ -329,6 +350,38 @@ async function sendWebhooks(videoID: VideoID, UUID: BrandingUUID) { }); } } + + // Downvotes on locked title + if (voteType === BrandingVoteType.Downvote && currentSubmission.locked === 1) { + const usernameRow = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [currentSubmission.userID]); + + const data = await getVideoDetails(videoID); + axios.post(config.discordDeArrowLockedWebhookURL, { + "embeds": [{ + "title": data?.title, + "url": `https://www.youtube.com/watch?v=${videoID}`, + "description": `Locked title with **${currentSubmission.score}** score received a downvote\ + \n\n**Locked title:** ${currentSubmission.title}\ + \n**Submitted by:** ${usernameRow?.userName ?? ""}\n${currentSubmission.userID}`, + "color": 10813440, + "thumbnail": { + "url": getMaxResThumbnail(videoID), + }, + }], + }) + .then(res => { + if (res.status >= 400) { + Logger.error("Error sending reported submission Discord hook"); + Logger.error(JSON.stringify((res.data))); + Logger.error("\n"); + } + }) + .catch(err => { + Logger.error("Failed to send reported submission Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); + }); + } } async function checkForWrongVideoDuration(videoID: VideoID, duration: number): Promise { @@ -336,4 +389,4 @@ async function checkForWrongVideoDuration(videoID: VideoID, duration: number): P const apiDuration = apiVideoDetails?.duration; return apiDuration && apiDuration > 2 && duration && duration > 2 && Math.abs(apiDuration - duration) > 3; -} \ No newline at end of file +} diff --git a/test/cases/getChapterNames.ts b/test/cases/getChapterNames.ts index 01bb7c5..df386ac 100644 --- a/test/cases/getChapterNames.ts +++ b/test/cases/getChapterNames.ts @@ -16,17 +16,18 @@ describe("getChapterNames", function () { "Weird name", "A different one", "Something else", + "Weirder name", ]; - const nameSearch = (query: string, expected: string): Promise => { + const nameSearch = (query: string, expected: string | null, expectedResults: number): Promise => { const expectedData = [{ description: expected }]; return client.get(`${endpoint}?description=${query}&channelID=${chapterChannelID}`) .then(res => { - assert.strictEqual(res.status, 200); - assert.strictEqual(res.data.length, chapterNames.length); - assert.ok(partialDeepEquals(res.data, expectedData)); + assert.strictEqual(res.status, expectedResults == 0 ? 404 : 200); + assert.strictEqual(res.data.length, expectedResults); + if (expected != null) assert.ok(partialDeepEquals(res.data, expectedData)); }); }; @@ -35,11 +36,13 @@ describe("getChapterNames", function () { await insertChapter(db, chapterNames[0], { videoID: chapterNamesVid1, startTime: 60, endTime: 80 }); await insertChapter(db, chapterNames[1], { videoID: chapterNamesVid1, startTime: 70, endTime: 75 }); await insertChapter(db, chapterNames[2], { videoID: chapterNamesVid1, startTime: 71, endTime: 76 }); + await insertChapter(db, chapterNames[3], { videoID: chapterNamesVid1, startTime: 72, endTime: 77 }); await insertVideoInfo(db, chapterNamesVid1, chapterChannelID); }); - it("Search for 'weird'", () => nameSearch("weird", chapterNames[0])); - it("Search for 'different'", () => nameSearch("different", chapterNames[1])); - it("Search for 'something'", () => nameSearch("something", chapterNames[2])); -}); \ No newline at end of file + it("Search for 'weird' (2 results)", () => nameSearch("weird", chapterNames[0], 2)); + it("Search for 'different' (1 result)", () => nameSearch("different", chapterNames[1], 1)); + it("Search for 'something' (1 result)", () => nameSearch("something", chapterNames[2], 1)); + it("Search for 'unrelated' (0 result)", () => nameSearch("unrelated", null, 0)); +});