Youtube outro skip

Sick of outros on your favourite creators videos? With this script simply set the outro length in seconds, apply and enjoy!

2020-05-27 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name         Youtube outro skip
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Sick of outros on your favourite creators videos? With this script simply set the outro length in seconds, apply and enjoy!
// @author       JustDaile
// @match        https://www.youtube.com/watch?*
// @grant        GM_setValue
// @grant        GM_getValue
// @require      http://code.jquery.com/jquery-latest.js
// ==/UserScript==

let destroy = function(){
    //
    // make sure we have no events binded, in the case fn() was called by interval on URL change
    // this will ensure that we can create clean controls for the current playlist without accidentally
    // having events persisting in the background.
    //
    if ($(".video-stream").unbind()) {console.log("unbinding .video-stream");}
    if ($("#set-outro").unbind()){console.log("unbinding #set-outro");}
    if ($(".outro-controls").remove()){console.log("removed controls");}
}

let whenReady = function(selector, callback) {
    if ($(selector).length) {
       return callback($(selector));
    } else {
        setTimeout(function(){
            whenReady(selector, callback);
        }, 100);
    }
};

(function(fn){
    "use strict";
    var l = document.URL;
    console.log(l);
    //
    $(document).ready(function(){
        console.log("document ready");
        fn();
    });
    //
    // detect page change hashchange not working
    // so check every 3 seconds if current URL matches URL we started with.
    // handle appropriately.
    //
    setInterval(function(){
       if (l != document.URL){
           console.log("document url: " + l);
           l = document.URL;
           if (l === "https://www.youtube.com/") {
               console.log("ignoring home");
               destroy();
               return;
           }
           $(document).ready(function(){
               destroy();
               fn();
           });
       }
   }, 3000);
})(function(){
    whenReady(".video-stream", function($stream){
        whenReady("#primary", function($container){
            whenReady("#meta-contents #text.ytd-channel-name,.ytp-ce-channel-title", function($channel){
                let channel = $channel.first().text();
                console.log("loaded channel: " + channel);
                //
                let urlString = document.URL + '&';
                //
                let isPlaylist = urlString.includes("list")
                let loadedChannel = true;
                let targetId = channel.split(" ").join("_");
                if (isPlaylist && !GM_getValue(targetId)) {
                    console.log("using playlist outro settings");
                    loadedOutroSetInSeconds = GM_getValue(urlString.match(/[\?\&]list=([^\&\#]+)[\&\#]/i)[1]);
                    loadedChannel = false;
                }
                //
                var loadedOutroSetInSeconds = GM_getValue(targetId);
                console.log("outro set: " + (loadedOutroSetInSeconds || 0));
                //
                // basic outro controls design prepended to the primary container.
                // if property length doesn't exist we can assume controls don't either.
                // prepend controls to #primary container
                //
                let target = isPlaylist? "playlist" : channel;
                console.log("target set " + (loadedChannel? "channel": "playlist") + " = " + targetId);
                console.log("writing outro controls");

                $container.prepend(
                    $("<div class='outro-controls'>").append(
                        $("<h3>Youtube Skip Outro Controller V1.1</h3>")
                    ).append(
                        $("<input type='number' id='outro-length' placeholder='using id: " + targetId + "'/>")
                    ).append(
                        $("<button id='set-outro'>apply</button>")
                    ).append(
                        $("<div>" + target + " outro set: <span id='outro-set'>0</span> seconds</div>").css({
                            "padding": "2px",
                        })
                    ).css({
                        "margin": "2px",
                        "textAlign": "right",
                    })
                );
                //
                let bindToStream = function(){
                    // hook video timeupdate, wait for outro and hit next button when time reached
                    //
                    if(loadedOutroSetInSeconds){
                        var skipOutroSeconds = parseInt(loadedOutroSetInSeconds) | 0;
                        $stream.unbind("timeupdate").on("timeupdate", function(e){
                            var currentTime = this.currentTime;
                            var duration = this.duration;
                            if(currentTime >= duration - skipOutroSeconds){
                                $(".ytp-next-button")[0].click();
                            }
                        });
                        $("#outro-set").text(loadedOutroSetInSeconds);
                    }
                }
                //
                // handle apply outro in seconds
                //
                $("#set-outro").on("click", function(e){
                    var seconds = $("#outro-length").val().toString();
                    if(seconds && seconds != "" && parseInt(seconds) != NaN){
                        GM_setValue(targetId, seconds);
                        loadedOutroSetInSeconds = seconds;
                        bindToStream();
                    }
                });
                bindToStream();
            });
        });
    });
});