// ==UserScript==
// @name        YouTube Playlist Deleted Video Name Recovery
// @description Recover (partial) names of YouTube videos which have been deleted
// @namespace   dnsev
// @include     *://*youtube.com/*
// @version     1.3
// @grant       none
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB+UlEQVR42u3XvWtTURgG8Hdqb3LzeWv9ABFUjCj+CU79BwQplA4uUjpYQQcdOhYcjSJasVpKC0UDhYigpKCxlNTWkA52SJWADYHbijdDMiTChcLjc+ES6mcPTehZOvyeA+fA877rEQBaHSwgBcvUSv8CeYbvOmVp1VchdEhlR2+aLpJ45COD0oR9liKRZcvsJ2jSL7m4mSJokpLFeDBP0CQv7+NBm6Bq8fhh7+wUW97Gg9sEFdljPfixtYnGxlcUb4wgeyTm3bdjW+ZjQajKXkjAdd2WxrctrI/exrsTR733PZE3DGXnE2g0Gr9oNpuob9pYu3UTmd6YepdPXjGUnTuDWq32V/V6Hd8/r2PlyqB6H0maoYwLOI7zX9VqFV+ez+JlT1ipU+aiASg7exq2be8qNz2FOSuk1CkphjIuUC6X/+nT8gckL1/CqNmFF4qdMstQljiFUqn0h7VCAeNDVzESNZEMG+p9JDMMZYmTKBaLLYWlJTzi4Gu9Fu6EujGt3tUiU5GAS1AxcSiGlYUF5OYzuDs4gOGIiTGzG0+9971x5VkkUCGomKBhK4qhQBfGQgYH8749G/IkEsgRVI17Z+fk5HHEmCRoMikPw0YfQZM+eRA2PEnCPkuSyH2Gb4Be0+oODqFNzm+dGX+WeOQeQx/9Cxz8DfUv8BPC7tqgW88FVAAAAABJRU5ErkJggg==
// @updateURL   https://raw.github.com/dnsev/ytd/master/ytd.user.js
// @downloadURL https://raw.github.com/dnsev/ytd/master/ytd.user.js
// ==/UserScript==



