s Youtube Automatic BS Skip 2.9.1 fork May2023 bugfix

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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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
}
`);