mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 01:02:30 +01:00
Add submitting description for chapters
This commit is contained in:
parent
0c16448065
commit
32150e4a1d
8 changed files with 98 additions and 10 deletions
1
ci.json
1
ci.json
|
@ -46,7 +46,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "poi_highlight"],
|
|
||||||
"maxNumberOfActiveWarnings": 3,
|
"maxNumberOfActiveWarnings": 3,
|
||||||
"hoursAfterWarningExpires": 24,
|
"hoursAfterWarningExpires": 24,
|
||||||
"rateLimit": {
|
"rateLimit": {
|
||||||
|
|
8
databases/_upgrade_sponsorTimes_27.sql
Normal file
8
databases/_upgrade_sponsorTimes_27.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE "sponsorTimes" ADD "description" TEXT NOT NULL default '';
|
||||||
|
ALTER TABLE "archivedSponsorTimes" ADD "description" TEXT NOT NULL default '';
|
||||||
|
|
||||||
|
UPDATE "config" SET value = 27 WHERE key = 'version';
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -19,7 +19,7 @@ addDefaults(config, {
|
||||||
privateDBSchema: "./databases/_private.db.sql",
|
privateDBSchema: "./databases/_private.db.sql",
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
webhooks: [],
|
webhooks: [],
|
||||||
categoryList: ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "poi_highlight"],
|
categoryList: ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "poi_highlight", "chapter"],
|
||||||
categorySupport: {
|
categorySupport: {
|
||||||
sponsor: ["skip", "mute"],
|
sponsor: ["skip", "mute"],
|
||||||
selfpromo: ["skip", "mute"],
|
selfpromo: ["skip", "mute"],
|
||||||
|
@ -29,6 +29,7 @@ addDefaults(config, {
|
||||||
preview: ["skip"],
|
preview: ["skip"],
|
||||||
music_offtopic: ["skip"],
|
music_offtopic: ["skip"],
|
||||||
poi_highlight: ["skip"],
|
poi_highlight: ["skip"],
|
||||||
|
chapter: ["chapter"]
|
||||||
},
|
},
|
||||||
maxNumberOfActiveWarnings: 1,
|
maxNumberOfActiveWarnings: 1,
|
||||||
hoursAfterWarningExpires: 24,
|
hoursAfterWarningExpires: 24,
|
||||||
|
|
|
@ -299,7 +299,7 @@ async function checkUserActiveWarning(userID: string): Promise<CheckResult> {
|
||||||
return CHECK_PASS;
|
return CHECK_PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkInvalidFields(videoID: any, userID: any, segments: Array<any>): CheckResult {
|
function checkInvalidFields(videoID: VideoID, userID: UserID, segments: IncomingSegment[]): CheckResult {
|
||||||
const invalidFields = [];
|
const invalidFields = [];
|
||||||
const errors = [];
|
const errors = [];
|
||||||
if (typeof videoID !== "string") {
|
if (typeof videoID !== "string") {
|
||||||
|
@ -320,6 +320,12 @@ function checkInvalidFields(videoID: any, userID: any, segments: Array<any>): Ch
|
||||||
(typeof endTime === "string" && endTime.includes(":"))) {
|
(typeof endTime === "string" && endTime.includes(":"))) {
|
||||||
invalidFields.push("segment time");
|
invalidFields.push("segment time");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof segmentPair.description !== "string"
|
||||||
|
|| (segmentPair.description.length > 60 && segmentPair.actionType === ActionType.Chapter)
|
||||||
|
|| (segmentPair.description.length !== 0 && segmentPair.actionType !== ActionType.Chapter)) {
|
||||||
|
invalidFields.push("segment description");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invalidFields.length !== 0) {
|
if (invalidFields.length !== 0) {
|
||||||
|
@ -541,7 +547,8 @@ function preprocessInput(req: Request) {
|
||||||
segments = [{
|
segments = [{
|
||||||
segment: [req.query.startTime as string, req.query.endTime as string],
|
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
|
actionType: (req.query.actionType as ActionType) ?? ActionType.Skip,
|
||||||
|
description: req.query.description as string || "",
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
// Add default action type
|
// Add default action type
|
||||||
|
@ -550,6 +557,7 @@ function preprocessInput(req: Request) {
|
||||||
segment.actionType = ActionType.Skip;
|
segment.actionType = ActionType.Skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
segment.description ??= "";
|
||||||
segment.segment = segment.segment.map((time) => typeof segment.segment[0] === "string" ? time?.replace(",", ".") : time);
|
segment.segment = segment.segment.map((time) => typeof segment.segment[0] === "string" ? time?.replace(",", ".") : time);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -633,9 +641,10 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
|
||||||
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", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent")
|
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent", "description")
|
||||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
||||||
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanRow.userCount, hashedVideoID, userAgent
|
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0
|
||||||
|
, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanRow.userCount, hashedVideoID, userAgent, segmentInfo.description
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { UserID } from "./user.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 VideoDuration = number & { __videoDurationBrand: unknown };
|
||||||
export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight") & { __categoryBrand: unknown };
|
export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight" | "chapter") & { __categoryBrand: unknown };
|
||||||
export type VideoIDHash = VideoID & HashedValue;
|
export type VideoIDHash = VideoID & HashedValue;
|
||||||
export type IPAddress = string & { __ipAddressBrand: unknown };
|
export type IPAddress = string & { __ipAddressBrand: unknown };
|
||||||
export type HashedIP = IPAddress & HashedValue;
|
export type HashedIP = IPAddress & HashedValue;
|
||||||
|
@ -13,6 +13,7 @@ export type HashedIP = IPAddress & HashedValue;
|
||||||
export enum ActionType {
|
export enum ActionType {
|
||||||
Skip = "skip",
|
Skip = "skip",
|
||||||
Mute = "mute",
|
Mute = "mute",
|
||||||
|
Chapter = "chapter"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncomment as needed
|
// Uncomment as needed
|
||||||
|
@ -30,6 +31,7 @@ export interface IncomingSegment {
|
||||||
category: Category;
|
category: Category;
|
||||||
actionType: ActionType;
|
actionType: ActionType;
|
||||||
segment: string[];
|
segment: string[];
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Segment {
|
export interface Segment {
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "poi_highlight"],
|
|
||||||
"maxNumberOfActiveWarnings": 3,
|
"maxNumberOfActiveWarnings": 3,
|
||||||
"hoursAfterWarningExpires": 24,
|
"hoursAfterWarningExpires": 24,
|
||||||
"rateLimit": {
|
"rateLimit": {
|
||||||
|
|
|
@ -108,7 +108,8 @@ describe("getLockReason", () => {
|
||||||
{ category: "outro", locked: 1, reason: "outro-reason", userID: vipUserID2, userName: vipUserName2 },
|
{ category: "outro", locked: 1, reason: "outro-reason", userID: vipUserID2, userName: vipUserName2 },
|
||||||
{ category: "preview", locked: 1, reason: "preview-reason", userID: vipUserID1, userName: vipUserName1 },
|
{ category: "preview", locked: 1, reason: "preview-reason", userID: vipUserID1, userName: vipUserName1 },
|
||||||
{ category: "music_offtopic", locked: 1, reason: "nonmusic-reason", userID: vipUserID1, userName: vipUserName1 },
|
{ category: "music_offtopic", locked: 1, reason: "nonmusic-reason", userID: vipUserID1, userName: vipUserName1 },
|
||||||
{ category: "poi_highlight", locked: 0, reason: "", userID: "", userName: "" }
|
{ category: "poi_highlight", locked: 0, reason: "", userID: "", userName: "" },
|
||||||
|
{ category: "chapter", locked: 0, reason: "", userID: "", userName: "" }
|
||||||
];
|
];
|
||||||
assert.deepStrictEqual(res.data, expected);
|
assert.deepStrictEqual(res.data, expected);
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -37,6 +37,7 @@ describe("postSkipSegments", () => {
|
||||||
|
|
||||||
const queryDatabase = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
const queryDatabase = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
||||||
const queryDatabaseActionType = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
const queryDatabaseActionType = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
||||||
|
const queryDatabaseChapter = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType", "description" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
||||||
const queryDatabaseDuration = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
const queryDatabaseDuration = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
|
||||||
const queryDatabaseVideoInfo = (videoID: string) => db.prepare("get", `SELECT * FROM "videoInfo" WHERE "videoID" = ?`, [videoID]);
|
const queryDatabaseVideoInfo = (videoID: string) => db.prepare("get", `SELECT * FROM "videoInfo" WHERE "videoID" = ?`, [videoID]);
|
||||||
|
|
||||||
|
@ -181,6 +182,34 @@ describe("postSkipSegments", () => {
|
||||||
.catch(err => done(err));
|
.catch(err => done(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should be able to submit a single chapter (JSON method)", (done) => {
|
||||||
|
const videoID = "postSkipChapter1";
|
||||||
|
postSkipSegmentJSON({
|
||||||
|
userID: submitUserOne,
|
||||||
|
videoID,
|
||||||
|
segments: [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "chapter",
|
||||||
|
actionType: "chapter",
|
||||||
|
description: "This is a chapter"
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await queryDatabaseChapter(videoID);
|
||||||
|
const expected = {
|
||||||
|
startTime: 0,
|
||||||
|
endTime: 10,
|
||||||
|
category: "chapter",
|
||||||
|
actionType: "chapter",
|
||||||
|
description: "This is a chapter"
|
||||||
|
};
|
||||||
|
assert.ok(partialDeepEquals(row, expected));
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
it("Should not be able to submit an intro with mute action type (JSON method)", (done) => {
|
it("Should not be able to submit an intro with mute action type (JSON method)", (done) => {
|
||||||
const videoID = "postSkip4";
|
const videoID = "postSkip4";
|
||||||
postSkipSegmentJSON({
|
postSkipSegmentJSON({
|
||||||
|
@ -201,6 +230,46 @@ describe("postSkipSegments", () => {
|
||||||
.catch(err => done(err));
|
.catch(err => done(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should not be able to submit a chapter with skip action type (JSON method)", (done) => {
|
||||||
|
const videoID = "postSkipChapter2";
|
||||||
|
postSkipSegmentJSON({
|
||||||
|
userID: submitUserOne,
|
||||||
|
videoID,
|
||||||
|
segments: [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "chapter",
|
||||||
|
actionType: "skip"
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
const row = await queryDatabaseActionType(videoID);
|
||||||
|
assert.strictEqual(row, undefined);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not be able to submit a sponsor with a description (JSON method)", (done) => {
|
||||||
|
const videoID = "postSkipChapter3";
|
||||||
|
postSkipSegmentJSON({
|
||||||
|
userID: submitUserOne,
|
||||||
|
videoID,
|
||||||
|
segments: [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "sponsor",
|
||||||
|
description: "This is a sponsor"
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
const row = await queryDatabaseActionType(videoID);
|
||||||
|
assert.strictEqual(row, undefined);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
it("Should be able to submit a single time with a duration from the YouTube API (JSON method)", (done) => {
|
it("Should be able to submit a single time with a duration from the YouTube API (JSON method)", (done) => {
|
||||||
const videoID = "postSkip5";
|
const videoID = "postSkip5";
|
||||||
postSkipSegmentJSON({
|
postSkipSegmentJSON({
|
||||||
|
|
Loading…
Reference in a new issue