volkan-k / Clean Loopy and Instant Autoplay For Youtube (Improved)

// ==UserScript==
// @name 			Clean Loopy and Instant Autoplay For Youtube (Improved)
// @namespace 		VolkanK_CL_YT
// @description 	Displays a link below YouTube videos to enable/disable auto replay. Instant Autoplay for Youtube. Play next video instantly.
// @include 	*://*.youtube.com/*
// @match 		*://*.youtube.com/*
// @credits 	QuaraMan (embed code) .Paradise (List Loop Support) RowenStipe (GreasyFork mirror)
// @version		4.5.3
// @author 		Volkan K.
// @run-at 		document-end
// @grant 		unsafeWindow
// @grant 		GM_addStyle
// @grant 		GM_setValue
// @grant 		GM_getValue
// @grant 		GM_registerMenuCommand
// @grant 		GM_log
// @require 	https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @license     MIT
// ==/UserScript==

this.$ = this.jQuery = jQuery.noConflict(true);
unsafeWindow.my_jQuery=jQuery.noConflict(true);

if (typeof GM_getValue == "undefined") {
	// We will use static values if we can't use Greasemonkey API
	var ytInstant = true;
	//alert("DEBUG1"); // for debug
}else {
	if (GM_getValue('yt_instant.autoplay', "abc") == "abc") GM_setValue('yt_instant.autoplay', true);
	var ytInstant = GM_getValue('yt_instant.autoplay', true);
	//alert("DEBUG2"); // for debug
	//alert(ytInstant); // for debug.
}
if (typeof GM_registerMenuCommand != "undefined") {
	GM_registerMenuCommand("Enable YT Instant Autoplay", function(){
		var r=confirm("Do you want to enable YT Instant Autoplay?\nNOTE:Change of this setting requres reload of YT tabs\n (current setting:"+GM_getValue('yt_instant.autoplay', true)+") (default setting:true)");
		if (r == true) {
			GM_setValue('yt_instant.autoplay', true);
			GM_log('Enabled YT Instant Autoplay');
		} else {
			GM_setValue('yt_instant.autoplay', false);
			GM_log('Disabled YT Instant Autoplay');
		}
	});
}

//alert(ytInstant); // for debug.

