Skip to content

Instantly share code, notes, and snippets.

@TheJzoli
Last active March 29, 2023 17:03
Show Gist options
  • Save TheJzoli/dc4ac8895f382328ca3c29e912e178bb to your computer and use it in GitHub Desktop.
Save TheJzoli/dc4ac8895f382328ca3c29e912e178bb to your computer and use it in GitHub Desktop.
Updated userscript to work
// ==UserScript==
// @name SponsorBlock Vip Interface
// @namespace sb.ltn.fi.VipInterface
// @version 1.0.12
// @description Adds option to lock categories and vote on segments.
// @author Deedit
// @match https://sb.ltn.fi/*
// @connect sponsor.ajay.app
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
// Thanks Hanabishi, blabdude and TheJzoli for the help and Nanobyte for having the acces to the category types in his colorcode userscript already done
(async function() {
'use strict';
//consts
const userID = await GM_getValue("privateUserID", "");
const categoryListUI = ['Sponsor', 'Promo', 'Interaction', 'Intro', 'Outro', 'Preview', 'Filler', 'Music', 'POI'];
const categoryListAPI = ['sponsor', 'selfpromo', 'interaction', 'intro', 'outro', 'preview', 'filler', 'music_offtopic', 'poi_highlight'];
let UUIDsaver = '';
// create UI
createDropDownMenu();
categoryChange();
document.addEventListener('newSegments', () => {
categoryChange();
});
// Stuff for changing categories
function categoryChange(){
createCategoryButtons();
createCategorySelectorPopup();
}
function createCategoryButtons() {
[...document.querySelectorAll('table')].forEach(table => {
const headers = [...table.querySelectorAll('thead th')].map(item => item.textContent.trim());
if (headers.includes('Start') && headers.includes('End')) {
const columnIndex = headers.indexOf('Category');
const rows = [...table.querySelectorAll('tbody tr')];
rows.forEach((row, i) => {
// If this update is due to a newSegment event, we need to check for previous elements
const targetEl = row.children[columnIndex].lastElementChild;
if (targetEl && targetEl.hasAttribute('id')) {
targetEl.setAttribute('id', `lockChangeCategoryButton${i}`);
return;
}
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', `lockChangeCategoryButton${i}`);
buttonEl.width = "100%";
buttonEl.onclick = (event) => {
buttonEl.blur();
const oldUUIDsaver = UUIDsaver;
UUIDsaver = row.children[headers.indexOf('UUID')].firstElementChild.value;
// Only disable the popup, when the same UUID was pressed twice
if (oldUUIDsaver == UUIDsaver) {
const popup = document.getElementById('changeCategorySelector');
popup.style.display = (popup.style.display) ? null : 'none';
popup.style.left = (event.clientX + 90).toString() + 'px';
moveElementIntoViewport(popup);
} else {
const popup = document.getElementById('changeCategorySelector');
popup.style.left = (event.clientX + 90).toString() + 'px';
popup.style.display = null;
moveElementIntoViewport(popup);
}
}
//for normal use
if (targetEl === null){
const categoryEl = row.children[columnIndex];
const name = categoryEl.textContent.trim();
categoryEl.textContent = '';
buttonEl.innerText = name;
categoryEl.appendChild(buttonEl);
buttonEl.style.color = 'white';
} else { // So that it works properly with colored categories
targetEl.parentNode.insertBefore(buttonEl, targetEl);
buttonEl.appendChild(targetEl);
}
});
}});
}
function createCategorySelectorPopup() {
const categorySelectorEl = document.createElement('div');
//This is just, so that it is accessible via its ID
document.body.appendChild(categorySelectorEl);
categorySelectorEl.classList.add('bg-light');
categorySelectorEl.style = 'position:fixed;z-index:999;top:30%;left:44%;padding:10px;border-radius:5px;box-shadow:4px 5px 5px black;white-space:nowrap;display:none;';
categorySelectorEl.setAttribute('id', 'changeCategorySelector');
// Category Buttons
const categoryListUICopy = [...categoryListUI];
categoryListUICopy.splice(categoryListUI.indexOf('POI'), 1);
categoryListUICopy.forEach((name, i) => {
const labelEl = document.createTextNode(name);
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', `lockSelectCategoryButton${i}`);
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.style.padding = '0';
buttonEl.onclick = () => {
buttonEl.blur();
categorySelectorEl.style.display = 'none';
const categoryListAPICopy = [...categoryListAPI];
categoryListAPICopy.splice(categoryListAPI.indexOf('poi_highlight'), 1);
setCategory(UUIDsaver, categoryListAPICopy[i]);
}
buttonEl.appendChild(labelEl);
categorySelectorEl.appendChild(buttonEl);
categorySelectorEl.appendChild(document.createElement('br'));
});
// Close button
const labelCloseEl = document.createTextNode('❌');
const buttonCloseEl = document.createElement('BUTTON');
buttonCloseEl.className = 'btn';
buttonCloseEl.style.backgroundColor = 'black';
buttonCloseEl.style.width = '240px';
buttonCloseEl.style.margin = '2px 2px';
buttonCloseEl.style.padding = '0';
buttonCloseEl.setAttribute('id', `lockSelectCategoryCloseButton`);
buttonCloseEl.onclick = () => {
buttonCloseEl.blur();
categorySelectorEl.style.display = 'none';
}
buttonCloseEl.appendChild(labelCloseEl);
categorySelectorEl.appendChild(buttonCloseEl);
categorySelectorEl.appendChild(document.createElement('br'));
// Vote buttons
const voteNames = ['Upvote','Downvote','Undo vote'];
const voteCodes = [1,0,20];
voteNames.forEach((name, i) => {
const labelEl = document.createTextNode(name);
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', `lockSelectCategoryButton${i}`);
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.style.padding = '0';
buttonEl.onclick = () => {
buttonEl.blur();
categorySelectorEl.style.display = 'none';
voteOnSegment(UUIDsaver, voteCodes[i]);
}
buttonEl.appendChild(labelEl);
categorySelectorEl.appendChild(buttonEl);
categorySelectorEl.appendChild(document.createElement('br'));
});
window.addEventListener('resize', () => { moveElementIntoViewport(categorySelectorEl) });
}
function setCategory(UUID, category){
//console.log('https://sponsor.ajay.app/api/voteOnSponsorTime?UUID=' + UUID + '&userID=' + userID + '&category=' + category);
document.body.style.cursor = "progress";
GM_xmlhttpRequest({
method: 'GET',
url: 'https://sponsor.ajay.app/api/voteOnSponsorTime?UUID=' + UUID + '&userID=' + userID + '&category=' + category,
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
if (!res) {
window.alert("Response object was undefined for some reason. Category was most likely not changed!");
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Could not change category! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
window.alert("Unknown error occurred and category wasn't changed!");
},
ontimeout: () => {
document.body.style.cursor = null;
window.alert("Request timed out and category may not have been changed!");
}
});
}
function voteOnSegment(UUID, voteType){
document.body.style.cursor = "progress";
GM_xmlhttpRequest({
method: 'GET',
url: 'https://sponsor.ajay.app/api/voteOnSponsorTime?UUID=' + UUID + '&userID=' + userID + '&type=' + voteType,
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
if (!res) {
window.alert("Response object was undefined for some reason. Vote most likely didn't go through!");
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Vote didn't go through! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
window.alert("Unknown error occurred and segment wasn't voted!");
},
ontimeout: () => {
document.body.style.cursor = null;
window.alert("Request timed out and segment may not have been voted!");
}
});
}
// Stuff for categorie locks/ purge / clear cache
function createDropDownMenu() {
// Create Lock-symbol button and "dropdown"-menu.
const navBar = [...document.getElementsByClassName('navbar')][0].lastElementChild;
const lockButtonDiv = document.createElement('div');
lockButtonDiv.style = 'position:relative;';
const lockBtn = document.createElement('button');
lockBtn.style = 'font-size:1.5em;background-color:transparent;border-style:none;';
lockBtn.innerText = '🔒';
const lockMenuEl = document.createElement('div');
lockMenuEl.classList.add('bg-light');
lockMenuEl.style = 'position:absolute;z-index:999;top:51px;left:50%;transform:translateX(-50%);padding:10px;border-radius:5px;box-shadow:4px 5px 5px black;white-space:nowrap;display:none;';
lockBtn.onclick = () => {
//if (lockMenuEl.style.display === 'none') getReasons();
// null is visible, none is invisible
const menuVisibility = (lockMenuEl.style.display) ? null : 'none';
lockMenuEl.style.display = menuVisibility;
lockBtn.innerText = menuVisibility ? '🔒' : '🔓';
lockBtn.blur();
moveElementIntoViewport(lockMenuEl);
}
lockButtonDiv.appendChild(lockBtn);
lockButtonDiv.appendChild(lockMenuEl);
navBar.insertBefore(lockButtonDiv, navBar.lastElementChild);
// Create Dropdown elements
const lockCategorieRegex = new RegExp('https:\/\/sb\.ltn\.fi\/video\/*');
if (lockCategorieRegex.test(document.URL)) {
const videoID = document.URL.split('/')[4];
lockMenuEl.style.transform = 'translateX(-50%)';
createLockButton(lockMenuEl, videoID);
createCheckboxButton(lockMenuEl);
createLockTable(lockMenuEl);
createUnlockButton(lockMenuEl, videoID);
createSetUserIDButton(lockMenuEl);
lockMenuEl.appendChild(document.createElement('br'));
//createClearCacheButton(lockMenuEl, videoID);
//createPurgeSegmentsButton(lockMenuEl, videoID);
} else {
createSetUserIDButton(lockMenuEl);
lockMenuEl.style.transform = 'translateX(-50%)';
}
window.addEventListener('resize', () => { moveElementIntoViewport(lockMenuEl) });
const observableEl = document.getElementById('navbarNav');
if (observableEl) {
const observer = new MutationObserver(() => { moveElementIntoViewport(lockMenuEl) });
observer.observe(observableEl, {
attributes: true,
attributeFilter: ['class']
});
}
}
function createLockButton(lockMenuEl, videoID) {
// Create button that triggers category locking
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', 'lockLockCategoriesButton');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
buttonEl.blur();
categoryListAPI.forEach((name,i) => {
if (document.getElementById(`lockCheckbox${i}`).checked) {
const lockReason = document.getElementById(`lockReason${i}`).value;
setReasons(name, lockReason, videoID);
}
});
}
const buttonText = document.createTextNode("Lock");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function createCheckboxButton(lockMenuEl) {
// Create button that selects all checkboxes
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', 'lockSelectAll');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
// Check whether all are checked.
let allTrue = true;
categoryListUI.forEach((name, i) => {
if (name != 'POI' && name != 'Filler') {
allTrue = allTrue && document.getElementById(`lockCheckbox${i}`).checked;
}
});
categoryListUI.forEach((name, i) => {
if (name != 'POI' && name != 'Filler') {
document.getElementById(`lockCheckbox${i}`).checked = (allTrue) ? false: true;
}
});
buttonEl.blur();
}
const buttonText = document.createTextNode("Select all");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function createLockTable(lockMenuEl) {
// Create table that is inside the "dropdown"-menu
const tableBodyEl = document.createElement("TBODY");
const tableEl = document.createElement("TABLE");
lockMenuEl.appendChild(tableEl);
tableEl.appendChild(tableBodyEl);
// Create a table with checkboxes inside of labels and textareas
categoryListUI.forEach((name, i) => {
const tableRowEl = document.createElement("TR");
const tableLabelEl = document.createElement("TD");
tableLabelEl.style.userSelect = "none";
const tableReasonEl = document.createElement("TD");
const checkboxEl = document.createElement('input');
checkboxEl.setAttribute('type', 'checkbox');
checkboxEl.setAttribute('id', `lockCheckbox${i}`);
checkboxEl.style.marginLeft = '5px';
const labelEl = document.createElement('label');
labelEl.innerText = name;
labelEl.style.height = "75px";
labelEl.style.verticalAlign = "middle";
const reasonTextareaEl = document.createElement('TEXTAREA');
const reasonEl = document.createTextNode('');
reasonTextareaEl.setAttribute('id', `lockReason${i}`);
reasonTextareaEl.style.height = '75px';
reasonTextareaEl.style.width = '400px';
reasonTextareaEl.className = 'form-control';
reasonTextareaEl.appendChild(reasonEl);
labelEl.appendChild(checkboxEl);
tableLabelEl.appendChild(labelEl);
tableReasonEl.appendChild(reasonTextareaEl);
tableRowEl.appendChild(tableLabelEl);
tableRowEl.appendChild(tableReasonEl);
tableBodyEl.appendChild(tableRowEl);
});
}
function createUnlockButton(lockMenuEl, videoID) {
// Create button that triggers category UNlocking
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', 'lockUnlockCategoriesButton');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
buttonEl.blur();
const names = [];
categoryListAPI.forEach((name,i) => {
if (document.getElementById(`lockCheckbox${i}`).checked) {
names.push(name);
}
});
unSetReasons(names, videoID);
}
const buttonText = document.createTextNode("Unlock");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function createSetUserIDButton(lockMenuEl) {
// Button for the UserID
const buttonEl = document.createElement('BUTTON');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
buttonEl.blur();
let tempUserID = prompt('Set private UserID')
if (tempUserID !== null) GM_setValue("privateUserID", tempUserID);
}
const buttonText = document.createTextNode("Set UserID");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function createClearCacheButton(lockMenuEl, videoID) {
// Create button that triggers category UNlocking
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', 'lockClearCacheButton');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
buttonEl.blur();
clearCache(videoID);
}
const buttonText = document.createTextNode("Clear cache");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function createPurgeSegmentsButton(lockMenuEl, videoID) {
// Create button that triggers category UNlocking
const buttonEl = document.createElement('BUTTON');
buttonEl.setAttribute('id', 'lockPurgeSegmentsButton');
buttonEl.className = 'btn btn-secondary';
buttonEl.style.width = '240px';
buttonEl.style.margin = '2px 2px';
buttonEl.onclick = () => {
buttonEl.blur();
if (prompt('Write "yes" to confirm.') == 'yes') {
console.log('purged');
clearCache(videoID);
}
}
const buttonText = document.createTextNode("Purge segments");
buttonEl.appendChild(buttonText);
lockMenuEl.appendChild(buttonEl);
}
function setReasons (category, lockReason, videoID){
// Set all 8 categories lock reasons and locks, or it unlocks if checkbox isn't set
document.body.style.cursor = "progress";
document.getElementById("lockLockCategoriesButton").style.cursor = "progress";
GM_xmlhttpRequest({
method: 'POST',
url: 'https://sponsor.ajay.app/api/lockCategories',
data: JSON.stringify({
videoID: videoID,
userID: userID,
categories: [category],
reason: lockReason
}),
headers: { 'Content-Type': 'application/json' },
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
document.getElementById("lockLockCategoriesButton").style.cursor = null;
if (!res) {
window.alert(`Response object was undefined for some reason. ${category} was most likely not locked!`);
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Could not lock ${category}! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
document.getElementById("lockLockCategoriesButton").style.cursor = null;
window.alert(`Unknown error occurred and ${category} wasn't locked!`);
},
ontimeout: () => {
document.body.style.cursor = null;
document.getElementById("lockLockCategoriesButton").style.cursor = null;
window.alert(`Request timed out and ${category} may not have been locked!`);
}
});
}
function unSetReasons (categories, videoID){
// Unlockes selectd categories
document.body.style.cursor = "progress";
document.getElementById("lockUnlockCategoriesButton").style.cursor = "progress";
GM_xmlhttpRequest({
method: 'DELETE',
url: 'https://sponsor.ajay.app/api/lockCategories',
data: JSON.stringify({
videoID: videoID,
userID: userID,
categories: categories
}),
headers: { 'Content-Type': 'application/json' },
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
document.getElementById("lockUnlockCategoriesButton").style.cursor = null;
if (!res) {
window.alert(`Response object was undefined for some reason. Categories were most likely not unlocked!`);
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Could not unlock categories! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
document.getElementById("lockUnlockCategoriesButton").style.cursor = null;
window.alert(`Unknown error occurred and categories weren't unlocked!`);
},
ontimeout: () => {
document.body.style.cursor = null;
document.getElementById("lockUnlockCategoriesButton").style.cursor = null;
window.alert(`Request timed out and categories may not have been unlocked!`);
}
});
}
function getReasons() {
// Calls API and fills reasons in textareas
GM_xmlhttpRequest({
method: 'GET',
//url: `https://sponsor.ajay.app/api/lockCategories?videoID=${id}`,
url: `https://sponsor.ajay.app/api/lockCategories?videoID=qJeKZkK31JE`,
responseType: 'json',
onload: (res) => {
// https://wiki.greasespot.net/GM.xmlHttpRequest#Response_Object is what is returned
if(!res) return;
const d = document.createElement('div'); // TODO
let page = prompt(`Reason: ${res.status ?? ''}`);
},
});
}
function clearCache (videoID){
document.body.style.cursor = "progress";
document.getElementById("lockClearCacheButton").style.cursor = "progress";
GM_xmlhttpRequest({
method: 'POST',
url: 'https://sponsor.ajay.app/api/clearCache',
data: JSON.stringify({
videoID: videoID,
userID: userID
}),
headers: { 'Content-Type': 'application/json' },
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
document.getElementById("lockClearCacheButton").style.cursor = null;
if (!res) {
window.alert(`Response object was undefined for some reason. Cache was most likely not cleared!`);
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Could not clear cache! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
document.getElementById("lockClearCacheButton").style.cursor = null;
window.alert(`Unknown error occurred and cache wasn't cleared!`);
},
ontimeout: () => {
document.body.style.cursor = null;
document.getElementById("lockClearCacheButton").style.cursor = null;
window.alert(`Request timed out and cache may not have been cleared!`);
}
});
}
function purgeSegments (videoID){
document.body.style.cursor = "progress";
document.getElementById("lockPurgeSegmentsButton").style.cursor = "progress";
GM_xmlhttpRequest({
method: 'POST',
url: 'https://sponsor.ajay.app/api/purgeAllSegments',
data: JSON.stringify({
videoID: videoID,
userID: userID
}),
headers: { 'Content-Type': 'application/json' },
timeout: 10000,
onload: (res) => {
document.body.style.cursor = null;
document.getElementById("lockPurgeSegmentsButton").style.cursor = null;
if (!res) {
window.alert(`Response object was undefined for some reason. Segments were most likely not purged!`);
return;
}
if (res.status !== 200 && res.status !== 304) {
window.alert(`Could not purge segments! Server responded with status ${res.status}: ${res.statusText ?? ""}`);
}
},
onerror: () => {
document.body.style.cursor = null;
document.getElementById("lockPurgeSegmentsButton").style.cursor = null;
window.alert(`Unknown error occurred and segments weren't purged!`);
},
ontimeout: () => {
document.body.style.cursor = null;
document.getElementById("lockPurgeSegmentsButton").style.cursor = null;
window.alert(`Request timed out and segments may not have been purged!`);
}
});
}
function moveElementIntoViewport(element) {
const documentWidth = document.documentElement.clientWidth;
const rect = element.getBoundingClientRect();
if (documentWidth < element.offsetWidth) {
return;
}
if (rect.left < 15 && rect.left !== 0) {
element.style.transform = null;
const rectNew = element.getBoundingClientRect();
element.style.transform = `translateX(-${rectNew.left - 15}px)`;
} else if (rect.right > documentWidth - 15) {
element.style.transform = null;
const rectNew = element.getBoundingClientRect();
element.style.transform = `translateX(-${rectNew.right - (documentWidth - 15)}px)`;
} else if (rect.left >= 16 && rect.right <= (documentWidth - 16) && rect.left !== 0) {
if (element.getAttribute('id') === 'changeCategorySelector') {
element.style.transform = null;
} else {
element.style.transform = 'translateX(-50%)';
}
const rectNew = element.getBoundingClientRect();
if (rectNew.right > documentWidth - 15) {
element.style.transform = `translateX(-${rectNew.right - (documentWidth - 15)}px)`;
}
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment