add Sponsorblocks userscript

This commit is contained in:
Alexander Bocken 2023-10-31 10:13:04 +01:00
parent 619fe68f6a
commit 3bfa50113c
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8

View File

@ -0,0 +1,192 @@
// ==UserScript==
// @name sb.js userscript
// @description SponsorBlock userscript
// @namespace mchang.name
// @homepage https://github.com/mchangrh/sb.js
// @icon https://mchangrh.github.io/sb.js/icon.png
// @version 1.3.2
// @license LGPL-3.0-or-later
// @match https://www.youtube.com/watch*
// @connect sponsor.ajay.app
// @grant none
// ==/UserScript==
/* START OF SETTINGS */
// https://wiki.sponsor.ajay.app/w/Types
const categories = [
"sponsor",
"selfpromo",
"interaction",
"intro",
"outro",
"preview",
"music_offtopic",
"exclusive_access",
"poi_highlight",
]
const actionTypes = ["skip", "mute", "full", "poi"]
const skipThreshold = [0.2, 1] // skip from between time-[0] and time+[1]
const serverEndpoint = "https://sponsor.ajay.app"
const skipTracking = true
const highlightKey = "Enter"
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
/* END OF SETTINGS */
/* sb.js - SponsorBlock for restrictive environments - by mchangrh
https://github.com/mchangrh/sb.js
Uses SponsorBlock data licensed used under CC BY-NC-SA 4.0 from https://sponsor.ajay.app/
LICENCED UNDER LGPL-3.0-or-later */
const VERSION = "1.3.2" // version constant
// initial setup
let video, videoID, muteEndTime
let skipSegments = new Map()
let muteSegments = new Map()
// functions
const getVideoID = () => new URL(window.location.href).searchParams.get("v")
function getJSON(url, callback) {
const xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.responseType = "json"
xhr.onload = () => xhr.status == 200 ? callback(null, xhr.response) : callback(xhr.status)
xhr.send()
}
const trackSkip = uuid => {
if (!skipTracking) return
const xhr = new XMLHttpRequest()
xhr.open("POST", `${serverEndpoint}/api/viewedVideoSponsorTime?UUID=${uuid}`)
xhr.send()
}
function fetch(videoID) {
const url = `${serverEndpoint}/api/skipSegments?videoID=${videoID}&categories=${JSON.stringify(categories)}&actionTypes=${JSON.stringify(actionTypes)}`
const convertSegment = s => [s.segment[0], { end: s.segment[1], uuid: s.UUID }]
getJSON(url, (err, data) => {
if (err) return console.error("[SB.js]", "error fetching segments", err)
data.forEach(s => {
if (s.actionType === "skip") skipSegments.set(...convertSegment(s))
else if (s.actionType === "mute") muteSegments.set(...convertSegment(s))
else if (s.actionType === "full") createVideoLabel(s)
else if (s.actionType === "poi") createPOILabel(s)
})
console.log("[SB.js] Loaded Segments")
})
}
function skipOrMute() {
const currentTime = video.currentTime
// if mute time is over, unmute video
if (video.muted && currentTime >= muteEndTime) {
video.muted = false
muteEndTime = 0
}
// check for any skip starts
const skipEnd = findEndTime(currentTime, skipSegments)
if (skipEnd) video.currentTime = skipEnd
// check for any mute starts
const muteEnd = findEndTime(currentTime, muteSegments)
if (muteEnd) {
video.muted = true
muteEndTime = muteEnd
}
}
function findEndTime(now, map) {
let endTime
for (const startTime of map.keys()) {
if (
now + skipThreshold[0] >= startTime &&
now - startTime <= skipThreshold[1]
) { // within threshold
const segment = map.get(startTime)
endTime = segment.end
trackSkip(segment.uuid)
map.delete(startTime) // only use segment once
for (const overlapStart of map.keys()) {
// check for overlap
if (endTime >= overlapStart && overlapStart >= now) {
// move to end of overlaps
const overSegment = map.get(overlapStart)
endTime = overSegment.end
trackSkip(overSegment.uuid)
map.delete(overlapStart)
}
}
return endTime // early return
}
}
return endTime
}
function createPOILabel(poiLabel) {
createVideoLabel(poiLabel, "poi")
// add binding
const poi_listener = e => {
if (e.key === highlightKey) {
video.currentTime = poiLabel.segment[1]
trackSkip(poiLabel.UUID)
// remove label
document.querySelector("#sbjs-label-poi").style.display = "none"
document.removeEventListener("keydown", poi_listener)
}
}
document.addEventListener("keydown", poi_listener)
}
function createVideoLabel(videoLabel, type = "full") {
// await title
const title = document.querySelector("#title h1, h1.title.ytd-video-primary-info-renderer")
if (!title) {
setTimeout(createVideoLabel, 200, videoLabel)
return
}
const category = videoLabel.category
const fvString = category => `The entire video is ${category} and is too tightly integrated to be able to seperate`
const styles = {
// fg, bg, hover text
sponsor: ["#0d0", "#111", fvString("sponsor")],
selfpromo: ["#ff0", "#111", fvString("selfpromo")],
exclusive_access: ["#085", "#fff", "This video showcases a product, service or location that they've received free or subsidized access to"],
poi_highlight: ["#f18", "#fff", `Press ${highlightKey} to skip to the highlight`],
}
const style = styles[category]
const label = document.createElement("span")
label.title = style[2]
label.innerText = category
label.id = `sbjs-label-${type}`
label.style = `color: ${style[1]}; background-color: ${style[0]}; display: flex; margin: 0 5px;`
// prepend to title
title.style = "display: flex;"
title.prepend(label)
}
const reset = () => {
video = undefined
videoID = undefined
muteEndTime = 0
skipSegments = new Map()
muteSegments = new Map()
}
function setup() {
if (videoID === getVideoID()) return // already running correctly
console.log(`@mchangrh/SB.js ${VERSION} Loaded`)
console.log(`Uses SponsorBlock data licensed used under CC BY-NC-SA 4.0 from https://sponsor.ajay.app/`)
if (document.querySelector("#previewbar")) // exit if previewbar exists
return console.log("[SB.js] Extension Present, Exiting")
video = document.querySelector("video")
videoID = getVideoID()
fetch(videoID)
if (!video) return console.log("[SB.js] no video")
video.addEventListener("timeupdate", skipOrMute) // add event listeners
}
// reset on page change
document.addEventListener("yt-navigate-start", reset)
// will start setup once event listener fired
document.addEventListener("yt-navigate-finish", setup)
setup()