diff --git a/package-lock.json b/package-lock.json index beb6b65..a3dd68c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2065,9 +2065,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "funding": [ { "type": "individual", @@ -4269,9 +4269,9 @@ ] }, "node_modules/simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "funding": [ { "type": "github", @@ -6697,9 +6697,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, "forwarded": { "version": "0.2.0", @@ -8344,9 +8344,9 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "requires": { "decompress-response": "^6.0.0", "once": "^1.3.1", diff --git a/src/routes/getLockCategories.ts b/src/routes/getLockCategories.ts index 26ed81a..3a8cd41 100644 --- a/src/routes/getLockCategories.ts +++ b/src/routes/getLockCategories.ts @@ -7,8 +7,13 @@ import { getService } from "../utils/getService"; export async function getLockCategories(req: Request, res: Response): Promise { const videoID = req.query.videoID as VideoID; const service = getService(req.query.service as string); - const actionTypes = req.query.actionTypes as ActionType[] || [ActionType.Skip, ActionType.Mute]; - + const actionTypes: ActionType[] = req.query.actionTypes + ? JSON.parse(req.query.actionTypes as string) + : req.query.actionType + ? Array.isArray(req.query.actionType) + ? req.query.actionType + : [req.query.actionType] + : [ActionType.Skip, ActionType.Mute]; if (!videoID || !Array.isArray(actionTypes)) { //invalid request return res.sendStatus(400); diff --git a/src/routes/getLockCategoriesByHash.ts b/src/routes/getLockCategoriesByHash.ts index 598f833..227f68a 100644 --- a/src/routes/getLockCategoriesByHash.ts +++ b/src/routes/getLockCategoriesByHash.ts @@ -44,7 +44,13 @@ const mergeLocks = (source: DBLock[], actionTypes: ActionType[]): LockResultByHa export async function getLockCategoriesByHash(req: Request, res: Response): Promise { let hashPrefix = req.params.prefix as VideoIDHash; - const actionTypes = req.query.actionTypes as ActionType[] || [ActionType.Mute, ActionType.Skip]; + const actionTypes: ActionType[] = req.query.actionTypes + ? JSON.parse(req.query.actionTypes as string) + : req.query.actionType + ? Array.isArray(req.query.actionType) + ? req.query.actionType + : [req.query.actionType] + : [ActionType.Skip, ActionType.Mute]; if (!hashPrefixTester(req.params.prefix)) { return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix } diff --git a/src/routes/getLockReason.ts b/src/routes/getLockReason.ts index e39ec01..ef4e5a3 100644 --- a/src/routes/getLockReason.ts +++ b/src/routes/getLockReason.ts @@ -27,9 +27,23 @@ const filterActionType = (actionTypes: ActionType[]) => { export async function getLockReason(req: Request, res: Response): Promise { const videoID = req.query.videoID as VideoID; + if (!videoID) { + // invalid request + return res.status(400).send("No videoID provided"); + } let categories: Category[] = []; - const actionTypes = req.query.actionTypes as ActionType[] || [ActionType.Skip, ActionType.Mute]; + const actionTypes: ActionType[] = req.query.actionTypes + ? JSON.parse(req.query.actionTypes as string) + : req.query.actionType + ? Array.isArray(req.query.actionType) + ? req.query.actionType + : [req.query.actionType] + : [ActionType.Skip, ActionType.Mute]; const possibleCategories = filterActionType(actionTypes); + if (!Array.isArray(actionTypes)) { + //invalid request + return res.status(400).send("actionTypes parameter does not match format requirements"); + } try { categories = req.query.categories ? JSON.parse(req.query.categories as string) diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index 7b287aa..282fea3 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -25,7 +25,9 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = fals SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as "categorySumMusicOfftopic", SUM(CASE WHEN category = 'preview' THEN 1 ELSE 0 END) as "categorySumPreview", SUM(CASE WHEN category = 'poi_highlight' THEN 1 ELSE 0 END) as "categorySumHighlight", - SUM(CASE WHEN category = 'filler' THEN 1 ELSE 0 END) as "categorySumFiller",`; + SUM(CASE WHEN category = 'filler' THEN 1 ELSE 0 END) as "categorySumFiller", + SUM(CASE WHEN category = 'exclusive_access' THEN 1 ELSE 0 END) as "categorySumExclusiveAccess", + `; } const rows = await db.prepare("all", `SELECT COUNT(*) as "totalSubmissions", SUM(views) as "viewCount", @@ -52,6 +54,7 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = fals row.categorySumPreview, row.categorySumHighlight, row.categorySumFiller, + row.categorySumExclusiveAccess ]); } } diff --git a/src/routes/getUserStats.ts b/src/routes/getUserStats.ts index 2bd9b65..41040f0 100644 --- a/src/routes/getUserStats.ts +++ b/src/routes/getUserStats.ts @@ -19,12 +19,17 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea SUM(CASE WHEN "category" = 'music_offtopic' THEN 1 ELSE 0 END) as "categorySumMusicOfftopic", SUM(CASE WHEN "category" = 'preview' THEN 1 ELSE 0 END) as "categorySumPreview", SUM(CASE WHEN "category" = 'poi_highlight' THEN 1 ELSE 0 END) as "categorySumHighlight", - SUM(CASE WHEN "category" = 'filler' THEN 1 ELSE 0 END) as "categorySumFiller",`; + SUM(CASE WHEN "category" = 'filler' THEN 1 ELSE 0 END) as "categorySumFiller", + SUM(CASE WHEN "category" = 'exclusive_access' THEN 1 ELSE 0 END) as "categorySumExclusiveAccess", + `; } if (fetchActionTypeStats) { additionalQuery += ` SUM(CASE WHEN "actionType" = 'skip' THEN 1 ELSE 0 END) as "typeSumSkip", - SUM(CASE WHEN "actionType" = 'mute' THEN 1 ELSE 0 END) as "typeSumMute",`; + SUM(CASE WHEN "actionType" = 'mute' THEN 1 ELSE 0 END) as "typeSumMute", + SUM(CASE WHEN "actionType" = 'full' THEN 1 ELSE 0 END) as "typeSumFull", + SUM(CASE WHEN "actionType" = 'poi' THEN 1 ELSE 0 END) as "typeSumPoi", + `; } try { const row = await db.prepare("get", ` @@ -54,12 +59,15 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea preview: proxy.categorySumPreview, poi_highlight: proxy.categorySumHighlight, filler: proxy.categorySumFiller, + exclusive_access: proxy.categorySumExclusiveAccess, }; } if (fetchActionTypeStats) { result.actionTypeCount = { skip: proxy.typeSumSkip, mute: proxy.typeSumMute, + full: proxy.typeSumFull, + poi: proxy.typeSumPoi }; } return result; diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 8986c91..9bce85b 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -56,15 +56,11 @@ function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise => { - // fetch videoInfo from cache const apiVideoInfo = await getYouTubeVideoInfo(videoID); - // get channelID or fallback to invalid channelID (<> are invalid URL characters and & is not a valid ChannelID character) - const channelID = apiVideoInfo?.data?.authorId ?? ""; + const channelID = apiVideoInfo?.data?.authorId; const { err, reply } = await redis.getAsync(tempVIPKey(nonAnonUserID)); - // return false if error, reply mismatch or reply is not length 21 and not in testing mode - return err ? false : - (config.mode === "test") ? reply == channelID - : (reply?.length === 21 && reply == channelID); + + return err || !reply ? false : (reply == channelID); }; const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2); diff --git a/test/cases/getLockCategories.ts b/test/cases/getLockCategories.ts index c131e86..cf8e6ac 100644 --- a/test/cases/getLockCategories.ts +++ b/test/cases/getLockCategories.ts @@ -5,7 +5,7 @@ import { client } from "../utils/httpClient"; import { mixedDeepEquals } from "../utils/partialDeepEquals"; const endpoint = "/api/lockCategories"; const defaultActionTypes = ["skip", "mute"]; -const getLockCategories = (videoID: string, actionTypes = defaultActionTypes, service = "YouTube") => client.get(endpoint, { params: { videoID, actionTypes, service } }); +const getLockCategories = (videoID: string, actionType = defaultActionTypes, service = "YouTube") => client.get(endpoint, { params: { videoID, actionType, service } }); describe("getLockCategories", () => { before(async () => { @@ -165,7 +165,6 @@ describe("getLockCategories", () => { assert.strictEqual(res.status, 200); const expected = { categories: [ - "sponsor", "interaction" ], reason: "1-longer-reason", @@ -184,6 +183,7 @@ describe("getLockCategories", () => { assert.strictEqual(res.status, 200); const expected = { categories: [ + "sponsor", "nonmusic", "outro" ], @@ -195,4 +195,31 @@ describe("getLockCategories", () => { }) .catch(err => done(err)); }); + + it("should return 200 by single actionType", (done) => { + client.get(`${endpoint}?videoID=getLockCategory1&actionType=mute`) + .then(res => { + assert.strictEqual(res.status, 200); + done(); + }) + .catch(err => done(err)); + }); + + it("should return 200 by single actionTypes JSON", (done) => { + client.get(`${endpoint}?videoID=getLockCategory1&actionTypes=["mute"]`) + .then(res => { + assert.strictEqual(res.status, 200); + done(); + }) + .catch(err => done(err)); + }); + + it("should return 200 by repeating actionTypes", (done) => { + client.get(`${endpoint}?videoID=getLockCategory1&actionType=mute&actionType=skip`) + .then(res => { + assert.strictEqual(res.status, 200); + done(); + }) + .catch(err => done(err)); + }); }); diff --git a/test/cases/getLockCategoriesByHash.ts b/test/cases/getLockCategoriesByHash.ts index fc5e364..9695731 100644 --- a/test/cases/getLockCategoriesByHash.ts +++ b/test/cases/getLockCategoriesByHash.ts @@ -6,7 +6,7 @@ import { ActionType } from "../../src/types/segments.model"; const fakeHash = "b05a20424f24a53dac1b059fb78d861ba9723645026be2174c93a94f9106bb35"; const endpoint = "/api/lockCategories"; -const getLockCategories = (hash: string, actionTypes = [ActionType.Mute, ActionType.Skip]) => client.get(`${endpoint}/${hash}`, { params: { actionTypes } }); +const getLockCategories = (hash: string, actionType = [ActionType.Mute, ActionType.Skip]) => client.get(`${endpoint}/${hash}`, { params: { actionType } }); describe("getLockCategoriesByHash", () => { before(async () => { diff --git a/test/cases/getLockReason.ts b/test/cases/getLockReason.ts index cb8f29a..1bb0dfd 100644 --- a/test/cases/getLockReason.ts +++ b/test/cases/getLockReason.ts @@ -129,7 +129,7 @@ describe("getLockReason", () => { }); it("should be able to get by actionType", (done) => { - client.get(endpoint, { params: { videoID: "getLockReason", actionTypes: ["full"] } }) + client.get(endpoint, { params: { videoID: "getLockReason", actionType: "full" } }) .then(res => { assert.strictEqual(res.status, 200); const expected = [ diff --git a/test/cases/getUserStats.ts b/test/cases/getUserStats.ts index 7704b15..df096bd 100644 --- a/test/cases/getUserStats.ts +++ b/test/cases/getUserStats.ts @@ -10,17 +10,19 @@ describe("getUserStats", () => { const insertUserNameQuery = 'INSERT INTO "userNames" ("userID", "userName") VALUES(?, ?)'; await db.prepare("run", insertUserNameQuery, [getHash("getuserstats_user_01"), "Username user 01"]); - const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid1", getHash("getuserstats_user_01"), 1, 1, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid2", getHash("getuserstats_user_01"), 2, 2, "selfpromo", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid3", getHash("getuserstats_user_01"), 3, 3, "interaction", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid4", getHash("getuserstats_user_01"), 4, 4, "intro", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid5", getHash("getuserstats_user_01"), 5, 5, "outro", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid6", getHash("getuserstats_user_01"), 6, 6, "preview", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid7", getHash("getuserstats_user_01"), 7, 7, "music_offtopic", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 11, 11, 0, "getuserstatsuuid8", getHash("getuserstats_user_01"), 8, 8, "poi_highlight", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, -2, "getuserstatsuuid9", getHash("getuserstats_user_02"), 8, 2, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "getuserstatsuuid10", getHash("getuserstats_user_01"), 8, 2, "filler", 0]); + const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "actionType", "UUID", "userID", "timeSubmitted", views, category, "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid1", getHash("getuserstats_user_01"), 1, 1, "sponsor", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid2", getHash("getuserstats_user_01"), 2, 2, "selfpromo", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid3", getHash("getuserstats_user_01"), 3, 3, "interaction", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid4", getHash("getuserstats_user_01"), 4, 4, "intro", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid5", getHash("getuserstats_user_01"), 5, 5, "outro", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid6", getHash("getuserstats_user_01"), 6, 6, "preview", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid7", getHash("getuserstats_user_01"), 7, 7, "music_offtopic", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 11, 11, 0, "poi", "getuserstatsuuid8", getHash("getuserstats_user_01"), 8, 8, "poi_highlight", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, -2, "skip", "getuserstatsuuid9", getHash("getuserstats_user_02"), 8, 2, "sponsor", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid10", getHash("getuserstats_user_01"), 8, 2, "filler", 0]); + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 0, 0, "full", "getuserstatsuuid11", getHash("getuserstats_user_01"), 8, 2, "exclusive_access", 0]); + }); @@ -49,15 +51,18 @@ describe("getUserStats", () => { preview: 1, music_offtopic: 1, poi_highlight: 1, - filler: 1 + filler: 1, + exclusive_access: 1 }, actionTypeCount: { mute: 0, - skip: 9 + skip: 8, + full: 1, + poi: 1 }, overallStats: { minutesSaved: 30, - segmentCount: 9 + segmentCount: 10 } }; assert.ok(partialDeepEquals(res.data, expected));