KissAnime Auto Play Next Episode

Automatically plays the next video in the list without ever leaving fullscreen mode! Works on Kissanime/kisscartoon/kissasian

// ==UserScript==
// @name        KissAnime Auto Play Next Episode
// @description Automatically plays the next video in the list without ever leaving fullscreen mode! Works on Kissanime/kisscartoon/kissasian
// @icon        http://kissanime.ru/Content/images/favicon.ico
// @locale      en
// @namespace   matthewmarillac.com
// @author      Matthew James de Marillac
// @include     *://kissanime.com/*
// @include     *://kisscartoon.me/*
// @include     *://kisscartoon.se/*
// @include     *://kissanime.to/*
// @include     *://kissanime.ru/*
// @include     *://kissasian.com/*
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js
// @resource    materialize https://cdn.rawgit.com/mattmarillac/kissanime-userscript/master/Userscript/materialize.css
// @supportURL  https://github.com/mattmarillac/kissanime-userscript/issues
// @version     2.0.1
// @grant       GM_addStyle
// @grant       GM_getResourceText
// ==/UserScript==
//Matthew de Marillacs
//Kissanime AutoPlayer

var Url; //global variables
var skipFrom;
var skipFromStart;
var itr = false;
var active = true;
var params = window.location.pathname.split('/').slice(1);
var animeName = params[1];
var templates = templates();
var videoPlaceholder = document.getElementById('divContentVideo');  //get current video parent
if(typeof videoPlaceholder !== 'undefined' && videoPlaceholder !== 'null'){
var ref;
var video = (ref = videoPlaceholder.getElementsByTagName('video')) !== null ? ref[0] : void 0;  //get element video from previous elements child
var errorState = false;

//create interface
var style = GM_getResourceText ("materialize");
	GM_addStyle(style);
	createOverlay();	//create interface
}

$.ajaxSetup({
	timeout: 3000, 
	retryAfter:4000
});

$("#skipFromSubmit").on('click', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	setStorage();
});

$("#removeSkip").on('click', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	removeStorage();
});

$("#skipFromStartSubmit").on('click', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	setStartStorage();
});

$("#removeSkipFromStart").on('click', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	removeStartStorage();
});

function buttonFeedback(){
	$("#overlay p,input, button").fadeIn(100).fadeOut(100).fadeIn(100);
}

$("#skip-ol").on('click', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	var overlay= document.getElementById('overlay');
	overlay.style.visibility= 'visible';
	event.stopPropagation();
});

$('#divQuality').on('change', function(e) {
  quality = $("#divQuality option:selected").html();
  setResolution(quality);
});

$('html').on('click', function(event){
	try{
		if(event.target.id === "overlay"||
		event.target.parentElement.id === "overlay"|| $(event.target).is(':input') ||
		$(event.target).is('.lever') || event.target.id === "skip-ol"){
			// do nothing, TODO: refractor this condition
		} else
	    {
		    hideMessage();
	    }
	 }catch(e){
	 	// likely an advert is using the same id :(
	 	// how can you not want to use an adblocker on this site?
	 }
});

$('.active').on('click', function(){
if (active === true)
	active = false;
else
	active = true;
});

$(video).on("playing", function(){
	itr = false;
	resume();
});

if(typeof video !== 'undefined' && video !== 'null'){
$(video).on('canplay', function (event) {       //when video is ready to play add poster - prevents overlaping with default initial loading icon
	$(video).attr('poster', "https://raw.githubusercontent.com/mattmarillac/kissanime-userscript/master/Userscript/loading.gif");  //add loading icon for pause between videos
});

$(video).on('ended',function()
{     //once video ended
	if(itr === false && active === true){
		getNextInQue();
	}else{
		itr = false;
	}
});
}

