add Sponsorblocks userscript
This commit is contained in:
parent
619fe68f6a
commit
3bfa50113c
192
.local/share/qutebrowser/greasemonkey/sb.user.js
Normal file
192
.local/share/qutebrowser/greasemonkey/sb.user.js
Normal 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()
|
Loading…
Reference in New Issue
Block a user