(function () {
	"use strict";



	var E = function (tag) {
		var el = document.createElement(tag);
		if (arguments.length > 1) {
			tag = "";
			for (var i = 1; i < arguments.length; ++i) {
				if (i > 1) tag += " ";
				tag += arguments[i];
			}
			el.className = tag;
		}
		return el;
	};



	var Ready = (function () {

		// Event callbacks
		var on_document_readystatechange_interval = null;
		var on_ready = null;
		var on_document_readystatechange = function () {
			// State check
			if (document.readyState == "interactive" || document.readyState == "complete") {
				// Clear
				clear_load_events();
				var on_ready2 = on_ready;
				on_ready = null;
				// Call ready
				if (on_ready2 instanceof Function) on_ready2.call(null);
				return true;
			}
			return false;
		};
		var on_document_load = function () {
			// Clear
			clear_load_events();
			var on_ready2 = on_ready;
			on_ready = null;
			// Call ready
			if (on_ready2 instanceof Function) on_ready2.call(null);
		};

		// Setup events
		var setup_load_events = function (callback) {
			// Setup events
			on_ready = callback;
			if (on_document_readystatechange_interval === null) {
				if (on_document_readystatechange() === false) {
					document.addEventListener("readystatechange", on_document_readystatechange, false);
					document.addEventListener("load", on_document_load, false);
					on_document_readystatechange_interval = setInterval(on_document_readystatechange, 20);
				}
			}
		};

		// Clear events
		var clear_load_events = function () {
			if (on_document_readystatechange_interval !== null) {
				// Remove timer
				clearInterval(on_document_readystatechange_interval);
				on_document_readystatechange_interval = null;

				// Remove events
				document.removeEventListener("readystatechange", on_document_readystatechange, false);
				document.removeEventListener("load", on_document_load, false);
				clearInterval(on_document_readystatechange_interval);
			}
		};

		// Return function
		return setup_load_events;

	})();



	var Fixer = (function () {

		var Fixer = function () {
			this.queue = [];
			this.running = false;
			this.timeout = 0.0;
			this.timeout_timer = null;
		};

		var run = function () {
			// Run?
			if (this.running || this.queue.length == 0) return;
			this.running = true;

			// Create url
			var url = "/watch?v=" + this.queue[0].video_id;

			// Create request
			var xhr = new XMLHttpRequest();
			xhr.open("GET", url, true);
			xhr.responseType = "document";

			// Error
			xhr.addEventListener("error", run_error.bind(this, xhr, "error"), false);
			// Abort
			xhr.addEventListener("abort", run_error.bind(this, xhr, "abort"), false);
			// Complete
			xhr.addEventListener("load", run_success.bind(this, xhr, "load"), false);

			// Start request
			xhr.send();
		};

		var run_error = function (xhr, reason) {
			// Error
			run_place_error.call(this, true, reason);

			// Next
			run_complete.call(this);
		};
		var run_success = function (xhr, reason) {
			// Get data
			var data = xhr.response;

			// Find video name
			var errorMessage = data.querySelector("#unavailable-message");
			if (errorMessage) {
				var m = /"(.+?)"/.exec(errorMessage.textContent || "");
				if (m) {
					// Show text
					var search_term = m[1].replace(/\.\.\.$/, "").trim();
					var link = E("a");
					link.setAttribute("target", "_blank");
					link.setAttribute("href", "/results?search_query=" + encodeURIComponent(search_term));
					link.textContent = m[1];
					run_place_links.call(this, link);
				}
				else {
					// Error
					run_place_error.call(this, false);
				}
			}

			// Next
			run_complete.call(this);
		};
		var run_place_error = function (ajax_error, ajax_error_message) {
			// Add an error
			var link = E("a");
			link.textContent = (ajax_error ? "Ajax error" + (ajax_error_message ? ": " + ajax_error_message : "") : "No results");
			run_place_links.call(this, link);
		};
		var run_place_links = function (element) {
			// Add links
			var div = E("div", "cyt-unavailable-video-name cyt-error");

			// Element
			div.appendChild(element);

			// Separator
			div.appendChild(E("span"));

			// Link
			var link = E("a");
			link.setAttribute("target", "_blank");
			link.setAttribute("href", "https://www.google.com/#q=" + encodeURIComponent("YouTube \"" + this.queue[0].video_id + "\"") + "&safe=off");
			link.textContent = "google";
			div.appendChild(link);

			// Add to document
			var par = this.queue[0].element.parentNode;
			if (par != null) {
				var s = this.queue[0].element.nextSibling;
				if (s == null) {
					par.appendChild(div);
				}
				else {
					par.insertBefore(div, s);
				}
			}
		};
		var run_complete = function () {
			// Run next queue item
			var self = this;
			this.queue.shift();
			this.timeout_timer = setTimeout(function () {
				// Run
				self.timeout_timer = null;
				self.running = false;
				run.call(self);
			}, this.timeout * 1000);
		};


		Fixer.prototype = {
			constructor: Fixer,

			add: function (element) {
				var queue_item = {
					element: element,
					video_id: null,
				};

				var m = /\?(?:.+?\&)?v=([^\&]+)/.exec(element.getAttribute("href"));
				if (m) queue_item.video_id = m[1];

				this.queue.push(queue_item);

				// Start if not running
				run.call(this);
			}

		};


		return Fixer;

	})();



	var main = function () {
		// Insert stylesheet
		var head = document.querySelector("head");
		if (head) {
			var style = E("style");
			style.innerHTML =
				'.cyt-unavailable-video-name{color:#2793e6;font-weight:bold;display:block;margin-top:4px;}\
				.cyt-unavailable-video-name>a{color:#e69327;text-decoration:none;}\
				.cyt-unavailable-video-name>a:hover{text-decoration:underline;}\
				.cyt-unavailable-video-name.cyt-error>a:not([href]){color:#777777;text-decoration:none;cursor:default;}\
				.cyt-unavailable-video-name>span:before{content:"/";margin:0px 4px;}\
				';
			head.appendChild(style);
		}

		// Find titles
		var titles = document.querySelectorAll(".pl-video-title-link");
		var deleteds = [];
		for (var i = 0; i < titles.length; ++i) {
			if ((titles[i].textContent || "").trim().toLowerCase() == "[deleted video]") {
				deleteds.push(titles[i]);
			}
		}

		// Fix deleteds
		if (deleteds.length > 0) {
			var fixer = new Fixer();
			for (var i = 0; i < deleteds.length; ++i) {
				fixer.add(deleteds[i]);
			}
		}
	};



	// Init
	Ready(function () {
		// Check url
		if (/^https?:\/\/(?:.+?\.)?youtube\.com\/playlist\?(?:(?:.+?\&)?list=([^\&]+))/i.test(document.location.href)) {
			main();
		}
	});

})();