function templates(){
    var ol = _.template("<div class='card-content white-text'><p>Thanks for using <a class='teal-text' href='http://www.matthewmarillac.com/api/anime.php' target='_BLANK'>Kissanime Autoplayer</a>. Be sure to leave a rating if you enjoy using it!</p>"+
                        "<p>Select a time to skip start from:</p> <input class='white-text' id='skipFromStart' placeholder='05:30'/>" +
                        "<button class='waves-effect waves-light btn' id='skipFromStartSubmit'>Submit</button>  <button class='waves-effect waves-light btn' id='removeSkipFromStart'>Remove</button></div>"+
                        "<p>Select a time to skip credits from:</p> <input class='white-text' id='skipFrom' placeholder='30:20'/>" +
                        "<button class='waves-effect waves-light btn' id='skipFromSubmit'>Submit</button>  <button class='waves-effect waves-light btn' id='removeSkip'>Remove</button></div>");
    var bar = _.template("<div id='skip-ol' style='float:right;' class='vjs-control'><img style='height: 100%;' src='<%= icon %>'/></div>");
    var innerstyle = _.template("<style>#overlay {position: absolute; right:0; bottom: 35px; color: #FFF; text-align: center; font-size: 20px; background-color: rgba(7, 20, 30, 0.7); width: 640px; padding: 10px 0; z-index: 2147483647; border: 2px solid rgba(128, 128, 128, 0.35);}</style>");
    //-> end templates
    return {'ol': ol,
            'bar': bar,
            'innerstyle':innerstyle
           };
}

function getNextInQue(){
	var element = document.getElementById('btnNext').parentNode;
	if(Url === "" || Url === null)
	{   //if this is the first url in que get the first video link and src
		getNextUrl("init");
	}
	else
	{   //otherwise we move foward with previous ajax requested page
		getNextUrl(Url);
	}
}

//When the user clicks on the next button, goes to the next video from the current selected index
$(document.getElementById('btnNext').parentNode).on('click', function(event) {
	event.preventDefault();
	PrevOrNext("next");
});

//When the user clicks on the previous button, goes to the previous video from the current selected index
$(document.getElementById('btnPrevious').parentNode).on('click', function(event) {
	event.preventDefault();
	PrevOrNext("prev");
});

function nextVideo(url){
	// request video URL
	$.ajax({
		type: "GET",
		url: url,
		cache: false,
		success: function (response)
		{
			var select;
			var res = findResolution($(response).find('#divQuality option'));
			if(res !== false && typeof res !== 'undefined' && res !== null){
				select = $(res);
			}
			else{
				select = $(response).find('#divQuality option')[0];
			}
			    //get next video in encoded form from quality dropdown value
		if (OnKissCartoon()) {
			video.src = $kissenc.decrypt(_.escape($(select).val()));     //decodes using kisscartoon's decoder
		}else{
			video.src = ovelWrap(_.escape($(select).val()));       //base 64 decode extracted url and play src
		}
			video.play();

			document.getElementById("selectEpisode").selectedIndex++;       //increment current episode selection in episode select dropdown

			skipStart();
		},
		error: function (xhr, status, error) {
			// error in ajax
			editMessage('<p>Conectivity issue: trying to load video again in 3 seconds</p>');
			
			setTimeout ( function(){ nextVideo(url); }, $.ajaxSetup().retryAfter );
			
			errorState = true;
		}
		});

		if(errorState){

			editMessage(templates.ol());
			
			var overlay= document.getElementById('overlay');
			overlay.style.visibility= 'hidden';

			errorState = false;
		}
}

function getNextUrl(currentUrl){   //get the next videos url from an ajax request by reading href of next link on that page

	if(currentUrl == "init")
	{//this is the first video in the que - get the next page from current page link
			var element = document.getElementById('btnNext').parentNode;    //get url of next video from button href
			if(element===null || typeof(element)==='undefined' || element==='')
			{
				console.log("No more videos in series");
				return;
			}

			history.pushState({}, '', _.escape(element.href));    //add page to history so users can keep track of what anime they have seen
			Url = _.escape(element.href);     //asign video to current video global variable
			nextVideo(element.href);
	}
	else
	{   //make ajax request to the next page in que to get video href from the last page we ajax requested
		var nextUrl;
		 $.ajax({
			type: "GET",
			url: currentUrl,
			cache: false,
			success: function (response)
			{
				var select = $(response).find('img#btnNext').parent();
				nextUrl = _.escape($(select).attr("href"));       //get url of next video from button href from within ajax request
				history.pushState({}, '', _.escape($(select).attr("href")));  //add page to history so users can keep track of what anime they have seen
				Url = nextUrl;      //asign video to current video global variable
				nextVideo(nextUrl);
			},
			error: function (xhr, status, error) {
				// error in ajax
				console.log(error);
				editMessage('<p>Conectivity issue: trying to load video again in 3 seconds</p>');
				errorState = true;
				setTimeout ( function(){ getNextUrl(currentUrl); }, $.ajaxSetup().retryAfter );
			
			}
		 });
		 console.log(errorState);
		 if(errorState){

			editMessage(templates.ol());
			
			var overlay= document.getElementById('overlay');
			overlay.style.visibility= 'hidden';

			errorState = false;
		}
	}
}

