mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-26 01:18:40 +03:00
@@ -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, SegmentUUID, Service, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model";
|
||||
import { ActionType, 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';
|
||||
@@ -12,7 +12,7 @@ import { QueryCacher } from '../utils/queryCacher';
|
||||
import { getReputation } from '../utils/reputation';
|
||||
|
||||
|
||||
async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise<Segment[]> {
|
||||
async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[],cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise<Segment[]> {
|
||||
const shouldFilter: boolean[] = await Promise.all(segments.map(async (segment) => {
|
||||
if (segment.votes < -1 && !segment.required) {
|
||||
return false; //too untrustworthy, just ignore it
|
||||
@@ -43,14 +43,16 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category:
|
||||
|
||||
const maxSegments = getCategoryActionType(category) === CategoryActionType.Skippable ? 32 : 1;
|
||||
return (await chooseSegments(filteredSegments, maxSegments)).map((chosenSegment) => ({
|
||||
category,
|
||||
category: chosenSegment.category,
|
||||
actionType: chosenSegment.actionType,
|
||||
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||
UUID: chosenSegment.UUID,
|
||||
videoDuration: chosenSegment.videoDuration
|
||||
}));
|
||||
}
|
||||
|
||||
async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], requiredSegments: SegmentUUID[], service: Service): Promise<Segment[]> {
|
||||
async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[],
|
||||
actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise<Segment[]> {
|
||||
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
|
||||
const segments: Segment[] = [];
|
||||
|
||||
@@ -59,16 +61,17 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories:
|
||||
if (categories.length === 0) return null;
|
||||
|
||||
const segmentsByCategory: SBRecord<Category, DBSegment[]> = (await getSegmentsFromDBByVideoID(videoID, service))
|
||||
.filter((segment: DBSegment) => categories.includes(segment?.category))
|
||||
.filter((segment: DBSegment) => categories.includes(segment?.category) && actionTypes.includes(segment?.actionType))
|
||||
.reduce((acc: SBRecord<Category, DBSegment[]>, segment: DBSegment) => {
|
||||
if (requiredSegments.includes(segment.UUID)) segment.required = true;
|
||||
|
||||
acc[segment.category] = acc[segment.category] || [];
|
||||
acc[segment.category] ??= [];
|
||||
acc[segment.category].push(segment);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
|
||||
segments.push(...(await prepareCategorySegments(req, videoID, category as Category, categorySegments, cache)));
|
||||
}
|
||||
@@ -82,7 +85,8 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories:
|
||||
}
|
||||
}
|
||||
|
||||
async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[], requiredSegments: SegmentUUID[], service: Service): Promise<SBRecord<VideoID, VideoData>> {
|
||||
async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[],
|
||||
actionTypes: ActionType[], requiredSegments: SegmentUUID[], service: Service): Promise<SBRecord<VideoID, VideoData>> {
|
||||
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
|
||||
const segments: SBRecord<VideoID, VideoData> = {};
|
||||
|
||||
@@ -93,17 +97,16 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
|
||||
if (categories.length === 0) return null;
|
||||
|
||||
const segmentPerVideoID: SegmentWithHashPerVideoID = (await getSegmentsFromDBByHash(hashedVideoIDPrefix, service))
|
||||
.filter((segment: DBSegment) => categories.includes(segment?.category))
|
||||
.filter((segment: DBSegment) => categories.includes(segment?.category) && actionTypes.includes(segment?.actionType))
|
||||
.reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => {
|
||||
acc[segment.videoID] = acc[segment.videoID] || {
|
||||
hash: segment.hashedVideoID,
|
||||
segmentPerCategory: {},
|
||||
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);
|
||||
acc[segment.videoID].segmentPerCategory[segment.category] ??= [];
|
||||
acc[segment.videoID].segmentPerCategory[segment.category].push(segment);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -114,6 +117,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
|
||||
segments: [],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const [category, segmentPerCategory] of Object.entries(videoData.segmentPerCategory)) {
|
||||
segments[videoID].segments.push(...(await prepareCategorySegments(req, videoID as VideoID, category as Category, segmentPerCategory, cache)));
|
||||
}
|
||||
@@ -132,7 +136,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service
|
||||
const fetchFromDB = () => db
|
||||
.prepare(
|
||||
'all',
|
||||
`SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "videoDuration", "reputation", "shadowHidden", "hashedVideoID" FROM "sponsorTimes"
|
||||
`SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "hashedVideoID" FROM "sponsorTimes"
|
||||
WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`,
|
||||
[hashedVideoIDPrefix + '%', service]
|
||||
) as Promise<DBSegment[]>;
|
||||
@@ -148,7 +152,7 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P
|
||||
const fetchFromDB = () => db
|
||||
.prepare(
|
||||
'all',
|
||||
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "videoDuration", "reputation", "shadowHidden" FROM "sponsorTimes"
|
||||
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden" FROM "sponsorTimes"
|
||||
WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`,
|
||||
[videoID, service]
|
||||
) as Promise<DBSegment[]>;
|
||||
@@ -275,7 +279,7 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
|
||||
const videoID = req.query.videoID as VideoID;
|
||||
// Default to sponsor
|
||||
// If using params instead of JSON, only one category can be pulled
|
||||
const categories = req.query.categories
|
||||
const categories: Category[] = req.query.categories
|
||||
? JSON.parse(req.query.categories as string)
|
||||
: req.query.category
|
||||
? Array.isArray(req.query.category)
|
||||
@@ -287,6 +291,18 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
|
||||
return false;
|
||||
}
|
||||
|
||||
const actionTypes: ActionType[] = req.query.actionTypes
|
||||
? JSON.parse(req.query.actionTypes as string)
|
||||
: req.query.actionType
|
||||
? Array.isArray(req.query.actionType)
|
||||
? req.query.actionType
|
||||
: [req.query.actionType]
|
||||
: [ActionType.Skip];
|
||||
if (!Array.isArray(actionTypes)) {
|
||||
res.status(400).send("actionTypes parameter does not match format requirements.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const requiredSegments: SegmentUUID[] = req.query.requiredSegments
|
||||
? JSON.parse(req.query.requiredSegments as string)
|
||||
: req.query.requiredSegment
|
||||
@@ -304,7 +320,7 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
|
||||
service = Service.YouTube;
|
||||
}
|
||||
|
||||
const segments = await getSegmentsByVideoID(req, videoID, categories, requiredSegments, service);
|
||||
const segments = await getSegmentsByVideoID(req, videoID, categories, actionTypes, requiredSegments, service);
|
||||
|
||||
if (segments === null || segments === undefined) {
|
||||
res.sendStatus(500);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {hashPrefixTester} from '../utils/hashPrefixTester';
|
||||
import {getSegmentsByHash} from './getSkipSegments';
|
||||
import {Request, Response} from 'express';
|
||||
import { Category, SegmentUUID, Service, VideoIDHash } from '../types/segments.model';
|
||||
import { ActionType, Category, SegmentUUID, Service, VideoIDHash } from '../types/segments.model';
|
||||
|
||||
export async function getSkipSegmentsByHash(req: Request, res: Response): Promise<Response> {
|
||||
let hashPrefix = req.params.prefix as VideoIDHash;
|
||||
@@ -26,6 +26,22 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis
|
||||
return res.status(400).send("Bad parameter: categories (invalid JSON)");
|
||||
}
|
||||
|
||||
let actionTypes: ActionType[] = [];
|
||||
try {
|
||||
actionTypes = req.query.actionTypes
|
||||
? JSON.parse(req.query.actionTypes as string)
|
||||
: req.query.actionType
|
||||
? Array.isArray(req.query.actionType)
|
||||
? req.query.actionType
|
||||
: [req.query.actionType]
|
||||
: [ActionType.Skip];
|
||||
if (!Array.isArray(actionTypes)) {
|
||||
return res.status(400).send("actionTypes parameter does not match format requirements.");
|
||||
}
|
||||
} catch(error) {
|
||||
return res.status(400).send("Bad parameter: actionTypes (invalid JSON)");
|
||||
}
|
||||
|
||||
let requiredSegments: SegmentUUID[] = [];
|
||||
try {
|
||||
requiredSegments = req.query.requiredSegments
|
||||
@@ -51,7 +67,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, requiredSegments, service);
|
||||
const segments = await getSegmentsByHash(req, hashPrefix, categories, actionTypes, requiredSegments, service);
|
||||
|
||||
if (!segments) return res.status(404).json([]);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {getFormattedTime} from '../utils/getFormattedTime';
|
||||
import {isUserTrustworthy} from '../utils/isUserTrustworthy';
|
||||
import {dispatchEvent} from '../utils/webhookUtils';
|
||||
import {Request, Response} from 'express';
|
||||
import { Category, CategoryActionType, IncomingSegment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model';
|
||||
import { ActionType, Category, CategoryActionType, IncomingSegment, SegmentUUID, Service, VideoDuration, VideoID } from '../types/segments.model';
|
||||
import { deleteLockCategories } from './deleteLockCategories';
|
||||
import { getCategoryActionType } from '../utils/categoryInfo';
|
||||
import { QueryCacher } from '../utils/queryCacher';
|
||||
@@ -315,7 +315,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
||||
const videoID = req.query.videoID || req.body.videoID;
|
||||
let userID = req.query.userID || req.body.userID;
|
||||
let service: Service = req.query.service ?? req.body.service ?? Service.YouTube;
|
||||
if (!Object.values(Service).some((val) => val == service)) {
|
||||
if (!Object.values(Service).some((val) => val === service)) {
|
||||
service = Service.YouTube;
|
||||
}
|
||||
let videoDuration: VideoDuration = (parseFloat(req.query.videoDuration || req.body.videoDuration) || 0) as VideoDuration;
|
||||
@@ -325,9 +325,16 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
||||
// Use query instead
|
||||
segments = [{
|
||||
segment: [req.query.startTime as string, req.query.endTime as string],
|
||||
category: req.query.category as Category
|
||||
category: req.query.category as Category,
|
||||
actionType: (req.query.actionType as ActionType) ?? ActionType.Skip
|
||||
}];
|
||||
}
|
||||
// Add default action type
|
||||
segments.forEach((segment) => {
|
||||
if (!Object.values(ActionType).some((val) => val === segment.actionType)){
|
||||
segment.actionType = ActionType.Skip;
|
||||
}
|
||||
});
|
||||
|
||||
const invalidFields = [];
|
||||
if (typeof videoID !== 'string') {
|
||||
@@ -518,9 +525,9 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
||||
const startingLocked = isVIP ? 1 : 0;
|
||||
try {
|
||||
await db.prepare('run', `INSERT INTO "sponsorTimes"
|
||||
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID")
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
||||
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, service, videoDuration, reputation, shadowBanned, hashedVideoID,
|
||||
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID")
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
||||
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanned, hashedVideoID,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user