Only return one segment for highlight category

This commit is contained in:
Ajay Ramachandran 2021-04-05 23:48:51 -04:00
parent 7bf09906d3
commit 8088f37632
7 changed files with 48 additions and 11 deletions

View file

@ -4,7 +4,8 @@ import { config } from '../config';
import { db, privateDB } from '../databases/databases'; import { db, privateDB } from '../databases/databases';
import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys'; import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys';
import { SBRecord } from '../types/lib.model'; import { SBRecord } from '../types/lib.model';
import { Category, 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, Service, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model";
import { getCategoryActionType } from '../utils/categoryInfo';
import { getHash } from '../utils/getHash'; import { getHash } from '../utils/getHash';
import { getIP } from '../utils/getIP'; import { getIP } from '../utils/getIP';
import { Logger } from '../utils/logger'; import { Logger } from '../utils/logger';
@ -40,7 +41,8 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category:
const filteredSegments = segments.filter((_, index) => shouldFilter[index]); const filteredSegments = segments.filter((_, index) => shouldFilter[index]);
return chooseSegments(filteredSegments).map((chosenSegment) => ({ const maxSegments = getCategoryActionType(category) === CategoryActionType.Skippable ? 32 : 1
return chooseSegments(filteredSegments, maxSegments).map((chosenSegment) => ({
category, category,
segment: [chosenSegment.startTime, chosenSegment.endTime], segment: [chosenSegment.startTime, chosenSegment.endTime],
UUID: chosenSegment.UUID, UUID: chosenSegment.UUID,
@ -206,7 +208,7 @@ function getWeightedRandomChoice<T extends VotableObject>(choices: T[], amountOf
//Only one similar time will be returned, randomly generated based on the sqrt of votes. //Only one similar time will be returned, randomly generated based on the sqrt of votes.
//This allows new less voted items to still sometimes appear to give them a chance at getting votes. //This allows new less voted items to still sometimes appear to give them a chance at getting votes.
//Segments with less than -1 votes are already ignored before this function is called //Segments with less than -1 votes are already ignored before this function is called
function chooseSegments(segments: DBSegment[]): DBSegment[] { function chooseSegments(segments: DBSegment[], max: number): DBSegment[] {
//Create groups of segments that are similar to eachother //Create groups of segments that are similar to eachother
//Segments must be sorted by their startTime so that we can build groups chronologically: //Segments must be sorted by their startTime so that we can build groups chronologically:
//1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group //1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group
@ -240,8 +242,8 @@ function chooseSegments(segments: DBSegment[]): DBSegment[] {
} }
}); });
//if there are too many groups, find the best 8 //if there are too many groups, find the best ones
return getWeightedRandomChoice(overlappingSegmentsGroups, 32).map( return getWeightedRandomChoice(overlappingSegmentsGroups, max).map(
//randomly choose 1 good segment per group and return them //randomly choose 1 good segment per group and return them
group => getWeightedRandomChoice(group.segments, 1)[0], group => getWeightedRandomChoice(group.segments, 1)[0],
); );

View file

@ -13,8 +13,9 @@ import {dispatchEvent} from '../utils/webhookUtils';
import {Request, Response} from 'express'; import {Request, Response} from 'express';
import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys'; import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys';
import redis from '../utils/redis'; import redis from '../utils/redis';
import { Category, IncomingSegment, Segment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model'; import { Category, CategoryActionType, IncomingSegment, Segment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model';
import { deleteNoSegments } from './deleteNoSegments'; import { deleteNoSegments } from './deleteNoSegments';
import { getCategoryActionType } from '../utils/categoryInfo';
interface APIVideoInfo { interface APIVideoInfo {
err: string | boolean, err: string | boolean,
@ -426,7 +427,8 @@ export async function postSkipSegments(req: Request, res: Response) {
if (isNaN(startTime) || isNaN(endTime) if (isNaN(startTime) || isNaN(endTime)
|| startTime === Infinity || endTime === Infinity || startTime < 0 || startTime > endTime || startTime === Infinity || endTime === Infinity || startTime < 0 || startTime > endTime
|| (segments[i].category !== "highlight" && startTime === endTime) || (segments[i].category === "highlight" && startTime !== endTime)) { || (getCategoryActionType(segments[i].category) === CategoryActionType.Skippable && startTime === endTime)
|| (getCategoryActionType(segments[i].category) === CategoryActionType.POI && startTime !== endTime)) {
//invalid request //invalid request
res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)"); res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)");
return; return;

View file

@ -13,7 +13,8 @@ import {config} from '../config';
import { UserID } from '../types/user.model'; import { UserID } from '../types/user.model';
import redis from '../utils/redis'; import redis from '../utils/redis';
import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys'; import { skipSegmentsHashKey, skipSegmentsKey } from '../middleware/redisKeys';
import { Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash } from '../types/segments.model'; import { Category, CategoryActionType, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash } from '../types/segments.model';
import { getCategoryActionType } from '../utils/categoryInfo';
const voteTypes = { const voteTypes = {
normal: 0, normal: 0,
@ -170,7 +171,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i
res.status(400).send("Category doesn't exist."); res.status(400).send("Category doesn't exist.");
return; return;
} }
if (category === "highlight") { if (getCategoryActionType(category) !== CategoryActionType.Skippable) {
res.status(400).send("Cannot vote for this category"); res.status(400).send("Cannot vote for this category");
return; return;
} }

View file

@ -73,3 +73,8 @@ export interface SegmentCache {
shadowHiddenSegmentIPs: SBRecord<VideoID, {hashedIP: HashedIP}[]>, shadowHiddenSegmentIPs: SBRecord<VideoID, {hashedIP: HashedIP}[]>,
userHashedIP?: HashedIP userHashedIP?: HashedIP
} }
export enum CategoryActionType {
Skippable,
POI
}

10
src/utils/categoryInfo.ts Normal file
View file

@ -0,0 +1,10 @@
import { Category, CategoryActionType } from "../types/segments.model";
export function getCategoryActionType(category: Category): CategoryActionType {
switch (category) {
case "highlight":
return CategoryActionType.POI;
default:
return CategoryActionType.Skippable;
}
}

View file

@ -19,6 +19,9 @@ describe('getSegmentsByHash', () => {
await db.prepare("run", startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 await db.prepare("run", startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910
await db.prepare("run", startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b await db.prepare("run", startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 'YouTube', 0, 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b
await db.prepare("run", startOfQuery + "('onlyHidden', 60, 70, 2, 'onlyHidden', 'testman', 0, 50, 'sponsor', 'YouTube', 1, 0, '" + getHash('onlyHidden', 1) + "')"); // hash = f3a199e1af001d716cdc6599360e2b062c2d2b3fa2885f6d9d2fd741166cbbd3 await db.prepare("run", startOfQuery + "('onlyHidden', 60, 70, 2, 'onlyHidden', 'testman', 0, 50, 'sponsor', 'YouTube', 1, 0, '" + getHash('onlyHidden', 1) + "')"); // hash = f3a199e1af001d716cdc6599360e2b062c2d2b3fa2885f6d9d2fd741166cbbd3
await db.prepare("run", startOfQuery + "('highlightVid', 60, 60, 2, 'highlightVid-1', 'testman', 0, 50, 'highlight', 'YouTube', 0, 0, '" + getHash('highlightVid', 1) + "')"); // hash = c962d387a9e50170c9118405d20b1081cee8659cd600b856b511f695b91455cb
await db.prepare("run", startOfQuery + "('highlightVid', 70, 70, 2, 'highlightVid-2', 'testman', 0, 50, 'highlight', 'YouTube', 0, 0, '" + getHash('highlightVid', 1) + "')"); // hash = c962d387a9e50170c9118405d20b1081cee8659cd600b856b511f695b91455cb
}); });
it('Should be able to get a 200', (done: Done) => { it('Should be able to get a 200', (done: Done) => {
@ -158,7 +161,7 @@ describe('getSegmentsByHash', () => {
if (res.status !== 200) done("non 200 status code, was " + res.status); if (res.status !== 200) done("non 200 status code, was " + res.status);
else { else {
const body = await res.json(); const body = await res.json();
if (body.length !== 1) done("expected 2 videos, got " + body.length); if (body.length !== 1) done("expected 1 video, got " + body.length);
else if (body[0].segments.length !== 1) done("expected 1 segments for first video, got " + body[0].segments.length); else if (body[0].segments.length !== 1) done("expected 1 segments for first video, got " + body[0].segments.length);
else if (body[0].segments[0].UUID !== 'getSegmentsByHash-0-0-1') done("both segments are not sponsor"); else if (body[0].segments[0].UUID !== 'getSegmentsByHash-0-0-1') done("both segments are not sponsor");
else done(); else done();
@ -167,6 +170,20 @@ describe('getSegmentsByHash', () => {
.catch(err => done("Couldn't call endpoint")); .catch(err => done("Couldn't call endpoint"));
}); });
it('Should only return one segment when fetching highlight segments', (done: Done) => {
fetch(getbaseURL() + '/api/skipSegments/c962?category=highlight')
.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 !== 1) done("expected 1 segment, got " + body[0].segments.length);
else done();
}
})
.catch(err => done("Couldn't call endpoint"));
});
it('Should be able to post a segment and get it using endpoint', (done: Done) => { it('Should be able to post a segment and get it using endpoint', (done: Done) => {
let testID = 'abc123goodVideo'; let testID = 'abc123goodVideo';
fetch(getbaseURL() + "/api/postVideoSponsorTimes", { fetch(getbaseURL() + "/api/postVideoSponsorTimes", {