function skipStart(){
	//Skip start credits if set
	        if(skipFromStart !== null && typeof skipFromStart != 'undefined'){
	            var seconds = timeToSeconds(skipFromStart);
	            video.currentTime = seconds;
	        }
}

function findResolution(option){
    var res;
    _.each(option, function(opt){
		if($(opt).text() === getResolution()){
			res = opt;
		}
	});
    return res;
}

function OnKissCartoon(){	//check if on kisscarton
	if(window.location.href.indexOf("kisscartoon") > -1) {
		return true;
	}else{
		return false;
	}
}


function PrevOrNext(pon){ 	//Goes to the next or previous page based off the currently selected episode
	var url;
	var element;
	var to;
	if (pon === "next"){
		url = window.location.href;
		element = document.getElementById("selectEpisode");
		element.selectedIndex++;
		to = url.lastIndexOf('/');
		to = to == -1 ? url.length : to + 1;
		Url = url.substring(0, to)  + element.options[element.selectedIndex].value;
		window.location.href = Url;
	}
	if (pon === "prev") {
		url = window.location.href;
		element = document.getElementById("selectEpisode");
		element.selectedIndex--;
		to = url.lastIndexOf('/');
		to = to == -1 ? url.length : to + 1;
		Url = url.substring(0, to)  + element.options[element.selectedIndex].value;
		window.location.href = Url;
	}
	if (pon != "prev" && pon != "next") {
		console.log("Varible taken in PrevOrNext is invalid");
	}
}

//->Recursive loop
function resume(){
	$(video).on('ended', function (event) {
		return;
	});
	$(video).on('paused', function (event) {
		return;
	});
	//if skipping hasn't been set exit this function
	if(skipFrom === "undefined" || skipFrom === "" || skipFrom === null || active === false)
	{
		return;
	}
	//if current video time matches stored skipping time trigger video ended event handler
	if(getTime(video.currentTime) == skipFrom && itr === false){
		itr = true;
		getNextInQue();
	}else{
	//recurse loop every second video playes
		setTimeout(resume, 1000);
	}

}
//->End loop

function timeToSeconds(time){
	var a = time.split(':'); // split it at the colons
	var seconds;
	
	if(a.length === 2){
		seconds = ((+a[0]) * 60 + (+a[1])); 
	}
	else{
		// minutes are worth 60 seconds. Hours are worth 60 minutes.
		seconds = (+a[0]) * 60 * 60 + (+a[1]) * 60 + (+a[2]); 
	}
	return seconds;
}

function getTime(totalSec){	//convert video play time(float) to timestamp
	var minutes = parseInt( totalSec / 60 ) % 60;
	var seconds = (totalSec % 60).toFixed(0);
	return((minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds  < 10 ? "0" + seconds : seconds));
}

//->DB
function getStorage(){	try{	//get the stored skip time from local storage if set
		if(typeof(Storage) !== "undefined") {
			skipFrom = localStorage.getItem(animeName+"_skipFrom");
			$("#skipFrom").val(skipFrom);
		}
	}catch(e)
	{
		console.log("Local storage not found");
	}
}


function setStorage(){	//user has clicked on button save credit skip time in local storage
	try{
		if(typeof(Storage) !== "undefined") {
			skipFrom = $("#skipFrom").val();
			//check for valid input
			if(skipFrom.match('^[0-5][0-9]:[0-5][0-9]$') || skipFrom.match('^[0-9]:[0-5][0-9]:[0-5][0-9]$')){
			//store entered time in local storage
				buttonFeedback();
				localStorage.setItem(animeName+"_skipFrom", skipFrom);
			}
			else{
				$("#skipFrom").val();
				$("#skipFrom").val('Invalid Number: format is h:mm:ss');
				_.delay(function() { $("#skipFrom").val(''); }, 3000);
			}
		}
	}catch(e)
	{
		console.log(e);
	}
}

