mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 09:07:47 +01:00
Add duration option when submitting and save duration in DB
This commit is contained in:
parent
29d2c9c25e
commit
5544491728
4 changed files with 271 additions and 170 deletions
29
databases/_upgrade_sponsorTimes_8.sql
Normal file
29
databases/_upgrade_sponsorTimes_8.sql
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
/* Add Service field */
|
||||||
|
CREATE TABLE "sqlb_temp_table_8" (
|
||||||
|
"videoID" TEXT NOT NULL,
|
||||||
|
"startTime" REAL NOT NULL,
|
||||||
|
"endTime" REAL NOT NULL,
|
||||||
|
"votes" INTEGER NOT NULL,
|
||||||
|
"locked" INTEGER NOT NULL default '0',
|
||||||
|
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||||
|
"UUID" TEXT NOT NULL UNIQUE,
|
||||||
|
"userID" TEXT NOT NULL,
|
||||||
|
"timeSubmitted" INTEGER NOT NULL,
|
||||||
|
"views" INTEGER NOT NULL,
|
||||||
|
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||||
|
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||||
|
"videoDuration" INTEGER NOT NULL DEFAULT '0',
|
||||||
|
"shadowHidden" INTEGER NOT NULL,
|
||||||
|
"hashedVideoID" TEXT NOT NULL default ''
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO sqlb_temp_table_8 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category","service",'0', "shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||||
|
|
||||||
|
DROP TABLE "sponsorTimes";
|
||||||
|
ALTER TABLE sqlb_temp_table_8 RENAME TO "sponsorTimes";
|
||||||
|
|
||||||
|
UPDATE "config" SET value = 8 WHERE key = 'version';
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -13,8 +13,12 @@ import {dispatchEvent} from '../utils/webhookUtils';
|
||||||
import {Request, Response} from 'express';
|
import {Request, Response} from 'express';
|
||||||
import { skipSegmentsKey } from '../middleware/redisKeys';
|
import { skipSegmentsKey } from '../middleware/redisKeys';
|
||||||
import redis from '../utils/redis';
|
import redis from '../utils/redis';
|
||||||
import { Service } from '../types/segments.model';
|
import { Category, IncomingSegment, Segment, Service, VideoDuration, VideoID } from '../types/segments.model';
|
||||||
|
|
||||||
|
interface APIVideoInfo {
|
||||||
|
err: string | boolean,
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
|
||||||
async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) {
|
async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) {
|
||||||
const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
|
const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
|
||||||
|
@ -46,62 +50,58 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendWebhooks(userID: string, videoID: string, UUID: string, segmentInfo: any, service: Service) {
|
async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: string, UUID: string, segmentInfo: any, service: Service) {
|
||||||
if (config.youtubeAPIKey !== null && service == Service.YouTube) {
|
if (apiVideoInfo && service == Service.YouTube) {
|
||||||
const userSubmissionCountRow = await db.prepare('get', `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
|
const userSubmissionCountRow = await db.prepare('get', `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]);
|
||||||
|
|
||||||
YouTubeAPI.listVideos(videoID, (err: any, data: any) => {
|
const {data, err} = apiVideoInfo;
|
||||||
if (err || data.items.length === 0) {
|
if (err) return;
|
||||||
err && Logger.error(err);
|
|
||||||
return;
|
const startTime = parseFloat(segmentInfo.segment[0]);
|
||||||
|
const endTime = parseFloat(segmentInfo.segment[1]);
|
||||||
|
sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, {
|
||||||
|
submissionStart: startTime,
|
||||||
|
submissionEnd: endTime,
|
||||||
|
}, segmentInfo);
|
||||||
|
|
||||||
|
// If it is a first time submission
|
||||||
|
// Then send a notification to discord
|
||||||
|
if (config.discordFirstTimeSubmissionsWebhookURL === null || userSubmissionCountRow.submissionCount > 1) return;
|
||||||
|
|
||||||
|
fetch(config.discordFirstTimeSubmissionsWebhookURL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
"embeds": [{
|
||||||
|
"title": data.items[0].snippet.title,
|
||||||
|
"url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseInt(startTime.toFixed(0)) - 2),
|
||||||
|
"description": "Submission ID: " + UUID +
|
||||||
|
"\n\nTimestamp: " +
|
||||||
|
getFormattedTime(startTime) + " to " + getFormattedTime(endTime) +
|
||||||
|
"\n\nCategory: " + segmentInfo.category,
|
||||||
|
"color": 10813440,
|
||||||
|
"author": {
|
||||||
|
"name": userID,
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
})
|
||||||
const startTime = parseFloat(segmentInfo.segment[0]);
|
.then(res => {
|
||||||
const endTime = parseFloat(segmentInfo.segment[1]);
|
if (res.status >= 400) {
|
||||||
sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, {
|
Logger.error("Error sending first time submission Discord hook");
|
||||||
submissionStart: startTime,
|
Logger.error(JSON.stringify(res));
|
||||||
submissionEnd: endTime,
|
|
||||||
}, segmentInfo);
|
|
||||||
|
|
||||||
// If it is a first time submission
|
|
||||||
// Then send a notification to discord
|
|
||||||
if (config.discordFirstTimeSubmissionsWebhookURL === null || userSubmissionCountRow.submissionCount > 1) return;
|
|
||||||
|
|
||||||
fetch(config.discordFirstTimeSubmissionsWebhookURL, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
"embeds": [{
|
|
||||||
"title": data.items[0].snippet.title,
|
|
||||||
"url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseInt(startTime.toFixed(0)) - 2),
|
|
||||||
"description": "Submission ID: " + UUID +
|
|
||||||
"\n\nTimestamp: " +
|
|
||||||
getFormattedTime(startTime) + " to " + getFormattedTime(endTime) +
|
|
||||||
"\n\nCategory: " + segmentInfo.category,
|
|
||||||
"color": 10813440,
|
|
||||||
"author": {
|
|
||||||
"name": userID,
|
|
||||||
},
|
|
||||||
"thumbnail": {
|
|
||||||
"url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.status >= 400) {
|
|
||||||
Logger.error("Error sending first time submission Discord hook");
|
|
||||||
Logger.error(JSON.stringify(res));
|
|
||||||
Logger.error("\n");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
Logger.error("Failed to send first time submission Discord hook.");
|
|
||||||
Logger.error(JSON.stringify(err));
|
|
||||||
Logger.error("\n");
|
Logger.error("\n");
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
Logger.error("Failed to send first time submission Discord hook.");
|
||||||
|
Logger.error(JSON.stringify(err));
|
||||||
|
Logger.error("\n");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,73 +167,98 @@ async function sendWebhooksNB(userID: string, videoID: string, UUID: string, sta
|
||||||
// Looks like this was broken for no defined youtube key - fixed but IMO we shouldn't return
|
// Looks like this was broken for no defined youtube key - fixed but IMO we shouldn't return
|
||||||
// false for a pass - it was confusing and lead to this bug - any use of this function in
|
// false for a pass - it was confusing and lead to this bug - any use of this function in
|
||||||
// the future could have the same problem.
|
// the future could have the same problem.
|
||||||
async function autoModerateSubmission(submission: { videoID: any; userID: any; segments: any }) {
|
async function autoModerateSubmission(apiVideoInfo: APIVideoInfo,
|
||||||
// Get the video information from the youtube API
|
submission: { videoID: any; userID: any; segments: any }) {
|
||||||
if (config.youtubeAPIKey !== null) {
|
if (apiVideoInfo) {
|
||||||
const {err, data} = await new Promise((resolve) => {
|
const {err, data} = apiVideoInfo;
|
||||||
YouTubeAPI.listVideos(submission.videoID, (err: any, data: any) => resolve({err, data}));
|
if (err) return false;
|
||||||
});
|
|
||||||
|
|
||||||
if (err) {
|
// Check to see if video exists
|
||||||
return false;
|
if (data.pageInfo.totalResults === 0) return "No video exists with id " + submission.videoID;
|
||||||
} else {
|
|
||||||
// Check to see if video exists
|
const duration = getYouTubeVideoDuration(apiVideoInfo);
|
||||||
if (data.pageInfo.totalResults === 0) {
|
const segments = submission.segments;
|
||||||
return "No video exists with id " + submission.videoID;
|
let nbString = "";
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
const startTime = parseFloat(segments[i].segment[0]);
|
||||||
|
const endTime = parseFloat(segments[i].segment[1]);
|
||||||
|
|
||||||
|
if (duration == 0) {
|
||||||
|
// Allow submission if the duration is 0 (bug in youtube api)
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const segments = submission.segments;
|
if (segments[i].category === "sponsor") {
|
||||||
let nbString = "";
|
//Prepare timestamps to send to NB all at once
|
||||||
for (let i = 0; i < segments.length; i++) {
|
nbString = nbString + segments[i].segment[0] + "," + segments[i].segment[1] + ";";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all submissions for this user
|
||||||
|
const allSubmittedByUser = await db.prepare('all', `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ? and "votes" > -1`, [submission.userID, submission.videoID]);
|
||||||
|
const allSegmentTimes = [];
|
||||||
|
if (allSubmittedByUser !== undefined) {
|
||||||
|
//add segments the user has previously submitted
|
||||||
|
for (const segmentInfo of allSubmittedByUser) {
|
||||||
|
allSegmentTimes.push([parseFloat(segmentInfo.startTime), parseFloat(segmentInfo.endTime)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add segments they are trying to add in this submission
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
let startTime = parseFloat(segments[i].segment[0]);
|
||||||
|
let endTime = parseFloat(segments[i].segment[1]);
|
||||||
|
allSegmentTimes.push([startTime, endTime]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//merge all the times into non-overlapping arrays
|
||||||
|
const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort(function (a, b) {
|
||||||
|
return a[0] - b[0] || a[1] - b[1];
|
||||||
|
}));
|
||||||
|
|
||||||
|
let videoDuration = data.items[0].contentDetails.duration;
|
||||||
|
videoDuration = isoDurations.toSeconds(isoDurations.parse(videoDuration));
|
||||||
|
if (videoDuration != 0) {
|
||||||
|
let allSegmentDuration = 0;
|
||||||
|
//sum all segment times together
|
||||||
|
allSegmentsSorted.forEach(segmentInfo => allSegmentDuration += segmentInfo[1] - segmentInfo[0]);
|
||||||
|
if (allSegmentDuration > (videoDuration / 100) * 80) {
|
||||||
|
// Reject submission if all segments combine are over 80% of the video
|
||||||
|
return "Total length of your submitted segments are over 80% of the video.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check NeuralBlock
|
||||||
|
const neuralBlockURL = config.neuralBlockURL;
|
||||||
|
if (!neuralBlockURL) return false;
|
||||||
|
const response = await fetch(neuralBlockURL + "/api/checkSponsorSegments?vid=" + submission.videoID +
|
||||||
|
"&segments=" + nbString.substring(0, nbString.length - 1));
|
||||||
|
if (!response.ok) return false;
|
||||||
|
|
||||||
|
const nbPredictions = await response.json();
|
||||||
|
let nbDecision = false;
|
||||||
|
let predictionIdx = 0; //Keep track because only sponsor categories were submitted
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
if (segments[i].category === "sponsor") {
|
||||||
|
if (nbPredictions.probabilities[predictionIdx] < 0.70) {
|
||||||
|
nbDecision = true; // At least one bad entry
|
||||||
const startTime = parseFloat(segments[i].segment[0]);
|
const startTime = parseFloat(segments[i].segment[0]);
|
||||||
const endTime = parseFloat(segments[i].segment[1]);
|
const endTime = parseFloat(segments[i].segment[1]);
|
||||||
|
|
||||||
let duration = data.items[0].contentDetails.duration;
|
const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime);
|
||||||
duration = isoDurations.toSeconds(isoDurations.parse(duration));
|
// Send to Discord
|
||||||
if (duration == 0) {
|
// Note, if this is too spammy. Consider sending all the segments as one Webhook
|
||||||
// Allow submission if the duration is 0 (bug in youtube api)
|
sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data);
|
||||||
return false;
|
|
||||||
} else if ((endTime - startTime) > (duration / 100) * 80) {
|
|
||||||
// Reject submission if over 80% of the video
|
|
||||||
return "One of your submitted segments is over 80% of the video.";
|
|
||||||
} else {
|
|
||||||
if (segments[i].category === "sponsor") {
|
|
||||||
//Prepare timestamps to send to NB all at once
|
|
||||||
nbString = nbString + segments[i].segment[0] + "," + segments[i].segment[1] + ";";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check NeuralBlock
|
|
||||||
const neuralBlockURL = config.neuralBlockURL;
|
|
||||||
if (!neuralBlockURL) return false;
|
|
||||||
const response = await fetch(neuralBlockURL + "/api/checkSponsorSegments?vid=" + submission.videoID +
|
|
||||||
"&segments=" + nbString.substring(0, nbString.length - 1));
|
|
||||||
if (!response.ok) return false;
|
|
||||||
|
|
||||||
const nbPredictions = await response.json();
|
|
||||||
let nbDecision = false;
|
|
||||||
let predictionIdx = 0; //Keep track because only sponsor categories were submitted
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
if (segments[i].category === "sponsor") {
|
|
||||||
if (nbPredictions.probabilities[predictionIdx] < 0.70) {
|
|
||||||
nbDecision = true; // At least one bad entry
|
|
||||||
const startTime = parseFloat(segments[i].segment[0]);
|
|
||||||
const endTime = parseFloat(segments[i].segment[1]);
|
|
||||||
|
|
||||||
const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime);
|
|
||||||
// Send to Discord
|
|
||||||
// Note, if this is too spammy. Consider sending all the segments as one Webhook
|
|
||||||
sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data);
|
|
||||||
}
|
|
||||||
predictionIdx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (nbDecision) {
|
|
||||||
return "Rejected based on NeuralBlock predictions.";
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
predictionIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbDecision) {
|
||||||
|
return "Rejected based on NeuralBlock predictions.";
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Skipped YouTube API");
|
Logger.debug("Skipped YouTube API");
|
||||||
|
@ -244,6 +269,21 @@ async function autoModerateSubmission(submission: { videoID: any; userID: any; s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getYouTubeVideoDuration(apiVideoInfo: APIVideoInfo): VideoDuration {
|
||||||
|
const duration = apiVideoInfo?.data?.items[0]?.contentDetails?.duration;
|
||||||
|
return duration ? isoDurations.toSeconds(isoDurations.parse(duration)) as VideoDuration : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getYouTubeVideoInfo(videoID: VideoID): Promise<APIVideoInfo> {
|
||||||
|
if (config.youtubeAPIKey !== null) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
YouTubeAPI.listVideos(videoID, (err: any, data: any) => resolve({err, data}));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function proxySubmission(req: Request) {
|
function proxySubmission(req: Request) {
|
||||||
fetch(config.proxySubmission + '/api/skipSegments?userID=' + req.query.userID + '&videoID=' + req.query.videoID, {
|
fetch(config.proxySubmission + '/api/skipSegments?userID=' + req.query.userID + '&videoID=' + req.query.videoID, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -272,13 +312,14 @@ export async function postSkipSegments(req: Request, res: Response) {
|
||||||
if (!Object.values(Service).some((val) => val == service)) {
|
if (!Object.values(Service).some((val) => val == service)) {
|
||||||
service = Service.YouTube;
|
service = Service.YouTube;
|
||||||
}
|
}
|
||||||
|
let videoDuration: VideoDuration = (parseFloat(req.query.videoDuration || req.body.videoDuration) || 0) as VideoDuration;
|
||||||
|
|
||||||
let segments = req.body.segments;
|
let segments = req.body.segments as IncomingSegment[];
|
||||||
if (segments === undefined) {
|
if (segments === undefined) {
|
||||||
// Use query instead
|
// Use query instead
|
||||||
segments = [{
|
segments = [{
|
||||||
segment: [req.query.startTime, req.query.endTime],
|
segment: [req.query.startTime as string, req.query.endTime as string],
|
||||||
category: req.query.category,
|
category: req.query.category as Category
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +419,15 @@ export async function postSkipSegments(req: Request, res: Response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let apiVideoInfo: APIVideoInfo = null;
|
||||||
|
if (service == Service.YouTube) {
|
||||||
|
apiVideoInfo = await getYouTubeVideoInfo(videoID);
|
||||||
|
}
|
||||||
|
videoDuration = getYouTubeVideoDuration(apiVideoInfo) || videoDuration;
|
||||||
|
|
||||||
// Auto moderator check
|
// Auto moderator check
|
||||||
if (!isVIP && service == Service.YouTube) {
|
if (!isVIP && service == Service.YouTube) {
|
||||||
const autoModerateResult = await autoModerateSubmission({userID, videoID, segments});//startTime, endTime, category: segments[i].category});
|
const autoModerateResult = await autoModerateSubmission(apiVideoInfo, {userID, videoID, segments});//startTime, endTime, category: segments[i].category});
|
||||||
if (autoModerateResult == "Rejected based on NeuralBlock predictions.") {
|
if (autoModerateResult == "Rejected based on NeuralBlock predictions.") {
|
||||||
// If NB automod rejects, the submission will start with -2 votes.
|
// If NB automod rejects, the submission will start with -2 votes.
|
||||||
// Note, if one submission is bad all submissions will be affected.
|
// Note, if one submission is bad all submissions will be affected.
|
||||||
|
@ -441,63 +488,18 @@ export async function postSkipSegments(req: Request, res: Response) {
|
||||||
|
|
||||||
let startingVotes = 0 + decreaseVotes;
|
let startingVotes = 0 + decreaseVotes;
|
||||||
|
|
||||||
if (config.youtubeAPIKey !== null) {
|
|
||||||
let {err, data} = await new Promise((resolve) => {
|
|
||||||
YouTubeAPI.listVideos(videoID, (err: any, data: any) => resolve({err, data}));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
Logger.error("Error while submitting when connecting to YouTube API: " + err);
|
|
||||||
} else {
|
|
||||||
//get all segments for this video and user
|
|
||||||
const allSubmittedByUser = await db.prepare('all', `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? and "videoID" = ? and "votes" > -1`, [userID, videoID]);
|
|
||||||
const allSegmentTimes = [];
|
|
||||||
if (allSubmittedByUser !== undefined) {
|
|
||||||
//add segments the user has previously submitted
|
|
||||||
for (const segmentInfo of allSubmittedByUser) {
|
|
||||||
allSegmentTimes.push([parseFloat(segmentInfo.startTime), parseFloat(segmentInfo.endTime)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//add segments they are trying to add in this submission
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
let startTime = parseFloat(segments[i].segment[0]);
|
|
||||||
let endTime = parseFloat(segments[i].segment[1]);
|
|
||||||
allSegmentTimes.push([startTime, endTime]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//merge all the times into non-overlapping arrays
|
|
||||||
const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort(function (a, b) {
|
|
||||||
return a[0] - b[0] || a[1] - b[1];
|
|
||||||
}));
|
|
||||||
|
|
||||||
let videoDuration = data.items[0].contentDetails.duration;
|
|
||||||
videoDuration = isoDurations.toSeconds(isoDurations.parse(videoDuration));
|
|
||||||
if (videoDuration != 0) {
|
|
||||||
let allSegmentDuration = 0;
|
|
||||||
//sum all segment times together
|
|
||||||
allSegmentsSorted.forEach(segmentInfo => allSegmentDuration += segmentInfo[1] - segmentInfo[0]);
|
|
||||||
if (allSegmentDuration > (videoDuration / 100) * 80) {
|
|
||||||
// Reject submission if all segments combine are over 80% of the video
|
|
||||||
res.status(400).send("Total length of your submitted segments are over 80% of the video.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const segmentInfo of segments) {
|
for (const segmentInfo of segments) {
|
||||||
//this can just be a hash of the data
|
//this can just be a hash of the data
|
||||||
//it's better than generating an actual UUID like what was used before
|
//it's better than generating an actual UUID like what was used before
|
||||||
//also better for duplication checking
|
//also better for duplication checking
|
||||||
const UUID = getSubmissionUUID(videoID, segmentInfo.category, userID, segmentInfo.segment[0], segmentInfo.segment[1]);
|
const UUID = getSubmissionUUID(videoID, segmentInfo.category, userID, parseFloat(segmentInfo.segment[0]), parseFloat(segmentInfo.segment[1]));
|
||||||
|
|
||||||
const startingLocked = isVIP ? 1 : 0;
|
const startingLocked = isVIP ? 1 : 0;
|
||||||
try {
|
try {
|
||||||
await db.prepare('run', `INSERT INTO "sponsorTimes"
|
await db.prepare('run', `INSERT INTO "sponsorTimes"
|
||||||
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "service", "shadowHidden", "hashedVideoID")
|
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "service", "videoDuration", "shadowHidden", "hashedVideoID")
|
||||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
||||||
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, service, shadowBanned, getHash(videoID, 1),
|
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, service, videoDuration, shadowBanned, getHash(videoID, 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -508,7 +510,7 @@ export async function postSkipSegments(req: Request, res: Response) {
|
||||||
redis.delAsync(skipSegmentsKey(videoID));
|
redis.delAsync(skipSegmentsKey(videoID));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
//a DB change probably occurred
|
//a DB change probably occurred
|
||||||
res.sendStatus(502);
|
res.sendStatus(500);
|
||||||
Logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
|
Logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " +
|
||||||
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
|
segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err);
|
||||||
|
|
||||||
|
@ -533,7 +535,7 @@ export async function postSkipSegments(req: Request, res: Response) {
|
||||||
res.json(newSegments);
|
res.json(newSegments);
|
||||||
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
sendWebhooks(userID, videoID, UUIDs[i], segments[i], service);
|
sendWebhooks(apiVideoInfo, userID, videoID, UUIDs[i], segments[i], service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { SBRecord } from "./lib.model";
|
||||||
|
|
||||||
export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
|
export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
|
||||||
export type VideoID = string & { __videoIDBrand: unknown };
|
export type VideoID = string & { __videoIDBrand: unknown };
|
||||||
|
export type VideoDuration = number & { __videoDurationBrand: unknown };
|
||||||
export type Category = string & { __categoryBrand: unknown };
|
export type Category = string & { __categoryBrand: unknown };
|
||||||
export type VideoIDHash = VideoID & HashedValue;
|
export type VideoIDHash = VideoID & HashedValue;
|
||||||
export type IPAddress = string & { __ipAddressBrand: unknown };
|
export type IPAddress = string & { __ipAddressBrand: unknown };
|
||||||
|
@ -18,6 +19,11 @@ export enum Service {
|
||||||
// Lbry = 'Lbry'
|
// Lbry = 'Lbry'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IncomingSegment {
|
||||||
|
category: Category;
|
||||||
|
segment: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Segment {
|
export interface Segment {
|
||||||
category: Category;
|
category: Category;
|
||||||
segment: number[];
|
segment: number[];
|
||||||
|
|
|
@ -97,6 +97,70 @@ describe('postSkipSegments', () => {
|
||||||
.catch(err => done(err));
|
.catch(err => done(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should be able to submit a single time with a duration (JSON method)', (done: Done) => {
|
||||||
|
fetch(getbaseURL()
|
||||||
|
+ "/api/postVideoSponsorTimes", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
userID: "test",
|
||||||
|
videoID: "dQw4w9WgXZX",
|
||||||
|
videoDuration: 100,
|
||||||
|
segments: [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "sponsor",
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
const row = await db.prepare('get', `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, ["dQw4w9WgXZX"]);
|
||||||
|
if (row.startTime === 0 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 5010) {
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
done("Status code was " + res.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to submit a single time with a duration from the API (JSON method)', (done: Done) => {
|
||||||
|
fetch(getbaseURL()
|
||||||
|
+ "/api/postVideoSponsorTimes", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
userID: "test",
|
||||||
|
videoID: "noDuration",
|
||||||
|
videoDuration: 100,
|
||||||
|
segments: [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "sponsor",
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
const row = await db.prepare('get', `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, ["noDuration"]);
|
||||||
|
if (row.startTime === 0 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 100) {
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
done("Submitted times were not saved. Actual submission: " + JSON.stringify(row));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
done("Status code was " + res.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
it('Should be able to submit a single time under a different service (JSON method)', (done: Done) => {
|
it('Should be able to submit a single time under a different service (JSON method)', (done: Done) => {
|
||||||
fetch(getbaseURL()
|
fetch(getbaseURL()
|
||||||
+ "/api/postVideoSponsorTimes", {
|
+ "/api/postVideoSponsorTimes", {
|
||||||
|
@ -276,7 +340,7 @@ describe('postSkipSegments', () => {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
if (res.status === 400) {
|
if (res.status === 403) {
|
||||||
const rows = await db.prepare('all', `SELECT "startTime", "endTime", "category" FROM "sponsorTimes" WHERE "videoID" = ? and "votes" > -1`, ["n9rIGdXnSJc"]);
|
const rows = await db.prepare('all', `SELECT "startTime", "endTime", "category" FROM "sponsorTimes" WHERE "videoID" = ? and "votes" > -1`, ["n9rIGdXnSJc"]);
|
||||||
let success = true;
|
let success = true;
|
||||||
if (rows.length === 4) {
|
if (rows.length === 4) {
|
||||||
|
@ -324,7 +388,7 @@ describe('postSkipSegments', () => {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
if (res.status === 400) {
|
if (res.status === 403) {
|
||||||
const rows = await db.prepare('all', `SELECT "startTime", "endTime", "category" FROM "sponsorTimes" WHERE "videoID" = ? and "votes" > -1`, ["80percent_video"]);
|
const rows = await db.prepare('all', `SELECT "startTime", "endTime", "category" FROM "sponsorTimes" WHERE "videoID" = ? and "votes" > -1`, ["80percent_video"]);
|
||||||
let success = rows.length == 2;
|
let success = rows.length == 2;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
|
|
Loading…
Reference in a new issue