diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 3d792d2..abb0539 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -1,15 +1,15 @@ # SponsorTimesDB -[vipUsers](###vipUsers) -[sponsorTimes](###sponsorTimes) -[userNames](###userNames) -[userNameLogs](###userNameLogs) -[categoryVotes](###categoryVotes) -[lockCategories](###lockCategories) -[warnings](###warnings) -[shadowBannedUsers](###shadowBannedUsers) -[unlistedVideos](###unlistedVideos) -[config](###config) +[vipUsers](#vipUsers) +[sponsorTimes](#sponsorTimes) +[userNames](#userNames) +[userNameLogs](#userNameLogs) +[categoryVotes](#categoryVotes) +[lockCategories](#lockCategories) +[warnings](#warnings) +[shadowBannedUsers](#shadowBannedUsers) +[unlistedVideos](#unlistedVideos) +[config](#config) ### vipUsers | Name | Type | | @@ -142,10 +142,10 @@ # Private -[vote](###vote) -[categoryVotes](###categoryVotes) -[sponsorTimes](###sponsorTimes) -[config](###config) +[vote](#vote) +[categoryVotes](#categoryVotes) +[sponsorTimes](#sponsorTimes) +[config](#config) ### vote diff --git a/package-lock.json b/package-lock.json index 10ab124..075da12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "express-promise-router": "^4.1.0", "express-rate-limit": "^5.1.3", "http": "0.0.0", - "iso8601-duration": "^1.2.0", "node-fetch": "^2.6.0", "pg": "^8.5.1", "redis": "^3.1.1", @@ -1501,9 +1500,9 @@ } }, "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" @@ -1842,11 +1841,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "node_modules/iso8601-duration": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz", - "integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg==" - }, "node_modules/js-yaml": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", @@ -5180,9 +5174,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -5432,11 +5426,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "iso8601-duration": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz", - "integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg==" - }, "js-yaml": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", diff --git a/package.json b/package.json index 87a50fa..e33c42e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "express-promise-router": "^4.1.0", "express-rate-limit": "^5.1.3", "http": "0.0.0", - "iso8601-duration": "^1.2.0", "node-fetch": "^2.6.0", "pg": "^8.5.1", "redis": "^3.1.1", diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 704cb13..8f78b5d 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -3,7 +3,7 @@ import { config } from '../config'; import { db, privateDB } from '../databases/databases'; import { skipSegmentsHashKey, skipSegmentsKey } from '../utils/redisKeys'; import { SBRecord } from '../types/lib.model'; -import { Category, CategoryActionType, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, Service, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model"; +import { Category, CategoryActionType, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, SegmentUUID, Service, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model"; import { getCategoryActionType } from '../utils/categoryInfo'; import { getHash } from '../utils/getHash'; import { getIP } from '../utils/getIP'; @@ -14,7 +14,7 @@ import { getReputation } from '../utils/reputation'; async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise { const shouldFilter: boolean[] = await Promise.all(segments.map(async (segment) => { - if (segment.votes < -1) { + if (segment.votes < -1 && !segment.required) { return false; //too untrustworthy, just ignore it } @@ -50,7 +50,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category: })); } -async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], service: Service): Promise { +async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], requiredSegments: SegmentUUID[], service: Service): Promise { const cache: SegmentCache = {shadowHiddenSegmentIPs: {}}; const segments: Segment[] = []; @@ -61,6 +61,8 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: const segmentsByCategory: SBRecord = (await getSegmentsFromDBByVideoID(videoID, service)) .filter((segment: DBSegment) => categories.includes(segment?.category)) .reduce((acc: SBRecord, segment: DBSegment) => { + if (requiredSegments.includes(segment.UUID)) segment.required = true; + acc[segment.category] = acc[segment.category] || []; acc[segment.category].push(segment); @@ -80,7 +82,7 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: } } -async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[], service: Service): Promise> { +async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[], requiredSegments: SegmentUUID[], service: Service): Promise> { const cache: SegmentCache = {shadowHiddenSegmentIPs: {}}; const segments: SBRecord = {}; @@ -97,8 +99,9 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, hash: segment.hashedVideoID, segmentPerCategory: {}, }; - const videoCategories = acc[segment.videoID].segmentPerCategory; + if (requiredSegments.includes(segment.UUID)) segment.required = true; + const videoCategories = acc[segment.videoID].segmentPerCategory; videoCategories[segment.category] = videoCategories[segment.category] || []; videoCategories[segment.category].push(segment); @@ -214,7 +217,7 @@ async function chooseSegments(segments: DBSegment[], max: number): Promise cursor) { - currentGroup = {segments: [], votes: 0, reputation: 0, locked: false}; + currentGroup = {segments: [], votes: 0, reputation: 0, locked: false, required: false}; overlappingSegmentsGroups.push(currentGroup); } @@ -233,11 +236,18 @@ async function chooseSegments(segments: DBSegment[], max: number): Promise { - if (group.locked) { + if (group.required) { + // Required beats locked + group.segments = group.segments.filter((segment) => segment.required); + } else if (group.locked) { group.segments = group.segments.filter((segment) => segment.locked); } @@ -277,12 +287,24 @@ async function handleGetSegments(req: Request, res: Response): Promise val == service)) { service = Service.YouTube; } - const segments = await getSegmentsByVideoID(req, videoID, categories, service); + const segments = await getSegmentsByVideoID(req, videoID, categories, requiredSegments, service); if (segments === null || segments === undefined) { res.sendStatus(500); diff --git a/src/routes/getSkipSegmentsByHash.ts b/src/routes/getSkipSegmentsByHash.ts index 33bda9a..a632581 100644 --- a/src/routes/getSkipSegmentsByHash.ts +++ b/src/routes/getSkipSegmentsByHash.ts @@ -1,7 +1,7 @@ import {hashPrefixTester} from '../utils/hashPrefixTester'; import {getSegmentsByHash} from './getSkipSegments'; import {Request, Response} from 'express'; -import { Category, Service, VideoIDHash } from '../types/segments.model'; +import { Category, SegmentUUID, Service, VideoIDHash } from '../types/segments.model'; export async function getSkipSegmentsByHash(req: Request, res: Response): Promise { let hashPrefix = req.params.prefix as VideoIDHash; @@ -15,16 +15,33 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis categories = req.query.categories ? JSON.parse(req.query.categories as string) : req.query.category - ? [req.query.category] - : ["sponsor"]; + ? Array.isArray(req.query.category) + ? req.query.category + : [req.query.category] + : ['sponsor']; if (!Array.isArray(categories)) { return res.status(400).send("Categories parameter does not match format requirements."); } - } - catch(error) { + } catch(error) { return res.status(400).send("Bad parameter: categories (invalid JSON)"); } + let requiredSegments: SegmentUUID[] = []; + try { + requiredSegments = req.query.requiredSegments + ? JSON.parse(req.query.requiredSegments as string) + : req.query.requiredSegment + ? Array.isArray(req.query.requiredSegment) + ? req.query.requiredSegment + : [req.query.requiredSegment] + : []; + if (!Array.isArray(requiredSegments)) { + return res.status(400).send("requiredSegments parameter does not match format requirements."); + } + } catch(error) { + return res.status(400).send("Bad parameter: requiredSegments (invalid JSON)"); + } + let service: Service = req.query.service ?? req.body.service ?? Service.YouTube; if (!Object.values(Service).some((val) => val == service)) { service = Service.YouTube; @@ -34,7 +51,7 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis categories = categories.filter((item: any) => typeof item === "string"); // Get all video id's that match hash prefix - const segments = await getSegmentsByHash(req, hashPrefix, categories, service); + const segments = await getSegmentsByHash(req, hashPrefix, categories, requiredSegments, service); if (!segments) return res.status(404).json([]); diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 4b64429..94c6a07 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -266,6 +266,34 @@ async function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promi } } +async function checkUserActiveWarning(userID: string): Promise<{ pass: boolean; errorMessage: string; }> { + const MILLISECONDS_IN_HOUR = 3600000; + const now = Date.now(); + const warnings = await db.prepare('all', + `SELECT "reason" + FROM warnings + WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1 + ORDER BY "issueTime" DESC + LIMIT ?`, + [ + userID, + Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR)), + config.maxNumberOfActiveWarnings + ], + ) as {reason: string}[]; + + if (warnings?.length >= config.maxNumberOfActiveWarnings) { + const defaultMessage = 'Submission rejected due to a warning from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. Could you please send a message in Discord or Matrix so we can further help you?'; + + return { + pass: false, + errorMessage: warnings[0]?.reason?.length > 0 ? warnings[0].reason : defaultMessage + }; + } + + return {pass: true, errorMessage: ''}; +} + function proxySubmission(req: Request) { fetch(config.proxySubmission + '/api/skipSegments?userID=' + req.query.userID + '&videoID=' + req.query.videoID, { method: 'POST', @@ -321,17 +349,9 @@ export async function postSkipSegments(req: Request, res: Response): Promise ? AND enabled = 1`, - [userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))], - )).count; - - if (warningsCount >= config.maxNumberOfActiveWarnings) { - return res.status(403).send('Submission rejected due to a warning from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. Could you please send a message in Discord or Matrix so we can further help you?'); + const warningResult: {pass: boolean, errorMessage: string} = await checkUserActiveWarning(userID); + if (!warningResult.pass) { + return res.status(403).send(warningResult.errorMessage); } let lockedCategoryList = (await db.prepare('all', 'SELECT category from "lockCategories" where "videoID" = ?', [videoID])).map((list: any) => list.category ); @@ -341,9 +361,15 @@ export async function postSkipSegments(req: Request, res: Response): Promise= 0 AND "videoDuration" != 0`, [videoID, service]) as - {videoDuration: VideoDuration, UUID: SegmentUUID}[]; + const previousSubmissions = await db.prepare('all', + `SELECT "videoDuration", "UUID" + FROM "sponsorTimes" + WHERE "videoID" = ? AND "service" = ? AND + "hidden" = 0 AND "shadowHidden" = 0 AND + "votes" >= 0 AND "videoDuration" != 0`, + [videoID, service] + ) as {videoDuration: VideoDuration, UUID: SegmentUUID}[]; + // If the video's duration is changed, then the video should be unlocked and old submissions should be hidden const videoDurationChanged = (videoDuration: number) => videoDuration != 0 && previousSubmissions.length > 0 && !previousSubmissions.some((e) => Math.abs(videoDuration - e.videoDuration) < 2); @@ -436,6 +462,9 @@ export async function postSkipSegments(req: Request, res: Response): Promise { await db.prepare("run", query, ['locked', 20, 33, 2, 1, '1-uuid-locked-8', 'testman', 0, 50, 'intro', 'YouTube', 230, 0, 0, getHash('locked', 1)]); await db.prepare("run", query, ['locked', 20, 34, 100000, 0, '1-uuid-9', 'testman', 0, 50, 'intro', 'YouTube', 190, 0, 0, getHash('locked', 1)]); await db.prepare("run", query, ['onlyHiddenSegments', 20, 34, 100000, 0, 'onlyHiddenSegments', 'testman', 0, 50, 'sponsor', 'YouTube', 190, 1, 0, getHash('onlyHiddenSegments', 1)]); + await db.prepare("run", query, ['requiredSegmentVid-raw', 60, 70, 2, 0, 'requiredSegmentVid-raw-1', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 0, getHash('requiredSegmentVid-raw', 1)]); + await db.prepare("run", query, ['requiredSegmentVid-raw', 60, 70, -2, 0, 'requiredSegmentVid-raw-2', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 0, getHash('requiredSegmentVid-raw', 1)]); + await db.prepare("run", query, ['requiredSegmentVid-raw', 80, 90, -2, 0, 'requiredSegmentVid-raw-3', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 0, getHash('requiredSegmentVid-raw', 1)]); + await db.prepare("run", query, ['requiredSegmentVid-raw', 80, 90, 2, 0, 'requiredSegmentVid-raw-4', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 0, getHash('requiredSegmentVid-raw', 1)]); return; }); @@ -309,4 +313,34 @@ describe('getSkipSegments', () => { }) .catch(() => ("Couldn't call endpoint")); }); + + it('Should be able to get specific segments with requiredSegments', (done: Done) => { + fetch(getbaseURL() + '/api/skipSegments?videoID=requiredSegmentVid-raw&requiredSegments=["requiredSegmentVid-raw-2","requiredSegmentVid-raw-3"]') + .then(async res => { + if (res.status !== 200) done("non 200 status code, was " + res.status); + else { + const body = await res.json(); + if (body.length !== 2) done("expected 2 segments, got " + body.length); + else if (body[0].UUID !== 'requiredSegmentVid-raw-2' + || body[1].UUID !== 'requiredSegmentVid-raw-3') done("Did not recieve the correct segments\n" + JSON.stringify(body, null, 2)); + else done(); + } + }) + .catch(err => done("Couldn't call endpoint")); + }); + + it('Should be able to get specific segments with repeating requiredSegment', (done: Done) => { + fetch(getbaseURL() + '/api/skipSegments?videoID=requiredSegmentVid-raw&requiredSegment=requiredSegmentVid-raw-2&requiredSegment=requiredSegmentVid-raw-3') + .then(async res => { + if (res.status !== 200) done("non 200 status code, was " + res.status); + else { + const body = await res.json(); + if (body.length !== 2) done("expected 2 segments, got " + body.length); + else if (body[0].UUID !== 'requiredSegmentVid-raw-2' + || body[1].UUID !== 'requiredSegmentVid-raw-3') done("Did not recieve the correct segments\n" + JSON.stringify(body, null, 2)); + else done(); + } + }) + .catch(err => done("Couldn't call endpoint")); + }); }); diff --git a/test/cases/getSkipSegmentsByHash.ts b/test/cases/getSkipSegmentsByHash.ts index 18f19b4..1aeb780 100644 --- a/test/cases/getSkipSegmentsByHash.ts +++ b/test/cases/getSkipSegmentsByHash.ts @@ -21,6 +21,10 @@ describe('getSegmentsByHash', () => { await db.prepare("run", query, ['onlyHidden', 60, 70, 2, 'onlyHidden', 'testman', 0, 50, 'sponsor', 'YouTube', 1, 0, 'f3a199e1af001d716cdc6599360e2b062c2d2b3fa2885f6d9d2fd741166cbbd3']); await db.prepare("run", query, ['highlightVid', 60, 60, 2, 'highlightVid-1', 'testman', 0, 50, 'highlight', 'YouTube', 0, 0, getHash('highlightVid', 1)]); await db.prepare("run", query, ['highlightVid', 70, 70, 2, 'highlightVid-2', 'testman', 0, 50, 'highlight', 'YouTube', 0, 0, getHash('highlightVid', 1)]); + await db.prepare("run", query, ['requiredSegmentVid', 60, 70, 2, 'requiredSegmentVid-1', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'd51822c3f681e07aef15a8855f52ad12db9eb9cf059e65b16b64c43359557f61']); + await db.prepare("run", query, ['requiredSegmentVid', 60, 70, -2, 'requiredSegmentVid-2', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'd51822c3f681e07aef15a8855f52ad12db9eb9cf059e65b16b64c43359557f61']); + await db.prepare("run", query, ['requiredSegmentVid', 80, 90, -2, 'requiredSegmentVid-3', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'd51822c3f681e07aef15a8855f52ad12db9eb9cf059e65b16b64c43359557f61']); + await db.prepare("run", query, ['requiredSegmentVid', 80, 90, 2, 'requiredSegmentVid-4', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'd51822c3f681e07aef15a8855f52ad12db9eb9cf059e65b16b64c43359557f61']); }); it('Should be able to get a 200', (done: Done) => { @@ -219,4 +223,67 @@ describe('getSegmentsByHash', () => { }) .catch(err => done('(post) ' + err)); }); + + it('Should be able to get multiple categories with repeating parameters', (done: Done) => { + fetch(getbaseURL() + "/api/skipSegments/fdaff4?&category=sponsor&category=intro") + .then(async res => { + if (res.status !== 200) done("Status code was: " + res.status); + else { + const body = await res.json(); + if (body.length !== 1) done("expected 1 video, got " + body.length); + + const data = body[0].segments; + if (data.length === 2) { + let success = true; + for (const segment of data) { + if ((segment.segment[0] !== 1 || segment.segment[1] !== 10 + || segment.category !== "sponsor" || segment.UUID !== "getSegmentsByHash-0-0") && + (segment.segment[0] !== 20 || segment.segment[1] !== 30 + || segment.category !== "intro" || segment.UUID !== "getSegmentsByHash-0-1")) { + success = false; + break; + } + } + + if (success) done(); + else done("Received incorrect body: " + JSON.stringify(body)); + } else { + done("Received incorrect body: " + JSON.stringify(body)); + } + } + }) + .catch(err => ("Couldn't call endpoint")); + }); + + it('Should be able to get specific segments with requiredSegments', (done: Done) => { + fetch(getbaseURL() + '/api/skipSegments/d518?requiredSegments=["requiredSegmentVid-2","requiredSegmentVid-3"]') + .then(async res => { + if (res.status !== 200) done("non 200 status code, was " + res.status); + else { + const body = await res.json(); + if (body.length !== 1) done("expected 1 video, got " + body.length); + else if (body[0].segments.length !== 2) done("expected 2 segments for video, got " + body[0].segments.length); + else if (body[0].segments[0].UUID !== 'requiredSegmentVid-2' + || body[0].segments[1].UUID !== 'requiredSegmentVid-3') done("Did not recieve the correct segments\n" + JSON.stringify(body, null, 2)); + else done(); + } + }) + .catch(err => done("Couldn't call endpoint")); + }); + + it('Should be able to get specific segments with repeating requiredSegment', (done: Done) => { + fetch(getbaseURL() + '/api/skipSegments/d518?requiredSegment=requiredSegmentVid-2&requiredSegment=requiredSegmentVid-3') + .then(async res => { + if (res.status !== 200) done("non 200 status code, was " + res.status); + else { + const body = await res.json(); + if (body.length !== 1) done("expected 1 video, got " + body.length); + else if (body[0].segments.length !== 2) done("expected 2 segments for video, got " + body[0].segments.length); + else if (body[0].segments[0].UUID !== 'requiredSegmentVid-2' + || body[0].segments[1].UUID !== 'requiredSegmentVid-3') done("Did not recieve the correct segments\n" + JSON.stringify(body, null, 2)); + else done(); + } + }) + .catch(err => done("Couldn't call endpoint")); + }); }); diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 32d13bc..a79317e 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -23,22 +23,31 @@ describe('postSkipSegments', () => { const warnUser01Hash = getHash("warn-user01"); const warnUser02Hash = getHash("warn-user02"); const warnUser03Hash = getHash("warn-user03"); + const warnUser04Hash = getHash("warn-user04"); + const reason01 = 'Reason01'; + const reason02 = ''; + const reason03 = 'Reason03'; + const reason04 = ''; const MILLISECONDS_IN_HOUR = 3600000; const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; - const insertWarningQuery = 'INSERT INTO warnings ("userID", "issueTime", "issuerUserID", "enabled") VALUES(?, ?, ?, ?)'; - db.prepare("run", insertWarningQuery, [warnUser01Hash, now, warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 1000), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 2000), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 3601000), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 1000)), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 2000)), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser03Hash, now, warnVip01Hash, 0]); - db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 1000), warnVip01Hash, 0]); - db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 2000), warnVip01Hash, 1]); - db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 3601000), warnVip01Hash, 1]); + const insertWarningQuery = 'INSERT INTO warnings ("userID", "issueTime", "issuerUserID", "enabled", "reason") VALUES(?, ?, ?, ?, ?)'; + db.prepare("run", insertWarningQuery, [warnUser01Hash, now, warnVip01Hash, 1, reason01]); + db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 1000), warnVip01Hash, 1, reason01]); + db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 2000), warnVip01Hash, 1, reason01]); + db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 3601000), warnVip01Hash, 1, reason01]); + db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1, reason02]); + db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1, reason02]); + db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 1000)), warnVip01Hash, 1, reason02]); + db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 2000)), warnVip01Hash, 1, reason02]); + db.prepare("run", insertWarningQuery, [warnUser03Hash, now, warnVip01Hash, 0, reason03]); + db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 1000), warnVip01Hash, 0, reason03]); + db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 2000), warnVip01Hash, 1, reason03]); + db.prepare("run", insertWarningQuery, [warnUser03Hash, (now - 3601000), warnVip01Hash, 1, reason03]); + db.prepare("run", insertWarningQuery, [warnUser04Hash, now, warnVip01Hash, 0, reason04]); + db.prepare("run", insertWarningQuery, [warnUser04Hash, (now - 1000), warnVip01Hash, 0, reason04]); + db.prepare("run", insertWarningQuery, [warnUser04Hash, (now - 2000), warnVip01Hash, 1, reason04]); + db.prepare("run", insertWarningQuery, [warnUser04Hash, (now - 3601000), warnVip01Hash, 1, reason04]); const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)'; db.prepare("run", insertVipUserQuery, [getHash("VIPUserSubmission")]); @@ -601,7 +610,7 @@ describe('postSkipSegments', () => { .catch(() => done("Couldn't call endpoint")); }); - it('Should be rejected if user has to many active warnings', (done: Done) => { + it('Should be rejected with custom message if user has to many active warnings', (done: Done) => { fetch(getbaseURL() + "/api/postVideoSponsorTimes", { method: 'POST', @@ -619,7 +628,12 @@ describe('postSkipSegments', () => { }) .then(async res => { if (res.status === 403) { - done(); // success + const errorMessage = await res.text(); + if (errorMessage === 'Reason01') { + done(); // success + } else { + done("Status code was 403 but message was: " + errorMessage); + } } else { done("Status code was " + res.status); } @@ -693,6 +707,37 @@ describe('postSkipSegments', () => { .catch(() => done(true)); }); + it('Should be rejected with default message if user has to many active warnings', (done: Done) => { + fetch(getbaseURL() + + "/api/postVideoSponsorTimes", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userID: "warn-user01", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [0, 10], + category: "sponsor", + }], + }), + }) + .then(async res => { + if (res.status === 403) { + const errorMessage = await res.text(); + if (errorMessage !== '') { + done(); // success + } else { + done("Status code was 403 but message was: " + errorMessage); + } + } else { + done("Status code was " + res.status); + } + }) + .catch(err => done(err)); + }); + it('Should return 400 for missing params (JSON method) 1', (done: Done) => { fetch(getbaseURL() + "/api/postVideoSponsorTimes", {