//->DB
function getStartStorage(){	
	try{	//get the stored skip time from local storage if set
		if(typeof(Storage) !== "undefined") {
			skipFromStart = localStorage.getItem(animeName+"_skipFromStart");
			$("#skipFromStart").val(skipFromStart);
          
	        skipStart();
        }
	}catch(e)
	{
		console.log("Local storage not found");
	}
}

function removeStorage(){
	try{
		localStorage.removeItem(animeName+"_skipFrom");
		skipFrom = null;
		getStorage();
		$("#skipFrom").val("");
		buttonFeedback();
	}catch(e)
	{
		console.log("Local storage not found");
	}
}

function setStartStorage(){	//user has clicked on button save credit skip time in local storage
	try{
		if(typeof(Storage) !== "undefined") {
			skipFromStart = $("#skipFromStart").val();
			//check for valid input
			if(skipFromStart.match('^[0-5][0-9]:[0-5][0-9]$') || skipFromStart.match('^[0-9]:[0-5][0-9]:[0-5][0-9]$')){
			//store entered time in local storage
				buttonFeedback();
				localStorage.setItem(animeName+"_skipFromStart", skipFromStart);
			}
			else{
				$("#skipFromStart").val();
				$("#skipFromStart").val('Invalid Number: format is h:mm:ss');
				_.delay(function() { $("#skipFromStart").val(''); }, 3000);
			}
		}
	}catch(e)
	{
		console.log(e);
	}
}

function removeStartStorage(){
	try{
		localStorage.removeItem(animeName+"_skipFromStart");
		skipFromStart = null;
		getStorage();
		$("#skipFromStart").val("");
		buttonFeedback();
	}catch(e)
	{
		console.log("Local storage not found");
	}
}

function getResolution(){
	try{	//get the stored skip time from local storage if set
		if(typeof(Storage) !== "undefined") {
			var stor = localStorage.getItem(animeName+"_quality");
			var res = $('#divQuality option');
            $('#divQuality option:selected').removeAttr('selected');
            _.each(res, function(opt){
                if($(opt).text() === stor)
                $(opt).attr('selected', '');
            });
		}
	}catch(e)
	{
		console.log("Local storage not found");
	}
}

function setResolution(quality){	//user has clicked on button save credit skip time in local storage
	try{
		if(typeof(Storage) !== "undefined") {
			//check for valid input
			localStorage.setItem(animeName+"_quality", quality);
		}
	}catch(e)
	{
		Console.log(e);
	}
}
//->END DB

function createButton(){	//create a form for user to submit skip time
	daily_icon = {icon: dailyIcon()};
    $('.vjs-control-bar').append(templates.bar(daily_icon));

}

function createOverlay(){
	createButton();
	$("#selectPlayer").parent().hide();		//remove select player - flash not supported!
	$('.vjs-live-controls.vjs-control').append($('#btnNext').parent().clone()); //copy next button to video bar
	$(videoPlaceholder).prepend("<div class='overlay' id='overlay'></div>");
	$("body").append(templates.innerstyle());
	editMessage(templates.ol());
	hideMessage();
	getStorage();
	getStartStorage();
    getResolution();
}

function editMessage(message){
	var overlay= document.getElementById('overlay');
	overlay.style.visibility= 'visible';
	overlay.innerHTML = message;
}

function hideMessage(){
	var overlay= document.getElementById('overlay');
	overlay.style.visibility='hidden';
}

function dailyIcon(){
	switch (new Date().getDay()) {
	    case 1:
	    case 2:
	    	return "https://raw.githubusercontent.com/mattmarillac/kissanime-userscript/master/Userscript/bar_icon3.png";
	    case 3:
	    case 4:
	    case 5:
	        return "https://raw.githubusercontent.com/mattmarillac/kissanime-userscript/master/Userscript/bar_icon.png";
	    case 0:
	    case 6:
	        return "https://raw.githubusercontent.com/mattmarillac/kissanime-userscript/master/Userscript/bar_icon2.png";
	}
}