mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-12 14:37:17 +03:00
Merge pull request #464 from mchangrh/postSkipSegment
update postSkipSegments
This commit is contained in:
@@ -16,6 +16,7 @@ import { getReputation } from "../utils/reputation";
|
|||||||
import { APIVideoData, APIVideoInfo } from "../types/youtubeApi.model";
|
import { APIVideoData, APIVideoInfo } from "../types/youtubeApi.model";
|
||||||
import { HashedUserID, UserID } from "../types/user.model";
|
import { HashedUserID, UserID } from "../types/user.model";
|
||||||
import { isUserVIP } from "../utils/isUserVIP";
|
import { isUserVIP } from "../utils/isUserVIP";
|
||||||
|
import { isUserTempVIP } from "../utils/isUserTempVIP";
|
||||||
import { parseUserAgent } from "../utils/userAgent";
|
import { parseUserAgent } from "../utils/userAgent";
|
||||||
import { getService } from "../utils/getService";
|
import { getService } from "../utils/getService";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -81,19 +82,19 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID:
|
|||||||
if (config.discordFirstTimeSubmissionsWebhookURL === null || userSubmissionCountRow.submissionCount > 1) return;
|
if (config.discordFirstTimeSubmissionsWebhookURL === null || userSubmissionCountRow.submissionCount > 1) return;
|
||||||
|
|
||||||
axios.post(config.discordFirstTimeSubmissionsWebhookURL, {
|
axios.post(config.discordFirstTimeSubmissionsWebhookURL, {
|
||||||
"embeds": [{
|
embeds: [{
|
||||||
"title": data?.title,
|
title: data?.title,
|
||||||
"url": `https://www.youtube.com/watch?v=${videoID}&t=${(parseInt(startTime.toFixed(0)) - 2)}s#requiredSegment=${UUID}`,
|
url: `https://www.youtube.com/watch?v=${videoID}&t=${(parseInt(startTime.toFixed(0)) - 2)}s#requiredSegment=${UUID}`,
|
||||||
"description": `Submission ID: ${UUID}\
|
description: `Submission ID: ${UUID}\
|
||||||
\n\nTimestamp: \
|
\n\nTimestamp: \
|
||||||
${getFormattedTime(startTime)} to ${getFormattedTime(endTime)}\
|
${getFormattedTime(startTime)} to ${getFormattedTime(endTime)}\
|
||||||
\n\nCategory: ${segmentInfo.category}`,
|
\n\nCategory: ${segmentInfo.category}`,
|
||||||
"color": 10813440,
|
color: 10813440,
|
||||||
"author": {
|
author: {
|
||||||
"name": userID,
|
name: userID,
|
||||||
},
|
},
|
||||||
"thumbnail": {
|
thumbnail: {
|
||||||
"url": getMaxResThumbnail(data) || "",
|
url: getMaxResThumbnail(data) || "",
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
@@ -112,55 +113,6 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendWebhooksNB(userID: string, videoID: string, UUID: string, startTime: number, endTime: number, category: string, probability: number, ytData: any) {
|
|
||||||
const submissionInfoRow = await db.prepare("get", `SELECT
|
|
||||||
(select count(1) from "sponsorTimes" where "userID" = ?) count,
|
|
||||||
(select count(1) from "sponsorTimes" where "userID" = ? and "votes" <= -2) disregarded,
|
|
||||||
coalesce((select "userName" FROM "userNames" WHERE "userID" = ?), ?) "userName"`,
|
|
||||||
[userID, userID, userID, userID]);
|
|
||||||
|
|
||||||
let submittedBy: string;
|
|
||||||
// If a userName was created then show both
|
|
||||||
if (submissionInfoRow.userName !== userID) {
|
|
||||||
submittedBy = `${submissionInfoRow.userName}\n${userID}`;
|
|
||||||
} else {
|
|
||||||
submittedBy = userID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send discord message
|
|
||||||
if (config.discordNeuralBlockRejectWebhookURL === null) return;
|
|
||||||
|
|
||||||
axios.post(config.discordNeuralBlockRejectWebhookURL, {
|
|
||||||
"embeds": [{
|
|
||||||
"title": ytData.items[0].snippet.title,
|
|
||||||
"url": `https://www.youtube.com/watch?v=${videoID}&t=${(parseFloat(startTime.toFixed(0)) - 2)}`,
|
|
||||||
"description": `**Submission ID:** ${UUID}\
|
|
||||||
\n**Timestamp:** ${getFormattedTime(startTime)} to ${getFormattedTime(endTime)}\
|
|
||||||
\n**Predicted Probability:** ${probability}\
|
|
||||||
\n**Category:** ${category}\
|
|
||||||
\n**Submitted by:** ${submittedBy}\
|
|
||||||
\n**Total User Submissions:** ${submissionInfoRow.count}\
|
|
||||||
\n**Ignored User Submissions:** ${submissionInfoRow.disregarded}`,
|
|
||||||
"color": 10813440,
|
|
||||||
"thumbnail": {
|
|
||||||
"url": ytData.items[0].snippet.thumbnails.maxres ? ytData.items[0].snippet.thumbnails.maxres.url : "",
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.status >= 400) {
|
|
||||||
Logger.error("Error sending NeuralBlock Discord hook");
|
|
||||||
Logger.error(JSON.stringify(res));
|
|
||||||
Logger.error("\n");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
Logger.error("Failed to send NeuralBlock Discord hook.");
|
|
||||||
Logger.error(JSON.stringify(err));
|
|
||||||
Logger.error("\n");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback: function(reject: "String containing reason the submission was rejected")
|
// callback: function(reject: "String containing reason the submission was rejected")
|
||||||
// returns: string when an error, false otherwise
|
// returns: string when an error, false otherwise
|
||||||
|
|
||||||
@@ -168,99 +120,47 @@ async function sendWebhooksNB(userID: string, videoID: string, UUID: string, sta
|
|||||||
// 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(apiVideoInfo: APIVideoInfo,
|
async function autoModerateSubmission(apiVideoInfo: APIVideoInfo,
|
||||||
submission: { videoID: VideoID; userID: UserID; segments: IncomingSegment[], service: Service }) {
|
submission: { videoID: VideoID; userID: UserID; segments: IncomingSegment[], service: Service, videoDuration: number }) {
|
||||||
if (apiVideoInfo) {
|
|
||||||
|
const apiVideoDuration = (apiVideoInfo: APIVideoInfo) => {
|
||||||
|
if (!apiVideoInfo) return undefined;
|
||||||
const { err, data } = apiVideoInfo;
|
const { err, data } = apiVideoInfo;
|
||||||
if (err) return false;
|
// return undefined if API error
|
||||||
|
if (err) return undefined;
|
||||||
|
return data?.lengthSeconds;
|
||||||
|
};
|
||||||
|
// get duration from API
|
||||||
|
const apiDuration = apiVideoDuration(apiVideoInfo);
|
||||||
|
// if API fail or returns 0, get duration from client
|
||||||
|
const duration = apiDuration || submission.videoDuration;
|
||||||
|
// return false on undefined or 0
|
||||||
|
if (!duration) return false;
|
||||||
|
|
||||||
const duration = apiVideoInfo?.data?.lengthSeconds;
|
|
||||||
const segments = submission.segments;
|
const segments = submission.segments;
|
||||||
let nbString = "";
|
// map all times to float array
|
||||||
for (let i = 0; i < segments.length; i++) {
|
const allSegmentTimes = segments.map(segment => [parseFloat(segment.segment[0]), parseFloat(segment.segment[1])]);
|
||||||
if (duration == 0) {
|
|
||||||
// Allow submission if the duration is 0 (bug in youtube api)
|
|
||||||
return false;
|
|
||||||
} 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]};`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all submissions for this user
|
// add previous submissions by 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 allSubmittedByUser = await db.prepare("all", `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? AND "videoID" = ? AND "votes" > -1 AND "hidden" = 0`, [submission.userID, submission.videoID]);
|
||||||
const allSegmentTimes = [];
|
|
||||||
if (allSubmittedByUser !== undefined) {
|
if (allSubmittedByUser) {
|
||||||
//add segments the user has previously submitted
|
//add segments the user has previously submitted
|
||||||
for (const segmentInfo of allSubmittedByUser) {
|
const allSubmittedTimes = allSubmittedByUser.map((segment: { startTime: string, endTime: string }) => [parseFloat(segment.startTime), parseFloat(segment.endTime)]);
|
||||||
allSegmentTimes.push([parseFloat(segmentInfo.startTime), parseFloat(segmentInfo.endTime)]);
|
allSegmentTimes.push(...allSubmittedTimes);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//add segments they are trying to add in this submission
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
const startTime = parseFloat(segments[i].segment[0]);
|
|
||||||
const endTime = parseFloat(segments[i].segment[1]);
|
|
||||||
allSegmentTimes.push([startTime, endTime]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//merge all the times into non-overlapping arrays
|
//merge all the times into non-overlapping arrays
|
||||||
const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort(function (a, b) {
|
const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort((a, b) => a[0] - b[0] || a[1] - b[1]));
|
||||||
return a[0] - b[0] || a[1] - b[1];
|
|
||||||
}));
|
|
||||||
|
|
||||||
const videoDuration = data?.lengthSeconds;
|
|
||||||
if (videoDuration != 0) {
|
|
||||||
let allSegmentDuration = 0;
|
|
||||||
//sum all segment times together
|
//sum all segment times together
|
||||||
allSegmentsSorted.forEach(segmentInfo => allSegmentDuration += segmentInfo[1] - segmentInfo[0]);
|
const allSegmentDuration = allSegmentsSorted.reduce((acc, curr) => acc + (curr[1] - curr[0]), 0);
|
||||||
if (allSegmentDuration > (videoDuration / 100) * 80) {
|
|
||||||
|
if (allSegmentDuration > (duration / 100) * 80) {
|
||||||
// Reject submission if all segments combine are over 80% of the video
|
// 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.";
|
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 axios.get(`${neuralBlockURL}/api/checkSponsorSegments?vid=${submission.videoID}
|
|
||||||
&segments=${nbString.substring(0, nbString.length - 1)}`, { validateStatus: () => true });
|
|
||||||
if (response.status !== 200) return false;
|
|
||||||
|
|
||||||
const nbPredictions = response.data;
|
|
||||||
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, segments[i].actionType, submission.userID, startTime, endTime, submission.service);
|
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Logger.debug("Skipped YouTube API");
|
|
||||||
|
|
||||||
// Can't moderate the submission without calling the youtube API
|
|
||||||
// so allow by default.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
|
function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
|
||||||
if (config.newLeafURLs !== null) {
|
if (config.newLeafURLs !== null) {
|
||||||
@@ -310,7 +210,7 @@ function checkInvalidFields(videoID: VideoID, userID: UserID, segments: Incoming
|
|||||||
invalidFields.push("userID");
|
invalidFields.push("userID");
|
||||||
if (userID?.length < 30) errors.push(`userID must be at least 30 characters long`);
|
if (userID?.length < 30) errors.push(`userID must be at least 30 characters long`);
|
||||||
}
|
}
|
||||||
if (!Array.isArray(segments) || segments.length < 1) {
|
if (!Array.isArray(segments) || segments.length == 0) {
|
||||||
invalidFields.push("segments");
|
invalidFields.push("segments");
|
||||||
}
|
}
|
||||||
// validate start and end times (no : marks)
|
// validate start and end times (no : marks)
|
||||||
@@ -323,7 +223,7 @@ function checkInvalidFields(videoID: VideoID, userID: UserID, segments: Incoming
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof segmentPair.description !== "string"
|
if (typeof segmentPair.description !== "string"
|
||||||
|| (segmentPair.description.length > 60 && segmentPair.actionType === ActionType.Chapter)
|
|| (segmentPair.actionType === ActionType.Chapter && segmentPair.description.length > 60 )
|
||||||
|| (segmentPair.description.length !== 0 && segmentPair.actionType !== ActionType.Chapter)) {
|
|| (segmentPair.description.length !== 0 && segmentPair.actionType !== ActionType.Chapter)) {
|
||||||
invalidFields.push("segment description");
|
invalidFields.push("segment description");
|
||||||
}
|
}
|
||||||
@@ -402,7 +302,7 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isVIP && segments[i].category === "sponsor"
|
if (!isVIP && segments[i].category === "sponsor"
|
||||||
&& segments[i].actionType !== ActionType.Full && Math.abs(startTime - endTime) < 1) {
|
&& segments[i].actionType !== ActionType.Full && (endTime - startTime) < 1) {
|
||||||
// Too short
|
// Too short
|
||||||
return { pass: false, errorMessage: "Segments must be longer than 1 second long", errorCode: 400 };
|
return { pass: false, errorMessage: "Segments must be longer than 1 second long", errorCode: 400 };
|
||||||
}
|
}
|
||||||
@@ -425,32 +325,19 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user
|
|||||||
return CHECK_PASS;
|
return CHECK_PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkByAutoModerator(videoID: any, userID: any, segments: Array<any>, isVIP: boolean, service:string, apiVideoInfo: APIVideoInfo, decreaseVotes: number): Promise<CheckResult & { decreaseVotes: number; } > {
|
async function checkByAutoModerator(videoID: any, userID: any, segments: Array<any>, service:string, apiVideoInfo: APIVideoInfo, videoDuration: number): Promise<CheckResult> {
|
||||||
// Auto moderator check
|
// Auto moderator check
|
||||||
if (!isVIP && service == Service.YouTube) {
|
if (service == Service.YouTube) {
|
||||||
const autoModerateResult = await autoModerateSubmission(apiVideoInfo, { userID, videoID, segments, service });//startTime, endTime, category: segments[i].category});
|
const autoModerateResult = await autoModerateSubmission(apiVideoInfo, { userID, videoID, segments, service, videoDuration });
|
||||||
|
if (autoModerateResult) {
|
||||||
if (autoModerateResult == "Rejected based on NeuralBlock predictions.") {
|
|
||||||
// If NB automod rejects, the submission will start with -2 votes.
|
|
||||||
// Note, if one submission is bad all submissions will be affected.
|
|
||||||
// However, this behavior is consistent with other automod functions
|
|
||||||
// already in place.
|
|
||||||
//decreaseVotes = -2; //Disable for now
|
|
||||||
} else if (autoModerateResult) {
|
|
||||||
//Normal automod behavior
|
|
||||||
return {
|
return {
|
||||||
pass: false,
|
pass: false,
|
||||||
errorCode: 403,
|
errorCode: 403,
|
||||||
errorMessage: `Request rejected by auto moderator: ${autoModerateResult} If this is an issue, send a message on Discord.`,
|
errorMessage: `Request rejected by auto moderator: ${autoModerateResult} If this is an issue, send a message on Discord.`
|
||||||
decreaseVotes
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return CHECK_PASS;
|
||||||
return {
|
|
||||||
...CHECK_PASS,
|
|
||||||
decreaseVotes
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateDataIfVideoDurationChange(videoID: VideoID, service: Service, videoDuration: VideoDuration, videoDurationParam: VideoDuration) {
|
async function updateDataIfVideoDurationChange(videoID: VideoID, service: Service, videoDuration: VideoDuration, videoDurationParam: VideoDuration) {
|
||||||
@@ -601,11 +488,12 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
|||||||
|
|
||||||
const userWarningCheckResult = await checkUserActiveWarning(userID);
|
const userWarningCheckResult = await checkUserActiveWarning(userID);
|
||||||
if (!userWarningCheckResult.pass) {
|
if (!userWarningCheckResult.pass) {
|
||||||
Logger.warn(`Caught a submission for for a warned user. userID: '${userID}', videoID: '${videoID}', category: '${segments.reduce<string>((prev, val) => `${prev} ${val.category}`, "")}', times: ${segments.reduce<string>((prev, val) => `${prev} ${val.segment}`, "")}`);
|
Logger.warn(`Caught a submission for a warned user. userID: '${userID}', videoID: '${videoID}', category: '${segments.reduce<string>((prev, val) => `${prev} ${val.category}`, "")}', times: ${segments.reduce<string>((prev, val) => `${prev} ${val.segment}`, "")}`);
|
||||||
return res.status(userWarningCheckResult.errorCode).send(userWarningCheckResult.errorMessage);
|
return res.status(userWarningCheckResult.errorCode).send(userWarningCheckResult.errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isVIP = await isUserVIP(userID);
|
const isVIP = await isUserVIP(userID);
|
||||||
|
const isTempVIP = await isUserTempVIP(userID, videoID);
|
||||||
const rawIP = getIP(req);
|
const rawIP = getIP(req);
|
||||||
|
|
||||||
const newData = await updateDataIfVideoDurationChange(videoID, service, videoDuration, videoDurationParam);
|
const newData = await updateDataIfVideoDurationChange(videoID, service, videoDuration, videoDurationParam);
|
||||||
@@ -618,13 +506,11 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
|||||||
return res.status(segmentCheckResult.errorCode).send(segmentCheckResult.errorMessage);
|
return res.status(segmentCheckResult.errorCode).send(segmentCheckResult.errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
let decreaseVotes = 0;
|
if (!isVIP && !isTempVIP) {
|
||||||
// Auto check by NB
|
const autoModerateCheckResult = await checkByAutoModerator(videoID, userID, segments, service, apiVideoInfo, videoDurationParam);
|
||||||
const autoModerateCheckResult = await checkByAutoModerator(videoID, userID, segments, isVIP, service, apiVideoInfo, decreaseVotes);
|
|
||||||
if (!autoModerateCheckResult.pass) {
|
if (!autoModerateCheckResult.pass) {
|
||||||
return res.status(autoModerateCheckResult.errorCode).send(autoModerateCheckResult.errorMessage);
|
return res.status(autoModerateCheckResult.errorCode).send(autoModerateCheckResult.errorMessage);
|
||||||
} else {
|
}
|
||||||
decreaseVotes = autoModerateCheckResult.decreaseVotes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will be filled when submitting
|
// Will be filled when submitting
|
||||||
@@ -645,7 +531,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
|||||||
|
|
||||||
//check to see if this user is shadowbanned
|
//check to see if this user is shadowbanned
|
||||||
const shadowBanRow = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]);
|
const shadowBanRow = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]);
|
||||||
const startingVotes = 0 + decreaseVotes;
|
const startingVotes = 0;
|
||||||
const reputation = await getReputation(userID);
|
const reputation = await getReputation(userID);
|
||||||
|
|
||||||
for (const segmentInfo of segments) {
|
for (const segmentInfo of segments) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Logger } from "../utils/logger";
|
import { Logger } from "../utils/logger";
|
||||||
import { isUserVIP } from "../utils/isUserVIP";
|
import { isUserVIP } from "../utils/isUserVIP";
|
||||||
|
import { isUserTempVIP } from "../utils/isUserTempVIP";
|
||||||
import { getMaxResThumbnail, YouTubeAPI } from "../utils/youtubeApi";
|
import { getMaxResThumbnail, YouTubeAPI } from "../utils/youtubeApi";
|
||||||
import { APIVideoInfo } from "../types/youtubeApi.model";
|
import { APIVideoInfo } from "../types/youtubeApi.model";
|
||||||
import { db, privateDB } from "../databases/databases";
|
import { db, privateDB } from "../databases/databases";
|
||||||
@@ -9,12 +10,10 @@ import { getFormattedTime } from "../utils/getFormattedTime";
|
|||||||
import { getIP } from "../utils/getIP";
|
import { getIP } from "../utils/getIP";
|
||||||
import { getHashCache } from "../utils/getHashCache";
|
import { getHashCache } from "../utils/getHashCache";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
import { HashedUserID, UserID } from "../types/user.model";
|
import { UserID } from "../types/user.model";
|
||||||
import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType } from "../types/segments.model";
|
import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType } from "../types/segments.model";
|
||||||
import { QueryCacher } from "../utils/queryCacher";
|
import { QueryCacher } from "../utils/queryCacher";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import redis from "../utils/redis";
|
|
||||||
import { tempVIPKey } from "../utils/redisKeys";
|
|
||||||
|
|
||||||
const voteTypes = {
|
const voteTypes = {
|
||||||
normal: 0,
|
normal: 0,
|
||||||
@@ -55,14 +54,6 @@ function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<API
|
|||||||
return config.newLeafURLs ? YouTubeAPI.listVideos(videoID, ignoreCache) : null;
|
return config.newLeafURLs ? YouTubeAPI.listVideos(videoID, ignoreCache) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUserTempVIP = async (nonAnonUserID: HashedUserID, videoID: VideoID): Promise<boolean> => {
|
|
||||||
const apiVideoInfo = await getYouTubeVideoInfo(videoID);
|
|
||||||
const channelID = apiVideoInfo?.data?.authorId;
|
|
||||||
const { err, reply } = await redis.getAsync(tempVIPKey(nonAnonUserID));
|
|
||||||
|
|
||||||
return err || !reply ? false : (reply == channelID);
|
|
||||||
};
|
|
||||||
|
|
||||||
const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2);
|
const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2);
|
||||||
|
|
||||||
async function updateSegmentVideoDuration(UUID: SegmentUUID) {
|
async function updateSegmentVideoDuration(UUID: SegmentUUID) {
|
||||||
|
|||||||
19
src/utils/isUserTempVIP.ts
Normal file
19
src/utils/isUserTempVIP.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import redis from "../utils/redis";
|
||||||
|
import { tempVIPKey } from "../utils/redisKeys";
|
||||||
|
import { HashedUserID } from "../types/user.model";
|
||||||
|
import { YouTubeAPI } from "../utils/youtubeApi";
|
||||||
|
import { APIVideoInfo } from "../types/youtubeApi.model";
|
||||||
|
import { VideoID } from "../types/segments.model";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
|
||||||
|
return config.newLeafURLs ? YouTubeAPI.listVideos(videoID, ignoreCache) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isUserTempVIP = async (hashedUserID: HashedUserID, videoID: VideoID): Promise<boolean> => {
|
||||||
|
const apiVideoInfo = await getYouTubeVideoInfo(videoID);
|
||||||
|
const channelID = apiVideoInfo?.data?.authorId;
|
||||||
|
const { err, reply } = await redis.getAsync(tempVIPKey(hashedUserID));
|
||||||
|
|
||||||
|
return err || !reply ? false : (reply == channelID);
|
||||||
|
};
|
||||||
@@ -734,22 +734,6 @@ describe("postSkipSegments", () => {
|
|||||||
.catch(err => done(err));
|
.catch(err => done(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should be rejected if NB's predicted probability is <70%.", (done) => {
|
|
||||||
const videoID = "LevkAjUE6d4";
|
|
||||||
postSkipSegmentParam({
|
|
||||||
videoID,
|
|
||||||
startTime: 40,
|
|
||||||
endTime: 60,
|
|
||||||
userID: submitUserTwo,
|
|
||||||
category: "sponsor"
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(err => done(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should be rejected with custom message if user has to many active warnings", (done) => {
|
it("Should be rejected with custom message if user has to many active warnings", (done) => {
|
||||||
postSkipSegmentJSON({
|
postSkipSegmentJSON({
|
||||||
userID: warnUser01,
|
userID: warnUser01,
|
||||||
|
|||||||
Reference in New Issue
Block a user