// ==UserScript==
// @name s Youtube Automatic BS Skip 2.9.1 fork May2023 bugfix
// @namespace https://greasyfork.org/en/scripts/392459-youtube-automatic-bs-skip
// @version 2.9.1.13
// @description Fork of YouTube Automatic BS Skip 2.9.1 by JustDaile https://greasyfork.org/en/scripts/392459-youtube-automatic-bs-skip . Automatic skipping of fixed length intros/outros for your favourite Youtube channels.
// @license MIT
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @require http://code.jquery.com/jquery-latest.js
// @run-at document-start
// ==/UserScript==
console.log(`${GM.info.script.name} run`)
//https://greasyfork.org/scripts/392459-youtube-automatic-bs-skip/code/Youtube%20Automatic%20BS%20Skip.user.js
const app = "YouTube Automatic BS Skip";
const version = '2.9.1_DaileAlimoMIT_edited';
const debug = false;
const log = function(line){if (debug) console.log(line)}
// Elements
const controlUI_ID = "outro-controls";
const modal_ID = "modal";
const progressBar_ID = "progress-bar";
const introLen_ID = "intro-length";
const outroLen_ID = "outro-length";
const channelTxt_ID = "channel_txt";
// Actions
const pauseOnOutro = "pause-on-outro";
const nextOnOutro = "next-on-outro";
const apply_ID = "apply";
// add indicators to the progress bar.
const setupProgressBar = function(selector) {
log('called setupProgressBar');
if($(`#${progressBar_ID}-intro`).remove()){log("removed intro bar");}
if($(`#${progressBar_ID}-outro`).remove()){log("removed outro bar");}
// add intro indicator to progress bar
if (!document.getElementById(`${progressBar_ID}-intro`)){
log('created intro indicator');
selector.prepend(
$(`<div id="${progressBar_ID}-intro">`).addClass("ytp-load-progress").css({
"left": "0%",
"transform": "scaleX(0)",
})
);
}
// add outro indicator to progress bar
if (!document.getElementById(`${progressBar_ID}-outro`)) {
log('created outro indicator');
selector.prepend(
$(`<div id="${progressBar_ID}-outro">`).addClass("ytp-load-progress").css({
"left": "100%",
"transform": "scaleX(0)",
})
);
}
return [`${progressBar_ID}-intro`, `${progressBar_ID}-outro`];
};
// update the indecators on the progressbar.
const updateProgressbars = function(intro, outro, duration) {
// update the intro progress bar
let introBar = $(`#${progressBar_ID}-intro`);
var introFraction = intro / duration;
introBar.css({
"left": "0%",
"transform": `scaleX(${introFraction})`,
"background-color": "green",
});
// update the outro progress bar
let outroBar = $(`#${progressBar_ID}-outro`);
var outroFraction = outro / duration;
outroBar.css({
"left": `${100 - (outroFraction * 100)}%`,
"transform": `scaleX(${outroFraction})`,
"background-color": "green",
});
};
const setupControls = function(selector) {
// Its easier to modify if we don't chain jquery.append($()) to build the html components
if($(`#${controlUI_ID}`).remove()){log("removed controls");}
var controls = document.getElementById(controlUI_ID);
if (controls == null) {
log('adding controls to video');
controls = selector.prepend(`
<button id="${controlUI_ID}" class="ytp-button loading" title="YABSS" aria-label="YABSS">
<div class="ytp-autonav-toggle-button-container">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path fill="white" d="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/></svg>
</div>
</button>
`);
}
if($(`#${modal_ID}`).remove()){log("removed modal");}
if (document.getElementById(modal_ID) == null) {
log('adding modal to DOM');
$('body').append(`
<div id="${modal_ID}">
<div id="${modal_ID}-escape"></div>
<div id="${modal_ID}-content">
<div id="${channelTxt_ID}">Loading Channel</div>
<h2 id="${controlUI_ID}-title" class="d-flex justify-space-between">YouTube Automatic BS Skip ${version}
<a href="https://www.buymeacoffee.com/JustDai" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24">
<g><path d="M0,0h24v24H0V0z" fill="none"></path></g>
<g fill="#ffffff"><path d="M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z"></path></g>
</svg>
</a>
</h2>
<div id="${controlUI_ID}-control-wrapper">
<div class="w-100 d-flex justify-space-around align-center">
<label for="${introLen_ID}">Intro</label>
<input type="number" min="0" id="${introLen_ID}" placeholder="unset" class="input">
</div>
<div class="w-100 d-flex justify-space-around align-center">
<label for="${outroLen_ID}">Outro</label>
<input type="number" min="0" id="${outroLen_ID}" placeholder="unset" class="input">
</div>
<div class="pa">
<div>
<label for="${controlUI_ID}-outro-action-group">Action on outro <span id='${controlUI_ID}-outro-settings'>(Disabled. Assign outro time to enable)</span></label>
</div>
<fieldset id="${controlUI_ID}-outro-action-group" class="d-flex">
<div>
<label for="${pauseOnOutro}">Pause Video</label>
<input type="radio" name="outro-action-group" id="${pauseOnOutro}">
</div>
<div>
<label for="${nextOnOutro}">Play Next Video</label>
<input type="radio" name="outro-action-group" id="${nextOnOutro}" checked="checked">
</div>
</fieldset>
</div>
<tp-yt-paper-button id="${apply_ID}" class="style-scope ytd-video-secondary-info-renderer d-flex justify-center align-center" style-target="host" role="button" elevation="3" aria-disabled="false">${apply_ID}</tp-yt-paper-button>
</div>
</div>
</div>`
);
var validate=x=>{
var y=$(`#${x}`).val().toString()
if(y && y != "" && parseInt(y) != NaN){
if (y < 0) y = 0
return Number(y)
}
return
}
$(`#${controlUI_ID}`).on('click', () => { $(`#${modal_ID}`).toggleClass("show"); });
$(`#${modal_ID}-escape`).on('click', () => { $(`#${modal_ID}`).removeClass("show"); });
$(`#${outroLen_ID}`).on('input', () => {
var outroSeconds = validate(outroLen_ID)
if(outroSeconds!=undefined) $(`#${controlUI_ID}-outro-settings`)[outroSeconds>0?'addClass':'removeClass']('hide')
})
$(`#${apply_ID}`).on("click", function() {
var introSeconds = validate(introLen_ID)
if(introSeconds!=undefined) updateapp({intro: introSeconds})
var outroSeconds = validate(outroLen_ID)
if(outroSeconds!=undefined) updateapp({outro: outroSeconds})
updateapp({outact: $(`#${nextOnOutro}`).get(0).checked?true:false })
$(`#${modal_ID}`).removeClass("show")
});
}
return controls;
};
const updateControls = ({loadedIntroSetInSeconds, loadedOutroSetInSeconds, channelTxt, tooltipTxt, loading, playNextOnOutro}) => {
if (channelTxt!=undefined) $(`#${channelTxt_ID}`).text(channelTxt)
if (tooltipTxt!=undefined) $(`#${controlUI_ID}`).attr('title', tooltipTxt)
if (loading!=undefined) $(`#${controlUI_ID}`)[loading?'addClass':'removeClass']('loading')
if (loadedIntroSetInSeconds!=undefined) $(`#${introLen_ID}`).attr("placeholder", loadedIntroSetInSeconds <= 0? "unset": loadedIntroSetInSeconds)
if (loadedOutroSetInSeconds!=undefined){
$(`#${outroLen_ID}`).attr("placeholder", loadedOutroSetInSeconds <= 0? "unset": loadedOutroSetInSeconds)
$(`#${controlUI_ID}-outro-settings`)[loadedOutroSetInSeconds>0?'addClass':'removeClass']('hide')
}
if (playNextOnOutro!=undefined){
$(`#${nextOnOutro}`).get(0).checked= playNextOnOutro?"checked":false
$(`#${pauseOnOutro}`).get(0).checked= playNextOnOutro?false:"checked"
}
};
var introid=x=>x.split(" ").join("_") + "-intro"
var outroid=x=>x.split(" ").join("_") + "-outro"
var outactid=x=>x.split(" ").join("_") + "-outro-action"
var curchannel
const CHNLOAD='loading'
var loadedIntroSetInSeconds
var loadedOutroSetInSeconds
var playNextOnOutro
var updateapp=({channel,intro,outro,outact})=>{
if(channel) curchannel=channel
if(curchannel===CHNLOAD){
updateControls({
channelTxt: 'N/A',
tooltipTxt: 'YABSS Loading',
loading: true,
})
}else{
if(intro!=undefined) GM_setValue(introid(curchannel), intro)
if(outro!=undefined) GM_setValue(outroid(curchannel), outro)
loadedIntroSetInSeconds = Number(GM_getValue(introid(curchannel), 0))
loadedOutroSetInSeconds = Number(GM_getValue(outroid(curchannel), 0))
if(outact!=undefined) GM_setValue(outactid(curchannel),outact)
playNextOnOutro = GM_getValue(outactid(curchannel), false)
updateControls({
channelTxt: curchannel,
tooltipTxt: `${curchannel}\nIntro: ${loadedIntroSetInSeconds}s\nOutro: ${loadedOutroSetInSeconds}s${loadedOutroSetInSeconds>0?playNextOnOutro?' (Play Next)':' (Pause)':''}`,
loading: false,
loadedIntroSetInSeconds: loadedIntroSetInSeconds,
loadedOutroSetInSeconds: loadedOutroSetInSeconds,
playNextOnOutro: playNextOnOutro
})
}
}
//
var vs
var curdur
var vsloop=e=>{
var v=e.target
if (curdur != v.duration && !isNaN(v.duration) ) { //do these one time per duration change
curdur = v.duration
updateProgressbars(loadedIntroSetInSeconds, loadedOutroSetInSeconds, v.duration)
if(v.currentTime < loadedIntroSetInSeconds) v.currentTime = loadedIntroSetInSeconds
}
if(loadedOutroSetInSeconds>0 && v.duration-loadedOutroSetInSeconds <= v.currentTime){
v.pause()
if(playNextOnOutro) $(".ytp-next-button")[0].click();
}
}
var vsupdate=_=>{
if(vs!=document.querySelector('.video-stream')){
vs=document.querySelector('.video-stream')
vs.addEventListener('timeupdate',vsloop)
}
setTimeout(vsupdate,1)
}
vsupdate()
//
const forkJoinJQExistCheck = function({selectors,func1,func2}) {
var $sins=selectors.map(s=>$(s))
if($sins.filter(x=>x.length /*.length jquery existence check */ ).length===selectors.length){
$sins.map((s,i)=>func1[i](s))
func2()
}
};
//
var lasturl
var cp=_=>{
if(lasturl!=document.URL){
updateapp({channel:CHNLOAD})
if(document.URL.includes("watch?")){
forkJoinJQExistCheck({
selectors: [ '.video-stream', ".ytp-right-controls", ".ytp-progress-bar"],
func1:[
_=>{},
setupControls,
setupProgressBar
],
func2: async function() {
lasturl=document.URL
updateapp({channel:CHNLOAD})
var thisurl=document.URL
var channel = await fetch(thisurl).then(a=>a.text()).then(a=>a.match(/"author":"(.*?)"/)[1])
if(thisurl===document.URL){ // not switched to another video while fetching channel/author
curdur=0
if(true){ // a redundant intro skip for the beginning few seconds to tackle 'timeupdate' latency
var v=document.querySelector('.video-stream')
var p=!v.paused
v.pause()
vsloop({target:v})
if(p) v.play()
}
updateapp({channel:channel})
}
}
})
}
}
setTimeout(cp,100)
}
;(_=>cp())();
// Write the CSS rules to the DOM
GM_addStyle(`
#${modal_ID}-escape {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
}
#${modal_ID} {
display: none;
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 999;
background: rgba(0,0,0,.8);
}
#${modal_ID}.show {
display: flex;
}
#${modal_ID}-content {
margin: auto;
width: 30%;
height: auto;
background-color: var(--yt-live-chat-action-panel-background-color);
border-radius: 6px 6px 6px;
border: 1px solid white;
padding: 15px;
color: white;
z-index: 1001;
}
#${introLen_ID},#${outroLen_ID} {
font-size: 1.2em;
padding: .4em;
border-radius: .5em;
border: none;
width: 80%
}
#${apply_ID} {
position: relative;
border: 1px solid white;
transition: background-color .2s ease-in-out
}
#${apply_ID}:hover {
background-color: rgba(255,255,255,0.3);
}
#${controlUI_ID}.loading {
opacity:.3
}
#${controlUI_ID} {
height: 100%;
padding: 0;
margin: 0;
bottom: 45%;
position: relative;
}
#${controlUI_ID} svg {
position: relative;
top: 20%;
left: 20%;
}
#${controlUI_ID}-panel {
margin-right: 1em;
vertical-align:top
}
#${controlUI_ID} > * {
display: inline-block;
max-height: 100%;
}
#${controlUI_ID}-title {
padding: 2px;
}
#${controlUI_ID}-outro-action-group {
padding: .5em;
}
#${controlUI_ID}-outro-action-group > div {
display: block;
margin: auto;
text-align-last: justify;
}
#${controlUI_ID}-control-wrapper > * {
padding: 1em;
}
#action-radios {
display: none;
}
#action-radios .actions{
padding-left: 2px;
text-align: left;
background-color: black;
color: white;
}
#${introLen_ID},#${outroLen_ID} {
margin-right: 2px;
}
#${channelTxt_ID} {
position: relative;
top: -3.5em;
margin-bottom: -1.5em;
}
.w-100 {
width: 100% !important;
}
.input {
padding: .2em;
}
.d-flex {
display: flex;
}
.justify-center {
justify-content: center;
}
.justify-space-around {
justify-content: space-around;
}
.justify-space-between {
justify-content: space-between;
}
.align-center {
align-items: center;
}
#${controlUI_ID}-outro-settings.hide{
display:none
}
`);