Skips annoying intros, sponsors and other YouTube segments using the SponsorBlock API. Fixed config page and safer runtime.
// ==UserScript==
// @name Simple Sponsor Skipper Fixed
// @author mthsk, fixed version
// @homepage https://codeberg.org/mthsk/userscripts/src/branch/master/simple-sponsor-skipper
// @match *://m.youtube.com/*
// @match *://youtu.be/*
// @match *://www.youtube.com/*
// @match *://www.youtube-nocookie.com/embed/*
// @match *://odysee.com/*
// @match *://yt.artemislena.eu/*
// @match *://tube.cadence.moe/*
// @match *://y.com.sb/*
// @match *://invidious.esmailelbob.xyz/*
// @match *://invidious.flokinet.to/*
// @match *://inv.frail.com.br/*
// @match *://invidious.garudalinux.org/*
// @match *://invidious.kavin.rocks/*
// @match *://inv.nadeko.net/*
// @match *://invidious.namazso.eu/*
// @match *://iv.nboeck.de/*
// @match *://invidious.nerdvpn.de/*
// @match *://youtube.owacon.moe/*
// @match *://inv.pistasjis.net/*
// @match *://invidious.projectsegfau.lt/*
// @match *://inv.bp.projectsegfau.lt/*
// @match *://inv.in.projectsegfau.lt/*
// @match *://inv.us.projectsegfau.lt/*
// @match *://vid.puffyan.us/*
// @match *://invidious.sethforprivacy.com/*
// @match *://invidious.slipfox.xyz/*
// @match *://invidious.snopyta.org/*
// @match *://inv.vern.cc/*
// @match *://invidious.weblibre.org/*
// @match *://youchu.be/*
// @match *://yewtu.be/*
// @grant GM.addElement
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.notification
// @grant GM.openInTab
// @grant GM.registerMenuCommand
// @grant GM.xmlHttpRequest
// @allFrames true
// @connect sponsor.ajay.app
// @connect sponsorblock.kavin.rocks
// @connect sponsorblock.gleesh.net
// @connect sb.theairplan.com
// @connect *
// @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @run-at document-start
// @version 2026.05-fixed
// @license AGPL-3.0-or-later
// @description Skips annoying intros, sponsors and other YouTube segments using the SponsorBlock API. Fixed config page and safer runtime.
// @namespace https://greasyfork.org/users/751327
// ==/UserScript==
(async function () {
"use strict";
const SCRIPT_NAME = "Simple Sponsor Skipper";
const DEFAULT_SETTINGS = {
categories: [
"preview",
"sponsor",
"outro",
"music_offtopic",
"selfpromo",
"poi_highlight",
"interaction",
"intro"
],
upvotes: -2,
notifications: true,
disable_hashing: false,
instance: "sponsor.ajay.app",
darkmode: -1
};
const CATEGORY_OPTIONS = [
{ id: "sponsor", label: "Skip sponsor segments" },
{ id: "intro", label: "Skip intro segments" },
{ id: "outro", label: "Skip outro segments" },
{ id: "interaction", label: "Skip interaction reminder segments" },
{ id: "selfpromo", label: "Skip self-promotion segments" },
{ id: "preview", label: "Skip preview segments" },
{ id: "music_offtopic", label: "Skip non-music segments in music videos" },
{ id: "filler", label: "Skip filler segments, very aggressive" }
];
const INSTANCE_OPTIONS = [
{ value: "sponsor.ajay.app", label: "sponsor.ajay.app (Official)" },
{ value: "sponsorblock.kavin.rocks", label: "sponsorblock.kavin.rocks" },
{ value: "sponsorblock.gleesh.net", label: "sponsorblock.gleesh.net" },
{ value: "sb.theairplan.com", label: "sb.theairplan.com" }
];
const PLAYER_SELECTOR = [
"#movie_player video",
"video#player_html5_api",
"video#player",
"video#video",
"video#vjs_video_3_html5_api",
"video"
].join(", ");
let s3settings = null;
let activeVideoId = "";
let activeCleanup = null;
let navigationTimer = null;
if (typeof GM.registerMenuCommand === "undefined") {
GM.registerMenuCommand = function () {};
}
if (typeof GM.notification === "undefined") {
GM.notification = function (notification) {
console.log(`${SCRIPT_NAME}: ${notification.title || ""} ${notification.text || ""}`);
};
}
function log(message) {
console.log(`${new Date().toTimeString().split(" ")[0]} - ${SCRIPT_NAME}: ${message}`);
}
function isIntegerLike(value) {
return !Number.isNaN(value) &&
parseInt(Number(value), 10) == value &&
!Number.isNaN(parseInt(value, 10));
}
function normalizeCategories(categories, notifications) {
if (isIntegerLike(categories)) {
const bitmask = Number(categories);
const converted = [];
if (bitmask & 2) converted.push("intro");
if (bitmask & 4) converted.push("outro");
if (bitmask & 8) converted.push("interaction");
if (bitmask & 16) converted.push("selfpromo");
if (bitmask & 32) converted.push("preview");
if (bitmask & 64) converted.push("music_offtopic");
if (bitmask & 128) converted.push("filler");
if ((bitmask & 1) || converted.length === 0) converted.push("sponsor");
if (notifications) converted.push("poi_highlight");
return [...new Set(converted)];
}
if (!Array.isArray(categories)) {
return [...DEFAULT_SETTINGS.categories];
}
const valid = new Set([
"sponsor",
"intro",
"outro",
"interaction",
"selfpromo",
"preview",
"music_offtopic",
"filler",
"poi_highlight"
]);
const cleaned = categories.filter(category => valid.has(category));
if (!cleaned.length) {
cleaned.push("sponsor");
}
return [...new Set(cleaned)];
}
function sanitizeInstance(value) {
let instance = String(value || DEFAULT_SETTINGS.instance).trim();
instance = instance.replace(/\s*\(Official\)\s*/gi, "");
instance = instance.replace(/^https?:\/\//i, "");
instance = instance.split("/")[0];
instance = instance.split("?")[0];
instance = instance.split("#")[0];
instance = instance.trim();
if (!/^[a-z0-9.-]+(?::[0-9]+)?$/i.test(instance)) {
return DEFAULT_SETTINGS.instance;
}
return instance || DEFAULT_SETTINGS.instance;
}
async function loadSettings() {
let stored = await GM.getValue("s3settings");
const isPaleMoon =
navigator.userAgent.toLowerCase().includes("pale moon") ||
navigator.userAgent.toLowerCase().includes("mypal") ||
navigator.userAgent.toLowerCase().includes("male poon");
if (!stored || typeof stored !== "object" || Object.keys(stored).length === 0) {
stored = { ...DEFAULT_SETTINGS };
if (isPaleMoon) stored.disable_hashing = true;
await GM.setValue("s3settings", stored);
log("Default settings saved.");
}
const normalized = {
...DEFAULT_SETTINGS,
...stored,
categories: normalizeCategories(stored.categories, stored.notifications),
upvotes: Number.isFinite(Number(stored.upvotes)) ? parseInt(stored.upvotes, 10) : DEFAULT_SETTINGS.upvotes,
notifications: Boolean(stored.notifications),
disable_hashing: Boolean(stored.disable_hashing),
instance: sanitizeInstance(stored.instance),
darkmode: [-1, 0, 1].includes(parseInt(stored.darkmode, 10)) ? parseInt(stored.darkmode, 10) : -1
};
await GM.setValue("s3settings", normalized);
return normalized;
}
function openConfigUrl() {
const url = new URL(location.href);
url.hostname = url.hostname.replace("youtube-nocookie.com", "youtube.com");
if (url.pathname.startsWith("/embed/")) {
const id = url.pathname.replace("/embed/", "").split("/")[0];
url.pathname = "/watch";
url.search = `?v=${encodeURIComponent(id)}`;
}
if (url.pathname.startsWith("/v/")) {
const id = url.pathname.replace("/v/", "").split("/")[0];
url.pathname = "/watch";
url.search = `?v=${encodeURIComponent(id)}`;
}
url.hash = "s3config";
return url.toString();
}
function isConfigPage() {
return location.hash.toLowerCase() === "#s3config";
}
function onReady(callback) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", callback, { once: true });
} else {
callback();
}
}
function add(parent, tag, attrs = {}) {
return GM.addElement(parent, tag, attrs);
}
function applyTheme(value) {
const dark =
value === 1 ||
(value === -1 &&
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
document.body.classList.toggle("dark-theme", dark);
}
function renderConfig() {
const docHtml = document.createElement("html");
const head = add(docHtml, "head");
add(head, "meta", { charset: "utf-8" });
add(head, "title", { textContent: `${SCRIPT_NAME} Configuration` });
add(head, "style", {
textContent: `
:root {
color-scheme: light dark;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body {
margin: 0;
min-height: 100vh;
background: #f6f7f9;
color: #111827;
display: grid;
place-items: center;
padding: 24px;
box-sizing: border-box;
}
body.dark-theme {
background: #0b0f19;
color: #f9fafb;
}
main {
width: min(720px, 100%);
background: white;
border-radius: 20px;
padding: 28px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12);
box-sizing: border-box;
}
body.dark-theme main {
background: #111827;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}
h1 {
margin: 0 0 6px;
font-size: 28px;
}
p {
margin: 0 0 24px;
color: #6b7280;
}
body.dark-theme p {
color: #9ca3af;
}
form {
display: grid;
gap: 18px;
}
fieldset {
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 16px;
display: grid;
gap: 12px;
}
body.dark-theme fieldset {
border-color: #374151;
}
legend {
padding: 0 8px;
font-weight: 700;
}
.row {
display: flex;
align-items: center;
gap: 10px;
}
.field {
display: grid;
gap: 6px;
}
label {
cursor: pointer;
}
input[type="text"],
input[type="number"],
select {
width: 100%;
box-sizing: border-box;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 10px;
background: white;
color: #111827;
font: inherit;
}
body.dark-theme input[type="text"],
body.dark-theme input[type="number"],
body.dark-theme select {
background: #030712;
border-color: #374151;
color: #f9fafb;
}
.buttons {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
button {
border: 0;
border-radius: 999px;
padding: 10px 16px;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button.primary {
background: #111827;
color: white;
}
body.dark-theme button.primary {
background: #f9fafb;
color: #111827;
}
button.secondary {
background: #e5e7eb;
color: #111827;
}
body.dark-theme button.secondary {
background: #374151;
color: #f9fafb;
}
.hint {
font-size: 13px;
color: #6b7280;
margin: 0;
}
body.dark-theme .hint {
color: #9ca3af;
}
`
});
const body = add(docHtml, "body");
const main = add(body, "main");
add(main, "h1", { textContent: SCRIPT_NAME });
add(main, "p", { textContent: "Configure which SponsorBlock segments should be skipped." });
const form = add(main, "form");
const categoryFieldset = add(form, "fieldset");
add(categoryFieldset, "legend", { textContent: "Segments" });
for (const option of CATEGORY_OPTIONS) {
const row = add(categoryFieldset, "div", { className: "row" });
add(row, "input", {
type: "checkbox",
id: option.id
});
add(row, "label", {
for: option.id,
textContent: option.label
});
}
const behaviorFieldset = add(form, "fieldset");
add(behaviorFieldset, "legend", { textContent: "Behavior" });
const upvotesField = add(behaviorFieldset, "div", { className: "field" });
add(upvotesField, "label", {
for: "upvotes",
textContent: "Minimum segment votes"
});
add(upvotesField, "input", {
type: "number",
id: "upvotes",
step: "1"
});
add(upvotesField, "p", {
className: "hint",
textContent: "-2 is permissive. Higher values skip only more trusted segments."
});
const notificationsRow = add(behaviorFieldset, "div", { className: "row" });
add(notificationsRow, "input", {
type: "checkbox",
id: "notifications"
});
add(notificationsRow, "label", {
for: "notifications",
textContent: "Enable desktop notifications and point-of-interest hints"
});
const hashingRow = add(behaviorFieldset, "div", { className: "row" });
add(hashingRow, "input", {
type: "checkbox",
id: "disable_hashing"
});
add(hashingRow, "label", {
for: "disable_hashing",
textContent: "Disable video ID hashing, only needed for old browser compatibility"
});
const instanceField = add(behaviorFieldset, "div", { className: "field" });
add(instanceField, "label", {
for: "instance",
textContent: "SponsorBlock database instance"
});
add(instanceField, "input", {
id: "instance",
type: "text",
list: "instances",
autocomplete: "off",
spellcheck: "false"
});
const dataList = add(instanceField, "datalist", { id: "instances" });
for (const instance of INSTANCE_OPTIONS) {
add(dataList, "option", {
value: instance.value,
label: instance.label
});
}
const themeField = add(behaviorFieldset, "div", { className: "field" });
add(themeField, "label", {
for: "darkmode",
textContent: "Theme"
});
const themeSelect = add(themeField, "select", { id: "darkmode" });
add(themeSelect, "option", { value: "-1", textContent: "Auto" });
add(themeSelect, "option", { value: "0", textContent: "Light" });
add(themeSelect, "option", { value: "1", textContent: "Dark" });
const buttons = add(form, "div", { className: "buttons" });
add(buttons, "button", {
type: "button",
id: "btnsave",
className: "primary",
textContent: "Save settings"
});
add(buttons, "button", {
type: "button",
id: "btnclose",
className: "secondary",
textContent: "Close"
});
add(buttons, "button", {
type: "button",
id: "btnreset",
className: "secondary",
textContent: "Reset defaults"
});
document.documentElement.replaceWith(docHtml);
document.title = `${SCRIPT_NAME} Configuration`;
for (const option of CATEGORY_OPTIONS) {
document.getElementById(option.id).checked = s3settings.categories.includes(option.id);
}
document.getElementById("upvotes").value = String(s3settings.upvotes);
document.getElementById("notifications").checked = Boolean(s3settings.notifications);
document.getElementById("disable_hashing").checked = Boolean(s3settings.disable_hashing);
document.getElementById("instance").value = sanitizeInstance(s3settings.instance);
document.getElementById("darkmode").value = String(s3settings.darkmode);
applyTheme(s3settings.darkmode);
document.getElementById("darkmode").addEventListener("change", event => {
applyTheme(parseInt(event.target.value, 10));
});
document.getElementById("btnsave").addEventListener("click", saveConfig);
document.getElementById("btnreset").addEventListener("click", resetConfig);
document.getElementById("btnclose").addEventListener("click", closeConfig);
}
async function saveConfig() {
const categories = [];
for (const option of CATEGORY_OPTIONS) {
if (document.getElementById(option.id).checked) {
categories.push(option.id);
}
}
if (!categories.length) {
categories.push("sponsor");
}
const notifications = document.getElementById("notifications").checked;
if (notifications) {
categories.push("poi_highlight");
}
s3settings = {
categories: [...new Set(categories)],
upvotes: parseInt(document.getElementById("upvotes").value, 10),
notifications,
disable_hashing: document.getElementById("disable_hashing").checked,
instance: sanitizeInstance(document.getElementById("instance").value),
darkmode: parseInt(document.getElementById("darkmode").value, 10)
};
if (!Number.isFinite(s3settings.upvotes)) {
s3settings.upvotes = DEFAULT_SETTINGS.upvotes;
}
await GM.setValue("s3settings", s3settings);
const button = document.getElementById("btnsave");
button.textContent = "Saved";
button.disabled = true;
setTimeout(() => {
button.textContent = "Save settings";
button.disabled = false;
}, 1500);
log("Settings saved.");
}
async function resetConfig() {
s3settings = { ...DEFAULT_SETTINGS };
await GM.setValue("s3settings", s3settings);
renderConfig();
log("Settings reset.");
}
function closeConfig() {
const url = new URL(location.href);
url.hash = "";
location.replace(url.toString());
}
function durationString(seconds) {
const safeSeconds = Math.max(0, Math.floor(Number(seconds) || 0));
const hours = Math.floor(safeSeconds / 3600);
const minutes = Math.floor((safeSeconds % 3600) / 60);
const secs = safeSeconds % 60;
if (hours > 0) {
return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
}
return `${minutes}:${String(secs).padStart(2, "0")}`;
}
function shuffleCopy(array) {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
return Array
.from(new Uint8Array(hashBuffer))
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
}
function requestJson(url) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url,
headers: {
Accept: "application/json"
},
timeout: 15000,
onload(response) {
if (response.status >= 200 && response.status < 300) {
resolve(response.responseText);
} else if (response.status === 404) {
resolve("[]");
} else {
reject(new Error(`HTTP ${response.status}`));
}
},
onerror() {
reject(new Error("Network error"));
},
ontimeout() {
reject(new Error("Request timed out"));
}
});
});
}
async function fetchSegments(videoId) {
const instance = sanitizeInstance(s3settings.instance);
const categories = shuffleCopy(s3settings.categories);
const encodedCategories = encodeURIComponent(JSON.stringify(categories));
let url;
if (s3settings.disable_hashing) {
url = `https://${instance}/api/skipSegments?videoID=${encodeURIComponent(videoId)}&categories=${encodedCategories}`;
const text = await requestJson(url);
const segments = JSON.parse(text);
return [{
videoID: videoId,
segments: Array.isArray(segments) ? segments : []
}];
}
const hash = await sha256(videoId);
url = `https://${instance}/api/skipSegments/${hash.substring(0, 4)}?categories=${encodedCategories}`;
const text = await requestJson(url);
const parsed = JSON.parse(text);
return Array.isArray(parsed) ? parsed : [];
}
function getVotes(segment) {
return Number(segment.votes ?? segment.upvotes ?? 0);
}
function processSegments(segments) {
if (!Array.isArray(segments)) {
return {
skipSegments: [],
highlightTime: null,
beforeCount: 0
};
}
const sorted = [...segments].sort((a, b) => {
const aStart = Number(a.segment?.[0] ?? 0);
const bStart = Number(b.segment?.[0] ?? 0);
return aStart - bStart;
});
const skipSegments = [];
let highlight = null;
let highlightVotes = s3settings.upvotes - 1;
for (const segment of sorted) {
if (!Array.isArray(segment.segment) || segment.segment.length < 2) {
continue;
}
const start = Number(segment.segment[0]);
const end = Number(segment.segment[1]);
const votes = getVotes(segment);
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) {
continue;
}
if (segment.category === "poi_highlight") {
if (votes > highlightVotes) {
highlight = segment;
highlightVotes = votes;
}
continue;
}
if (votes < s3settings.upvotes) {
continue;
}
const last = skipSegments[skipSegments.length - 1];
if (last && last.segment[1] >= start) {
if (end > last.segment[1]) {
last.segment[1] = end;
last.category = last.category === segment.category ? last.category : "combined";
}
continue;
}
skipSegments.push({
...segment,
segment: [start, end]
});
}
return {
skipSegments,
highlightTime: highlight ? Number(highlight.segment[0]) : null,
beforeCount: segments.length
};
}
function getVideoIdFromUrl() {
const url = new URL(location.href);
const host = url.hostname;
if (host === "youtu.be") {
return url.pathname.slice(1).split("/")[0] || "";
}
if (url.searchParams.has("v")) {
return url.searchParams.get("v") || "";
}
if (url.pathname.startsWith("/embed/")) {
return url.pathname.replace("/embed/", "").split("/")[0] || "";
}
if (url.pathname.startsWith("/v/")) {
return url.pathname.replace("/v/", "").split("/")[0] || "";
}
return "";
}
function waitForPlayer(timeoutMs = 12000) {
return new Promise(resolve => {
const start = Date.now();
const timer = setInterval(() => {
const player = document.querySelector(PLAYER_SELECTOR);
if (player && player.readyState >= 1) {
clearInterval(timer);
resolve(player);
return;
}
if (Date.now() - start > timeoutMs) {
clearInterval(timer);
resolve(null);
}
}, 100);
});
}
function getFavicon() {
const icon = document.head?.querySelector("link[rel='icon'][href], link[rel='shortcut icon'][href]");
return icon ? icon.href : undefined;
}
function notify(notification) {
if (!s3settings.notifications || window.self !== window.top) {
return;
}
try {
GM.notification({
silent: true,
timeout: 5000,
...notification
});
} catch (error) {
log(`Notification failed: ${error.message}`);
}
}
async function go(videoId) {
if (!videoId || videoId === activeVideoId) {
return;
}
activeVideoId = videoId;
if (typeof activeCleanup === "function") {
activeCleanup();
activeCleanup = null;
}
log(`New video ID: ${videoId}`);
let apiResponse;
try {
apiResponse = await fetchSegments(videoId);
} catch (error) {
log(`SponsorBlock request failed: ${error.message}`);
return;
}
const matching = apiResponse.find(item => item.videoID === videoId);
if (!matching) {
log("No matching SponsorBlock result found.");
return;
}
const {
skipSegments,
highlightTime,
beforeCount
} = processSegments(matching.segments);
const player = await waitForPlayer();
if (!player) {
log("No video player found.");
return;
}
const favicon = getFavicon();
const showHighlightNotification = () => {
if (highlightTime !== null && player.currentTime < highlightTime) {
notify({
title: "Point of interest found",
text: `This video has a highlight at ${durationString(highlightTime)}.\nClick to skip to it.\n\u00AD\n${document.title} (${videoId})`,
image: favicon,
onclick: () => {
player.currentTime = highlightTime;
}
});
}
};
if (!skipSegments.length) {
showHighlightNotification();
const playHandler = showHighlightNotification;
player.addEventListener("play", playHandler);
activeCleanup = () => {
player.removeEventListener("play", playHandler);
};
return;
}
let newDuration = Number(skipSegments[0].videoDuration || player.duration || 0);
if (Number.isFinite(newDuration) && newDuration > 0) {
for (const segment of skipSegments) {
newDuration -= segment.segment[1] - segment.segment[0];
}
}
notify({
title: "Skippable segments found",
text:
`Received ${beforeCount} segment${beforeCount === 1 ? "" : "s"}, ` +
`${skipSegments.length} active after processing.` +
(Number.isFinite(newDuration) && newDuration > 0 ? `\nDuration after skips: ${durationString(newDuration)}` : "") +
(highlightTime !== null ? `\nHighlight: ${durationString(highlightTime)}` : "") +
`\n\u00AD\n${document.title} (${videoId})`,
image: favicon,
onclick: highlightTime !== null
? () => {
player.currentTime = highlightTime;
}
: undefined
});
let index = 0;
let previousTime = -1;
const timeUpdateHandler = () => {
const currentId = getVideoIdFromUrl();
if (currentId && currentId !== videoId) {
if (typeof activeCleanup === "function") {
activeCleanup();
activeCleanup = null;
}
return;
}
const currentTime = player.currentTime;
if (currentTime < previousTime) {
index = skipSegments.findIndex(segment => currentTime < segment.segment[1]);
if (index < 0) index = skipSegments.length;
}
while (index < skipSegments.length && currentTime >= skipSegments[index].segment[1]) {
index++;
}
if (
!player.paused &&
index < skipSegments.length &&
currentTime >= skipSegments[index].segment[0] &&
currentTime < skipSegments[index].segment[1]
) {
const segment = skipSegments[index];
player.currentTime = segment.segment[1];
notify({
title: `Skipped ${segment.category.replace("music_offtopic", "non-music").replace("selfpromo", "self-promotion")}`,
text: `Segment ${index + 1} of ${skipSegments.length}\n\u00AD\n${document.title} (${videoId})`,
image: favicon
});
log(`Skipped ${segment.category} from ${segment.segment[0]} to ${segment.segment[1]}`);
index++;
}
previousTime = player.currentTime;
};
const playHandler = showHighlightNotification;
player.addEventListener("timeupdate", timeUpdateHandler);
player.addEventListener("play", playHandler);
activeCleanup = () => {
player.removeEventListener("timeupdate", timeUpdateHandler);
player.removeEventListener("play", playHandler);
};
}
function checkCurrentVideoSoon() {
clearTimeout(navigationTimer);
navigationTimer = setTimeout(() => {
const videoId = getVideoIdFromUrl();
if (!videoId) {
activeVideoId = "";
if (typeof activeCleanup === "function") {
activeCleanup();
activeCleanup = null;
}
return;
}
go(videoId);
}, 250);
}
function startWatchers() {
checkCurrentVideoSoon();
window.addEventListener("yt-navigate-finish", checkCurrentVideoSoon, true);
window.addEventListener("yt-page-data-updated", checkCurrentVideoSoon, true);
window.addEventListener("popstate", checkCurrentVideoSoon, true);
window.addEventListener("hashchange", () => {
if (isConfigPage()) {
renderConfig();
} else {
checkCurrentVideoSoon();
}
}, true);
onReady(() => {
if (!document.body) return;
const observer = new MutationObserver(() => {
checkCurrentVideoSoon();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
s3settings = await loadSettings();
if (window.self === window.top) {
GM.registerMenuCommand("Configuration", () => {
location.href = openConfigUrl();
setTimeout(() => location.reload(), 50);
});
}
if (isConfigPage() && window.self === window.top) {
onReady(renderConfig);
return;
}
startWatchers();
})();