From ca64a77ed0236fd9cfb4b40e450578a186638dc7 Mon Sep 17 00:00:00 2001 From: TC Date: Thu, 3 Jun 2021 21:47:00 +0200 Subject: [PATCH] Add SponsorBlock plugin --- config/defaults.js | 14 +++++- plugins/sponsorblock/back.js | 51 +++++++++++++++++++++ plugins/sponsorblock/front.js | 27 +++++++++++ plugins/sponsorblock/segments.js | 29 ++++++++++++ plugins/sponsorblock/tests/segments.test.js | 34 ++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 plugins/sponsorblock/back.js create mode 100644 plugins/sponsorblock/front.js create mode 100644 plugins/sponsorblock/segments.js create mode 100644 plugins/sponsorblock/tests/segments.test.js diff --git a/config/defaults.js b/config/defaults.js index 12f6a248..f0533241 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -63,7 +63,19 @@ const defaultConfig = { volumeDown: "Shift+PageDown" }, savedVolume: undefined //plugin save volume between session here - } + }, + sponsorblock: { + enabled: false, + apiURL: "https://sponsor.ajay.app", + categories: [ + "sponsor", + "intro", + "outro", + "interaction", + "selfpromo", + "music_offtopic", + ], + }, }, }; diff --git a/plugins/sponsorblock/back.js b/plugins/sponsorblock/back.js new file mode 100644 index 00000000..3c7eb5b4 --- /dev/null +++ b/plugins/sponsorblock/back.js @@ -0,0 +1,51 @@ +const fetch = require("node-fetch"); + +const defaultConfig = require("../../config/defaults"); +const registerCallback = require("../../providers/song-info"); +const { sortSegments } = require("./segments"); + +let videoID; + +module.exports = (win, options) => { + const { apiURL, categories } = { + ...defaultConfig.plugins.sponsorblock, + ...options, + }; + + registerCallback(async (info) => { + const newURL = info.url || win.webContents.getURL(); + const newVideoID = new URL(newURL).searchParams.get("v"); + + if (videoID !== newVideoID) { + videoID = newVideoID; + const segments = await fetchSegments(apiURL, categories); + win.webContents.send("sponsorblock-skip", segments); + } + }); +}; + +const fetchSegments = async (apiURL, categories) => { + const sponsorBlockURL = `${apiURL}/api/skipSegments?videoID=${videoID}&categories=${JSON.stringify( + categories + )}`; + try { + const resp = await fetch(sponsorBlockURL, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + }); + if (resp.status !== 200) { + return []; + } + const segments = await resp.json(); + const sortedSegments = sortSegments( + segments.map((submission) => submission.segment) + ); + + return sortedSegments; + } catch { + return []; + } +}; diff --git a/plugins/sponsorblock/front.js b/plugins/sponsorblock/front.js new file mode 100644 index 00000000..f9a9dcaa --- /dev/null +++ b/plugins/sponsorblock/front.js @@ -0,0 +1,27 @@ +const { ipcRenderer } = require("electron"); + +const is = require("electron-is"); + +const { ontimeupdate } = require("../../providers/video-element"); + +let currentSegments = []; + +module.exports = () => { + ipcRenderer.on("sponsorblock-skip", (_, segments) => { + currentSegments = segments; + }); + + ontimeupdate((videoElement) => { + if ( + currentSegments.length > 0 && + videoElement.currentTime >= currentSegments[0][0] && + videoElement.currentTime <= currentSegments[0][1] + ) { + videoElement.currentTime = currentSegments[0][1]; + const skipped = currentSegments.shift(); + if (is.dev()) { + console.log("SponsorBlock: skipping segment", skipped); + } + } + }); +}; diff --git a/plugins/sponsorblock/segments.js b/plugins/sponsorblock/segments.js new file mode 100644 index 00000000..c12a9e88 --- /dev/null +++ b/plugins/sponsorblock/segments.js @@ -0,0 +1,29 @@ +// Segments are an array [ [start, end], … ] +module.exports.sortSegments = (segments) => { + segments.sort((segment1, segment2) => + segment1[0] === segment2[0] + ? segment1[1] - segment2[1] + : segment1[0] - segment2[0] + ); + + const compiledSegments = []; + let currentSegment; + + segments.forEach((segment) => { + if (!currentSegment) { + currentSegment = segment; + return; + } + + if (currentSegment[1] < segment[0]) { + compiledSegments.push(currentSegment); + currentSegment = segment; + return; + } + + currentSegment[1] = Math.max(currentSegment[1], segment[1]); + }); + compiledSegments.push(currentSegment); + + return compiledSegments; +}; diff --git a/plugins/sponsorblock/tests/segments.test.js b/plugins/sponsorblock/tests/segments.test.js new file mode 100644 index 00000000..dbc3d4b0 --- /dev/null +++ b/plugins/sponsorblock/tests/segments.test.js @@ -0,0 +1,34 @@ +const { sortSegments } = require("../segments"); + +test("Segment sorting", () => { + expect( + sortSegments([ + [0, 3], + [7, 8], + [5, 6], + ]) + ).toEqual([ + [0, 3], + [5, 6], + [7, 8], + ]); + + expect( + sortSegments([ + [0, 5], + [6, 8], + [4, 6], + ]) + ).toEqual([[0, 8]]); + + expect( + sortSegments([ + [0, 6], + [7, 8], + [4, 6], + ]) + ).toEqual([ + [0, 6], + [7, 8], + ]); +});