myScript = function(ytInstant) {
	//alert(ytInstant); // for debug.
	var ytLoop = false;
	var ytPlayList;
	var ytPLIndex;
	var script_name="USER-SCRIPT CLEAN-LOOPY";
	var debug_internal=true;
	function debugLog(message){
		if (debug_internal) {
			console.log(script_name+" : "+message);
		}
	}
	loopy = document.createElement("div");
	loopy.setAttribute("id","loopyButton");
	loopy.setAttribute("class","ytp-menuitem");
	loopy.setAttribute("role","menuitemcheckbox");
	loopy.setAttribute("aria-checked","false");
	loopy.setAttribute("tabindex","0");
	loopy.setAttribute("onClick", "LoopyOnOff();");
	loopy.innerHTML = '<div class="ytp-menuitem-label">Loop</div>'
	+'<div id="loopyContent" class="ytp-menuitem-content">'
	+'	<div class="ytp-menuitem-toggle-checkbox"></div>'
	+'</div>';
	
	var is_frame=false;
	var ytPlayer;
	function checkNested(obj /*, level1, level2, ... levelN*/) {
		var args = Array.prototype.slice.call(arguments),
		obj = args.shift();

		for (var i = 0; i < args.length; i++) {
			if (!obj.hasOwnProperty(args[i])) {
				return false;
			}
			obj = obj[args[i]];
		}
		return true;
	}
	function start() {
		if (typeof ytplayer === "object" && checkNested(ytplayer, "config", "attrs", "id") && document.getElementById(ytplayer.config.attrs.id)) {
			ytPlayer = document.getElementById(ytplayer.config.attrs.id);
			//debugLog("90");
		}
		else if (document.getElementById("movie_player")) {
			ytPlayer = document.getElementById("movie_player");
		}
		else if (document.getElementById("movie_player_neo")) {
			ytPlayer = document.getElementById("movie_player_neo");
		}
		else if (document.getElementsByClassName("html5-video-player")) {
			ytPlayer = document.getElementsByClassName("html5-video-player")[0];
		}
		else if (document.getElementById("ytplayer").contentDocument.getElementsByClassName("html5-video-player")) {
			is_frame=true;
			ytPlayer = document.getElementById("ytplayer").contentDocument.getElementsByClassName("html5-video-player")[0];
		}
		//alert (ytPlayer); // for debug
		if ( typeof ytPlayer.stopVideo === 'function' ) {
			initLoopy();
		}else {
			ytPlayer.addEventListener("onReady", "initLoopy");
		}
	}
	setInterval(start,1000);

	function initLoopy() {
		if (!document.getElementById("loopyButton")) { 
			//alert ("loopy button not found"); // for debug
			settings_button = document.querySelector("div.ytp-right-controls > button.ytp-button.ytp-settings-button");
			if (settings_button === null) {
				debugLog("ERROR: couldnt find settings button!");
				return;
			}
			settings_menu_c = document.querySelector("div.ytp-settings-menu > div.ytp-panel > div.ytp-panel-menu > div");
			if (settings_menu_c === null) {
				settings_button.click(); // to open settings menu, so it populates HTML
				setTimeout(function (settings_button){settings_button.click();},1,settings_button); // to close it after job done.
			}
			settings_menu = document.querySelector("div.ytp-settings-menu > div.ytp-panel > div.ytp-panel-menu");
			if (settings_menu === null) {
				debugLog("ERROR: couldnt find settings menu!");
				return ;
			}
			for (var i = 0; i < settings_menu.childNodes.length; i++) {
				if (settings_menu.childNodes[i].innerText.includes("Speed") && settings_menu.childNodes[i].getAttribute("aria-haspopup")=="true"){
					settings_menu.insertBefore(loopy, settings_menu.childNodes[i]);
					fix_height_fit_children("div.ytp-settings-menu > div.ytp-panel > div.ytp-panel-menu",2)
					break;
				}
			}
		}

		if (is_frame == true) {
			ytPlayer.addEventListener("onStateChange", "window.parent.onPlayerStateChange");
			//alert("DEBUG1"); // debugging.
		} else {
			ytPlayer.addEventListener("onStateChange", "onPlayerStateChange");
			//alert("DEBUG2"); // debugging.
		}
		mic_add_EL();
		mic_prepare();
		document.querySelector('div.html5-video-player').addEventListener("contextmenu",mic_add_EL,false);
	}

	mic_prepare = function (){
		var m_i_c = document.querySelector('div.ytp-contextmenu div.ytp-menuitem[role="menuitemcheckbox"]');
		if (m_i_c!==null){ // menuitemcheckbox (loop) is here. exit.
			return ;
		}
		// trigger contextmenu
		var element = ytPlayer;
		if (window.CustomEvent) {
			element.dispatchEvent(new CustomEvent('contextmenu'));
		} else if (document.createEvent) {
			var ev = document.createEvent('HTMLEvents');
			ev.initEvent('contextmenu', true, false);
			element.dispatchEvent(ev);
		} else { // Internet Explorer
			element.fireEvent('oncontextmenu');
		}
		// close it
		setTimeout(function(){document.body.click()},1);
	}

	mic_add_EL = function() { // menuitemcheckbox addEventListener
		var m_i_c = document.querySelector('div.ytp-contextmenu div.ytp-menuitem[role="menuitemcheckbox"]');
		if (m_i_c!==null){
			m_i_c.addEventListener("click", sync_the_loop, false);
		}
	}

	sync_the_loop = function() {
		var m_i_c = document.querySelector('div.ytp-contextmenu div.ytp-menuitem[role="menuitemcheckbox"]');
		if (m_i_c===null){
			return;
		}
		if ((m_i_c.getAttribute("aria-checked")=="true" && ytLoop===false) || (m_i_c.getAttribute("aria-checked")=="false" && ytLoop===true)){
			LoopyOnOff();
		}
	}

	fix_height_fit_children = function(selector,up_level){
		if (typeof selector !== "string"){
			return;
		}
		new_height=0;
		parent=my_jQuery(selector);
		children=parent.children();
		children.each(function(){
			new_height+=my_jQuery(this).outerHeight();
		});
		if (parent.height()<new_height){
			parent.height(new_height);
		}
		debugLog(selector+" height = "+parent.height()+" ; children height = "+new_height);
		if (typeof up_level==="number"){
			for (var i = 0; i < up_level; i++) {
				current_parent=parent.parents().eq(i);
				if (current_parent.height()<new_height){
					current_parent.height(new_height);
				}
				debugLog(current_parent.prop("tagName")+'.'+current_parent.attr('class').split(' ').join('.')+" height = "+current_parent.height());
			}
		}
	}

	onPlayerStateChange = function(newState) {
		//alert("DEBUG3"); // debugging.
		if (newState == "0"){
			if (ytLoop) {
				//alert("DEBUG 185"); // debugging.
				window.setTimeout(function() { ytPlayer.playVideo(); }, 60);
			} else if (ytInstant) {
				if ((document.getElementById("autoplay-checkbox") && document.getElementById("autoplay-checkbox").checked) ||
				(document.getElementById("toggle") && document.getElementById("toggle").checked)) {
					//alert("DEBUG5"); // debugging.
					window.setTimeout(function() {
						if ( document.querySelector("button.ytp-upnext-cancel-button")!==null) {
							document.querySelector("button.ytp-upnext-cancel-button").click();
						}
						if ( document.querySelector("ytd-compact-autoplay-renderer a#thumbnail")!==null ) {
							document.querySelector("ytd-compact-autoplay-renderer a#thumbnail").click();
						} else {
							ytPlayer.nextVideo();
						}
					}, 60);
				}
			}
		}
	}

	LoopyOnOff = function() {
		if (ytLoop) {
			document.getElementById("loopyButton").setAttribute("aria-checked", "false");

			ytLoop = false;
			loop_item=document.querySelector('div.ytp-contextmenu div.ytp-menuitem[role="menuitemcheckbox"][aria-checked="true"]');
			if (loop_item!==null){
				loop_item.click();
			} else {
				document.getElementsByTagName("video")[0].removeAttribute("loop");
			}
		} else {
			document.getElementById("loopyButton").setAttribute("aria-checked", "true");
			ytLoop = true;
			/*if((apbut = document.querySelectorAll('.playlist-nav-controls .yt-uix-button-player-controls.toggle-autoplay')) && (apbut = apbut[0]) && apbut.classList.contains('yt-uix-button-toggled')){
				apbut.click(); // Turn off Playlist Autoplay so we can loop the current video.
			}*/
			loop_item=document.querySelector('div.ytp-contextmenu div.ytp-menuitem[role="menuitemcheckbox"][aria-checked="false"]');
			if (loop_item!==null){
				loop_item.click();
			} else {
				document.getElementsByTagName("video")[0].createAttribute("loop");
			}
		}
		mic_add_EL();
	}

	function getCookie(name) {
		var results = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
		if (results) {
			return unescape(results[2]);
		} else {
			return null;
		}
	}

	function setCookie(name, value) {
		document.cookie = name + "=" + escape(value);
	}

	if (typeof GM_addStyle == "undefined") {
		GM_addStyle = function(text) {
			var head = document.getElementsByTagName("head")[0];
			var style = document.createElement("style");
			style.setAttribute("type", "text/css");
			style.textContent = text;
			head.appendChild(style);
		}
	}

	GM_addStyle("						\
loopyButton:hover {\
		border: 0px none;} \
 		img.LoopyOff{\
		background: url(\"//image.ibb.co/j0Mnhm/jlhKt.png\") -0px -0px no-repeat transparent !important;\
		height: 18px;\
		width: 30px;}\
		img.LoopyOn{\
		background: url(\"//image.ibb.co/j0Mnhm/jlhKt.png\") -0px -18px no-repeat transparent !important;\
		height: 18px;\
		width: 30px;}"		

	);
};

var uw;

// unwraps the element so we can use its methods freely
function unwrap(elem) {
	if (elem) {
		if ( typeof XPCNativeWrapper === 'function' && typeof XPCNativeWrapper.unwrap === 'function' ) {
			return XPCNativeWrapper.unwrap(elem);
		} else if (elem.wrappedJSObject) {
			return elem.wrappedJSObject;
		}
	}
	return elem;
}

// get the raw window object of the YouTube page
uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : unwrap(window);

// disable Red Bar aka SPF
if (uw._spf_state && uw._spf_state.config) {
	uw._spf_state.config['navigate-limit'] = 0;
	uw._spf_state.config['reload-identifier'] =null;
}

if ( /^\/?watch/i.test(window.location.pathname) ) {
	document.body.appendChild(document.createElement("script")).innerHTML = "("+myScript+")("+ytInstant+")";
}