Add support for spotify service

This commit is contained in:
Ajay
2025-10-14 03:34:02 -04:00
parent 1eedc9fa09
commit 0412386870
4 changed files with 85 additions and 42 deletions

View File

@@ -1,16 +1,18 @@
import { db } from "../databases/databases"; import { db } from "../databases/databases";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { getService } from "../utils/getService";
export async function getSegmentID(req: Request, res: Response): Promise<Response> { export async function getSegmentID(req: Request, res: Response): Promise<Response> {
const partialUUID = req.query?.UUID; const partialUUID = req.query?.UUID;
const videoID = req.query?.videoID; const videoID = req.query?.videoID;
const service = getService(req.query?.service as string);
if (!partialUUID || !videoID) { if (!partialUUID || !videoID) {
//invalid request //invalid request
return res.sendStatus(400); return res.sendStatus(400);
} }
const data = await db.prepare("get", `SELECT "UUID" from "sponsorTimes" WHERE "UUID" LIKE ? AND "videoID" = ?`, [`${partialUUID}%`, videoID]); const data = await db.prepare("get", `SELECT "UUID" from "sponsorTimes" WHERE "UUID" LIKE ? AND "videoID" = ? AND "service" = ?`, [`${partialUUID}%`, videoID, service]);
if (data) { if (data) {
return res.status(200).send(data.UUID); return res.status(200).send(data.UUID);

View File

@@ -141,8 +141,8 @@ async function autoModerateSubmission(apiVideoDetails: videoDetails,
.map(segment => [parseFloat(segment.segment[0]), parseFloat(segment.segment[1])]); .map(segment => [parseFloat(segment.segment[0]), parseFloat(segment.segment[1])]);
// add previous submissions by 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 AND "actionType" != 'chapter' AND "hidden" = 0` const allSubmittedByUser = await db.prepare("all", `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? AND "videoID" = ? AND "service" = ? AND "votes" > -1 AND "actionType" != 'chapter' AND "hidden" = 0`
, [submission.userID, submission.videoID]) as { startTime: string, endTime: string }[]; , [submission.userID, submission.videoID, submission.service]) as { startTime: string, endTime: string }[];
if (allSubmittedByUser) { if (allSubmittedByUser) {
//add segments the user has previously submitted //add segments the user has previously submitted
@@ -195,13 +195,18 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID
if (typeof videoID !== "string" || videoID?.length == 0) { if (typeof videoID !== "string" || videoID?.length == 0) {
invalidFields.push("videoID"); invalidFields.push("videoID");
} }
if (service === Service.YouTube && config.mode !== "test") { if (service === Service.YouTube) {
if (config.mode !== "test") {
const sanitizedVideoID = youtubeID.validate(videoID) ? videoID : youtubeID.sanitize(videoID); const sanitizedVideoID = youtubeID.validate(videoID) ? videoID : youtubeID.sanitize(videoID);
if (!youtubeID.validate(sanitizedVideoID)) { if (!youtubeID.validate(sanitizedVideoID)) {
invalidFields.push("videoID"); invalidFields.push("videoID");
errors.push("YouTube videoID could not be extracted"); errors.push("YouTube videoID could not be extracted");
} }
} }
} else if (service !== Service.Spotify) {
invalidFields.push("service");
errors.push("Service is not supported");
}
const minLength = config.minUserIDLength; const minLength = config.minUserIDLength;
if (typeof userID !== "string" || userID?.length < minLength) { if (typeof userID !== "string" || userID?.length < minLength) {
invalidFields.push("userID"); invalidFields.push("userID");
@@ -360,6 +365,15 @@ async function checkByAutoModerator(videoID: VideoID, userID: HashedUserID, segm
async function updateDataIfVideoDurationChange(videoID: VideoID, service: Service, videoDuration: VideoDuration, videoDurationParam: VideoDuration) { async function updateDataIfVideoDurationChange(videoID: VideoID, service: Service, videoDuration: VideoDuration, videoDurationParam: VideoDuration) {
let lockedCategoryList = await db.prepare("all", 'SELECT category, "actionType", reason from "lockCategories" where "videoID" = ? AND "service" = ?', [videoID, service]); let lockedCategoryList = await db.prepare("all", 'SELECT category, "actionType", reason from "lockCategories" where "videoID" = ? AND "service" = ?', [videoID, service]);
if (service === Service.Spotify) {
// Don't handle changed durations
return {
videoDuration,
apiVideoDetails: null,
lockedCategoryList
};
}
const previousSubmissions = await db.prepare("all", const previousSubmissions = await db.prepare("all",
`SELECT "videoDuration", "UUID" `SELECT "videoDuration", "UUID"
FROM "sponsorTimes" FROM "sponsorTimes"
@@ -617,10 +631,12 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
//add to private db as well //add to private db as well
await privateDB.prepare("run", `INSERT INTO "sponsorTimes" VALUES(?, ?, ?, ?)`, [videoID, hashedIP, timeSubmitted, service]); await privateDB.prepare("run", `INSERT INTO "sponsorTimes" VALUES(?, ?, ?, ?)`, [videoID, hashedIP, timeSubmitted, service]);
if (service === Service.YouTube) {
await db.prepare("run", `INSERT INTO "videoInfo" ("videoID", "channelID", "title", "published") await db.prepare("run", `INSERT INTO "videoInfo" ("videoID", "channelID", "title", "published")
SELECT ?, ?, ?, ? SELECT ?, ?, ?, ?
WHERE NOT EXISTS (SELECT 1 FROM "videoInfo" WHERE "videoID" = ?)`, [ WHERE NOT EXISTS (SELECT 1 FROM "videoInfo" WHERE "videoID" = ?)`, [
videoID, apiVideoDetails?.authorId || "", apiVideoDetails?.title || "", apiVideoDetails?.published || 0, videoID]); videoID, apiVideoDetails?.authorId || "", apiVideoDetails?.title || "", apiVideoDetails?.published || 0, videoID]);
}
// Clear redis cache for this video // Clear redis cache for this video
QueryCacher.clearSegmentCache({ QueryCacher.clearSegmentCache({

View File

@@ -22,7 +22,8 @@ export enum ActionType {
// Uncomment as needed // Uncomment as needed
export enum Service { export enum Service {
YouTube = "YouTube", YouTube = "YouTube",
PeerTube = "PeerTube", Spotify = "Spotify",
PeerTube = "PeerTube"
// Twitch = 'Twitch', // Twitch = 'Twitch',
// Nebula = 'Nebula', // Nebula = 'Nebula',
// RSS = 'RSS', // RSS = 'RSS',

View File

@@ -46,8 +46,8 @@ describe("postSkipSegments", () => {
const submitVIPuser = `VIPPostSkipUser${".".repeat(16)}`; const submitVIPuser = `VIPPostSkipUser${".".repeat(16)}`;
const queryDatabase = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "votes", "userID", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]); const queryDatabase = (videoID: string, service = "YouTube") => db.prepare("get", `SELECT "startTime", "endTime", "votes", "userID", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ?`, [videoID, service]);
const queryDatabaseActionType = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]); const queryDatabaseActionType = (videoID: string, service = "YouTube") => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ?`, [videoID, service]);
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]);
before(async () => { before(async () => {
@@ -142,33 +142,6 @@ describe("postSkipSegments", () => {
.catch(err => done(err)); .catch(err => done(err));
}); });
it("Should be able to submit a single time under a different service (JSON method)", (done) => {
const videoID = "postSkip7";
postSkipSegmentJSON({
userID: submitUserOne,
videoID,
service: "PeerTube",
segments: [{
segment: [0, 10],
category: "sponsor",
}],
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "service" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const expected = {
startTime: 0,
endTime: 10,
locked: 0,
category: "sponsor",
service: "PeerTube",
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
it("VIP submission should start locked", (done) => { it("VIP submission should start locked", (done) => {
const videoID = "vipuserIDSubmission"; const videoID = "vipuserIDSubmission";
postSkipSegmentJSON({ postSkipSegmentJSON({
@@ -374,4 +347,55 @@ describe("postSkipSegments", () => {
}) })
.catch(err => done(err)); .catch(err => done(err));
}); });
it("Should be able to submit for spotify service", (done) => {
const videoID = "postSkipParamSingle";
postSkipSegmentParam({
videoID,
startTime: 23,
endTime: 105,
userID: submitUserOne,
category: "sponsor",
service: "Spotify"
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await queryDatabase(videoID, "Spotify");
const expected = {
startTime: 23,
endTime: 105,
category: "sponsor",
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
it("Should be able to submit a time for spotify service (JSON method)", (done) => {
const videoID = "postSkipJSONSingle";
postSkipSegmentJSON({
userID: submitUserOne,
videoID,
segments: [{
segment: [22, 103],
category: "sponsor",
}],
service: "Spotify"
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await queryDatabase(videoID, "Spotify");
const expected = {
startTime: 22,
endTime: 103,
locked: 0,
category: "sponsor",
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
}); });