mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-06 11:36:58 +03:00
Don't always use YouTube API cache
This commit is contained in:
@@ -19,7 +19,7 @@ import { getCategoryActionType } from '../utils/categoryInfo';
|
|||||||
|
|
||||||
interface APIVideoInfo {
|
interface APIVideoInfo {
|
||||||
err: string | boolean,
|
err: string | boolean,
|
||||||
data: any
|
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) {
|
||||||
@@ -276,11 +276,9 @@ function getYouTubeVideoDuration(apiVideoInfo: APIVideoInfo): VideoDuration {
|
|||||||
return duration ? isoDurations.toSeconds(isoDurations.parse(duration)) as VideoDuration : null;
|
return duration ? isoDurations.toSeconds(isoDurations.parse(duration)) as VideoDuration : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getYouTubeVideoInfo(videoID: VideoID): Promise<APIVideoInfo> {
|
async function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
|
||||||
if (config.youtubeAPIKey !== null) {
|
if (config.youtubeAPIKey !== null) {
|
||||||
return new Promise((resolve) => {
|
return YouTubeAPI.listVideos(videoID, ignoreCache);
|
||||||
YouTubeAPI.listVideos(videoID, (err: any, data: any) => resolve({err, data}));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -368,9 +366,16 @@ export async function postSkipSegments(req: Request, res: Response) {
|
|||||||
|
|
||||||
const decreaseVotes = 0;
|
const decreaseVotes = 0;
|
||||||
|
|
||||||
|
const previousSubmissions = await db.prepare('all', `SELECT "videoDuration", "UUID" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0
|
||||||
|
AND "shadowHidden" = 0 AND "votes" >= 0 AND "videoDuration" != 0`, [videoID, service]) as
|
||||||
|
{videoDuration: VideoDuration, UUID: SegmentUUID}[];
|
||||||
|
// If the video's duration is changed, then the video should be unlocked and old submissions should be hidden
|
||||||
|
const videoDurationChanged = (videoDuration: number) => previousSubmissions.length > 0 && !previousSubmissions.some((e) => Math.abs(videoDuration - e.videoDuration) < 2);
|
||||||
|
|
||||||
let apiVideoInfo: APIVideoInfo = null;
|
let apiVideoInfo: APIVideoInfo = null;
|
||||||
if (service == Service.YouTube) {
|
if (service == Service.YouTube) {
|
||||||
apiVideoInfo = await getYouTubeVideoInfo(videoID);
|
// Don't use cache if we don't know the video duraton, or the client claims that it has changed
|
||||||
|
apiVideoInfo = await getYouTubeVideoInfo(videoID, !videoDuration || videoDurationChanged(videoDuration));
|
||||||
}
|
}
|
||||||
const apiVideoDuration = getYouTubeVideoDuration(apiVideoInfo);
|
const apiVideoDuration = getYouTubeVideoDuration(apiVideoInfo);
|
||||||
if (!videoDuration || (apiVideoDuration && Math.abs(videoDuration - apiVideoDuration) > 2)) {
|
if (!videoDuration || (apiVideoDuration && Math.abs(videoDuration - apiVideoDuration) > 2)) {
|
||||||
@@ -378,12 +383,7 @@ export async function postSkipSegments(req: Request, res: Response) {
|
|||||||
videoDuration = apiVideoDuration || 0 as VideoDuration;
|
videoDuration = apiVideoDuration || 0 as VideoDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousSubmissions = await db.prepare('all', `SELECT "videoDuration", "UUID" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0
|
if (videoDurationChanged(videoDuration)) {
|
||||||
AND "shadowHidden" = 0 AND "votes" >= 0 AND "videoDuration" != 0`, [videoID, service]) as
|
|
||||||
{videoDuration: VideoDuration, UUID: SegmentUUID}[];
|
|
||||||
// If the video's duration is changed, then the video should be unlocked and old submissions should be hidden
|
|
||||||
const videoDurationChanged = previousSubmissions.length > 0 && !previousSubmissions.some((e) => Math.abs(videoDuration - e.videoDuration) < 2);
|
|
||||||
if (videoDurationChanged) {
|
|
||||||
// Hide all previous submissions
|
// Hide all previous submissions
|
||||||
for (const submission of previousSubmissions) {
|
for (const submission of previousSubmissions) {
|
||||||
await db.prepare('run', `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "UUID" = ?`, [submission.UUID]);
|
await db.prepare('run', `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "UUID" = ?`, [submission.UUID]);
|
||||||
|
|||||||
@@ -60,42 +60,43 @@ async function sendWebhooks(voteData: VoteData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.youtubeAPIKey !== null) {
|
if (config.youtubeAPIKey !== null) {
|
||||||
YouTubeAPI.listVideos(submissionInfoRow.videoID, (err, data) => {
|
const { err, data } = await YouTubeAPI.listVideos(submissionInfoRow.videoID);
|
||||||
|
|
||||||
if (err || data.items.length === 0) {
|
if (err || data.items.length === 0) {
|
||||||
err && Logger.error(err.toString());
|
if (err) Logger.error(err.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isUpvote = voteData.incrementAmount > 0;
|
const isUpvote = voteData.incrementAmount > 0;
|
||||||
// Send custom webhooks
|
// Send custom webhooks
|
||||||
dispatchEvent(isUpvote ? "vote.up" : "vote.down", {
|
dispatchEvent(isUpvote ? "vote.up" : "vote.down", {
|
||||||
"user": {
|
// "user": {
|
||||||
"status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission),
|
// "status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission),
|
||||||
},
|
// },
|
||||||
"video": {
|
// "video": {
|
||||||
"id": submissionInfoRow.videoID,
|
// "id": submissionInfoRow.videoID,
|
||||||
"title": data.items[0].snippet.title,
|
// "title": data.items[0].snippet.title,
|
||||||
"url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID,
|
// "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID,
|
||||||
"thumbnail": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
// "thumbnail": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "",
|
||||||
},
|
// },
|
||||||
"submission": {
|
// "submission": {
|
||||||
"UUID": voteData.UUID,
|
// "UUID": voteData.UUID,
|
||||||
"views": voteData.row.views,
|
// "views": voteData.row.views,
|
||||||
"category": voteData.category,
|
// "category": voteData.category,
|
||||||
"startTime": submissionInfoRow.startTime,
|
// "startTime": submissionInfoRow.startTime,
|
||||||
"endTime": submissionInfoRow.endTime,
|
// "endTime": submissionInfoRow.endTime,
|
||||||
"user": {
|
// "user": {
|
||||||
"UUID": submissionInfoRow.userID,
|
// "UUID": submissionInfoRow.userID,
|
||||||
"username": submissionInfoRow.userName,
|
// "username": submissionInfoRow.userName,
|
||||||
"submissions": {
|
// "submissions": {
|
||||||
"total": submissionInfoRow.count,
|
// "total": submissionInfoRow.count,
|
||||||
"ignored": submissionInfoRow.disregarded,
|
// "ignored": submissionInfoRow.disregarded,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
"votes": {
|
// "votes": {
|
||||||
"before": voteData.row.votes,
|
// "before": voteData.row.votes,
|
||||||
"after": (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount),
|
// "after": (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount),
|
||||||
},
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send discord message
|
// Send discord message
|
||||||
@@ -142,8 +143,6 @@ async function sendWebhooks(voteData: VoteData) {
|
|||||||
Logger.error("\n");
|
Logger.error("\n");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {config} from '../config';
|
import {config} from '../config';
|
||||||
import {Logger} from '../utils/logger';
|
import {Logger} from '../utils/logger';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
import AbortController from "abort-controller";
|
||||||
|
|
||||||
function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string {
|
function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string {
|
||||||
if (isOwnSubmission) {
|
if (isOwnSubmission) {
|
||||||
@@ -30,7 +31,8 @@ function dispatchEvent(scope: string, data: any): void {
|
|||||||
let webhooks = config.webhooks;
|
let webhooks = config.webhooks;
|
||||||
if (webhooks === undefined || webhooks.length === 0) return;
|
if (webhooks === undefined || webhooks.length === 0) return;
|
||||||
Logger.debug("Dispatching webhooks");
|
Logger.debug("Dispatching webhooks");
|
||||||
webhooks.forEach(webhook => {
|
|
||||||
|
for (const webhook of webhooks) {
|
||||||
let webhookURL = webhook.url;
|
let webhookURL = webhook.url;
|
||||||
let authKey = webhook.key;
|
let authKey = webhook.key;
|
||||||
let scopes = webhook.scopes || [];
|
let scopes = webhook.scopes || [];
|
||||||
@@ -43,13 +45,13 @@ function dispatchEvent(scope: string, data: any): void {
|
|||||||
"Authorization": authKey,
|
"Authorization": authKey,
|
||||||
"Event-Type": scope, // Maybe change this in the future?
|
"Event-Type": scope, // Maybe change this in the future?
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
Logger.warn('Couldn\'t send webhook to ' + webhook.url);
|
Logger.warn('Couldn\'t send webhook to ' + webhook.url);
|
||||||
Logger.warn(err);
|
Logger.warn(err);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -10,43 +10,45 @@ _youTubeAPI.authenticate({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export class YouTubeAPI {
|
export class YouTubeAPI {
|
||||||
static listVideos(videoID: string, callback: (err: string | boolean, data: any) => void) {
|
static async listVideos(videoID: string, ignoreCache = false): Promise<{err: string | boolean, data?: any}> {
|
||||||
const part = 'contentDetails,snippet';
|
const part = 'contentDetails,snippet';
|
||||||
if (!videoID || videoID.length !== 11 || videoID.includes(".")) {
|
if (!videoID || videoID.length !== 11 || videoID.includes(".")) {
|
||||||
callback("Invalid video ID", undefined);
|
return { err: "Invalid video ID" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const redisKey = "youtube.video." + videoID;
|
const redisKey = "youtube.video." + videoID;
|
||||||
redis.get(redisKey, (getErr, result) => {
|
if (!ignoreCache) {
|
||||||
if (getErr || !result) {
|
const {err, reply} = await redis.getAsync(redisKey);
|
||||||
|
|
||||||
|
if (!err && reply) {
|
||||||
Logger.debug("redis: no cache for video information: " + videoID);
|
Logger.debug("redis: no cache for video information: " + videoID);
|
||||||
_youTubeAPI.videos.list({
|
|
||||||
|
return { err: err?.message, data: JSON.parse(reply) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ytErr, data } = await new Promise((resolve) => _youTubeAPI.videos.list({
|
||||||
part,
|
part,
|
||||||
id: videoID,
|
id: videoID,
|
||||||
}, (ytErr: boolean | string, { data }: any) => {
|
}, (ytErr: boolean | string, { data }: any) => resolve({ytErr, data})));
|
||||||
|
|
||||||
if (!ytErr) {
|
if (!ytErr) {
|
||||||
// Only set cache if data returned
|
// Only set cache if data returned
|
||||||
if (data.items.length > 0) {
|
if (data.items.length > 0) {
|
||||||
redis.set(redisKey, JSON.stringify(data), (setErr) => {
|
const { err: setErr } = await redis.setAsync(redisKey, JSON.stringify(data));
|
||||||
|
|
||||||
if (setErr) {
|
if (setErr) {
|
||||||
Logger.warn(setErr.message);
|
Logger.warn(setErr.message);
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("redis: video information cache set for: " + videoID);
|
Logger.debug("redis: video information cache set for: " + videoID);
|
||||||
}
|
}
|
||||||
callback(false, data); // don't fail
|
|
||||||
});
|
return { err: false, data }; // don't fail
|
||||||
} else {
|
} else {
|
||||||
callback(false, data); // don't fail
|
return { err: false, data }; // don't fail
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callback(ytErr, data);
|
return { err: ytErr, data };
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Logger.debug("redis: fetched video information from cache: " + videoID);
|
|
||||||
callback(getErr?.message, JSON.parse(result));
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,6 @@
|
|||||||
"vote.up",
|
"vote.up",
|
||||||
"vote.down"
|
"vote.down"
|
||||||
]
|
]
|
||||||
}, {
|
|
||||||
"url": "http://unresolvable.host:8081/FailedWebhook",
|
|
||||||
"key": "superSecretKey",
|
|
||||||
"scopes": [
|
|
||||||
"vote.up",
|
|
||||||
"vote.down"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "highlight"],
|
"categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "highlight"],
|
||||||
|
|||||||
@@ -9,21 +9,27 @@ YouTubeAPI.videos.list({
|
|||||||
|
|
||||||
|
|
||||||
export class YouTubeApiMock {
|
export class YouTubeApiMock {
|
||||||
static listVideos(videoID: string, callback: (ytErr: any, data: any) => void) {
|
static async listVideos(videoID: string, ignoreCache = false): Promise<{err: string | boolean, data?: any}> {
|
||||||
const obj = {
|
const obj = {
|
||||||
id: videoID
|
id: videoID
|
||||||
};
|
};
|
||||||
|
|
||||||
if (obj.id === "knownWrongID") {
|
if (obj.id === "knownWrongID") {
|
||||||
callback(undefined, {
|
return {
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
totalResults: 0,
|
totalResults: 0,
|
||||||
},
|
},
|
||||||
items: [],
|
items: [],
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (obj.id === "noDuration") {
|
if (obj.id === "noDuration") {
|
||||||
callback(undefined, {
|
return {
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
totalResults: 1,
|
totalResults: 1,
|
||||||
},
|
},
|
||||||
@@ -42,9 +48,12 @@ export class YouTubeApiMock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
}
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
callback(undefined, {
|
return {
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
totalResults: 1,
|
totalResults: 1,
|
||||||
},
|
},
|
||||||
@@ -63,7 +72,8 @@ export class YouTubeApiMock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user