volkan-k / DailyMotion Download

// ==UserScript==
// @name           DailyMotion Download
// @id             DailyMotionDownload
// @version        1.4
// @description    Adds download link for DailyMotion Videos
// @namespace      https://openuserjs.org/users/volkan-k/scripts
// @match          *://www.dailymotion.com/*
// @grant       unsafeWindow
// @grant       GM_download
// @grant       GM_openInTab
// @grant 		GM_xmlhttpRequest
// @require 	https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @require 	https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @require 	https://cdnjs.cloudflare.com/ajax/libs/filesize/3.6.1/filesize.min.js
// @require-test 	https://paste.ee/r/XNUsq/0
// @connect 	cloudflare.com
// @connect 	dailymotion.com
// @license 	MIT
// ==/UserScript==

var usw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; // Firefox, Opera<15

var RANDOM = Math.floor(Math.random() * 1234567890);
var DIALOG_ID = 'dmotion_download' + RANDOM;
var dl_handle, download_div, progressbar, progressLabel;
var is_downloading = false;
var timer1, timer2, timer3;

if (typeof GM_openInTab === "undefined") {
	GM_openInTab = window.open;
}

if (typeof GM_info.downloadMode === "undefined") {
	GM_info.downloadMode = "browser";
}

function find(selector) {
	return document.querySelector(selector);
}

function debugLog(message) {
	console.log("USER-SCRIPT DAILYMOTION-DOWNLOAD : " + message);
}

function convert_relative_urls(css, base) {
	return css.replace(/url\s*\(\s*['"]{1}([^'"]+)['"]{1}\s*\)/ig, function(match, capture) {
		if (/^(https?|file|ftps?|mailto|javascript|data:image\/[^;]{2,9};):/i.test(capture)) {
			return match; // url is already absolute
		}
		return "url('" + new URL(capture, base).href + "')";
	});
}

function addStyle_external(css_link, once, xhr) {
	id = btoa(css_link).replace(/[+\/=]+/ig, "");
	if (typeof xhr === "boolean" && xhr === true) {
		// xhr it
		GM_xmlhttpRequest({
			method: "GET",
			url: css_link,
			onerror: function(oEvent) {
				alert("Error " + oEvent.target.status + " occurred while receiving the document.");
			},
			onload: function(response) {
				if (response.readyState !== 4 || response.status !== 200) return;
				addGlobalStyle(convert_relative_urls(response.responseText, css_link), once, id)
			}
		});
		return;
	}
	var head, style;
	head = document.getElementsByTagName('head')[0];
	if (!head) {
		return;
	}
	if (once && $("link[href='" + css_link + "']").length > 0) {
		return;
	}
	style = document.createElement('link');
	style.setAttribute("rel", "stylesheet");
	style.setAttribute("type", "text/css");
	style.setAttribute("id", id);
	style.setAttribute("href", css_link);
	head.appendChild(style);
}

function addGlobalStyle(css, once, id) {
	var head, style;
	head = document.getElementsByTagName('head')[0];
	if (!head) {
		return;
	}
	if (once && document.getElementById(id)) {
		return;
	}
	style = document.createElement('style');
	style.setAttribute("type", "text/css");
	if (typeof id === "string") {
		style.setAttribute("id", id);
	}
	style.innerHTML = css;
	head.appendChild(style);
}

function downloadVideoNatively(e) {
	if ($.inArray(GM_info.downloadMode, ["native", "browser"]) === -1) {
		return;
	}
	var elem = e.currentTarget;
	e.returnValue = false;
	if (e.preventDefault) {
		e.preventDefault();
	}
	var link = $(elem).find('a').attr('href');
	var name = $(elem).find('a').attr('title') + '.mp4';
	name = name.replace(/[\\<>:"\/|?*]*/g, "");
	if (link && name) {
		if (typeof GM_download !== 'undefined') {
			if (GM_info.downloadMode === "native") {
				open_download_dialog();
				if (is_downloading === false) {
					dl_handle = GM_download({
						url: link,
						name: name,
						onerror: gm_dl_error,
						onload: gm_dl_load,
						onprogress: gm_dl_progress,
						ontimeout: gm_dl_timeout
					});
					dl_handle.my_filename = name;
					is_downloading = true;
				}
			} else {
				GM_download(link, name); // browser handles
			}
			debugLog("Downloading should start now.. File Name = " + name + " ; File URL = " + link);
		} else {
			GM_openInTab(link);
			debugLog("Download link should be opened in new tab now.. File Name = " + name + " ; File URL = " + link);
		}
	}
	return false;
}

function open_download_dialog() {
	if ($('#' + DIALOG_ID).length > 0 && $('#' + DIALOG_ID).dialog('isOpen')) {
		$('#' + DIALOG_ID).dialog('close').remove();
	}
	var dialogButtons = [{
		text: "Cancel Download",
		click: cancel_download
	}];
	addStyle_external('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css', true, true);
	addGlobalStyle("#progressbar" + RANDOM + "{margin-top: 20px;}.progress-label" + RANDOM + "{font-weight: bold; text-shadow: 1px 1px 0 #fff;}", true, 'gm_added_style_dmotiondl');
	download_div = $("<div title='File Download' id='" + DIALOG_ID + "'></div>").dialog({
		resizable: false,
		closeText: "Continue in background",
		buttons: dialogButtons,
		close: function(event, ui) {
			$(this).dialog('destroy').remove();
		}
	});
	$("button.ui-dialog-titlebar-close").tooltip();
	$('#' + DIALOG_ID).append('<div class="progress-label' + RANDOM + '">Starting download...</div>' +
		'<div id="extrainfo' + RANDOM + '"></div>' +
		'<div id="progressbar' + RANDOM + '"></div>');
	/*		$("#master" + RANDOM).slider({
				value: volume,
				create: function() {
					$("#handle" + RANDOM).text(' = %' + $(this).slider("value"));
				},
				slide: function(event, ui) {
					$("#handle" + RANDOM).text(' = %' + ui.value);
					SetVolume(ui.value);
				}
			});*/
	progressbar = $("#progressbar" + RANDOM);
	progressLabel = $(".progress-label" + RANDOM);
	progressbar.progressbar({
		value: false,
		change: function() {
			progressLabel.text("Current Progress: " + progressbar.progressbar("value") + "%");
		},
		complete: function() {
			progressLabel.text("Complete!");
			change_button_to_close();
		}
	});
}

function gm_dl_progress(response) {
	if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) {
		return;
	}
	if (response.lengthComputable === false) {
		return;
	}
	//console.log(response);
	var fs_html = "<p>File size: " + filesize(response.total) + "</p>";
	if (typeof dl_handle.my_filename === "string") {
		fs_html += "<p>File name: " + dl_handle.my_filename + "</p>";
	}
	if ($("#extrainfo" + RANDOM).html() !== fs_html) {
		$("#extrainfo" + RANDOM).html(fs_html);
	}
	var val = Math.floor(response.done / response.total * 100);

	progressbar.progressbar("value", val);
}

function gm_dl_load(response) {
	is_downloading = false;
	if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) {
		return;
	}
	progressbar.progressbar("value", 100);
	change_button_to_close();
}

function gm_dl_timeout(response) {
	is_downloading = false;
	if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) {
		return;
	}
	progressbar.progressbar("value", false);
	progressLabel.text("Download time-out!");
	change_button_to_close();
}

function gm_dl_error(response) {
	is_downloading = false;
	if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) {
		return;
	}
	progressbar.progressbar("value", false);
	progressLabel.text("Download error!");
	change_button_to_close();
}

function change_button_to_close() {
	download_div.dialog("option", "buttons", [{
		text: "Close",
		click: function(event, ui) {
			$(this).dialog('close');
		}
	}]);
	download_div.dialog("option", "closeText", "Close");
}

function cancel_download() {
	is_downloading = false;
	dl_handle.abort();
	if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) {
		return;
	}
	download_div.dialog("close");
}

function are_we_on_video_page(){
	var url = window.location.href;
	var video_id = getDailyMotionId(url);
	if (video_id === null){
		return false;
	}
	// else
	return true;
}

function setup(new_data) {
	if (((usw && '__PLAYER_CONFIG__' in usw) || (typeof new_data === "object")) && find("p[class^='QueueInfoContent__stats']")) {
		try {
			if (typeof new_data === "object" && new_data !== null ) {
				debugLog("DEBUG: using XHR metadata");
				var video_id=new_data.id;
				var video_title=new_data.title;
				var video_qualities=new_data.qualities;
			} else if (typeof usw.__PLAYER_CONFIG__.metadata === "object" && usw.__PLAYER_CONFIG__.metadata !== null) {
				debugLog("DEBUG: using __PLAYER_CONFIG__ metadata");
				var video_id = usw.__PLAYER_CONFIG__.metadata.id;
				var video_title = usw.__PLAYER_CONFIG__.metadata.title;
				var video_qualities = usw.__PLAYER_CONFIG__.metadata.qualities;
			} else if (are_we_on_video_page()===true) {
				debugLog("DEBUG: can't find any metadata, but we are on video page, running XHR");
				clearTimeout(timer3); 
				timer3=setTimeout(xhr_video_info,1000);
				return;
			} else {
				debugLog("NOTICE: couldnt find any metadata nor video ID. Exiting..");
				return;
			}

			var container = document.createElement("div");
			container.setAttribute('class', 'dailymotion_downloader');
			var url;
			for (var resolution in video_qualities) {
				video_qualities[resolution].some(function(element) {
					if (element.type === "video/mp4") {
						url = element.url;
						//console.log(resolution); console.log(url); // for debug
						var div = document.createElement("div");
						div.style.display = "inline-block";
						var span = document.createElement("span");
						//span.innerHTML = resolution;
						span.style.display = "inline-block";
						span.style.width = "130px";
						div.appendChild(span);
						var a = document.createElement("a");
						a.title = video_title;
						a.href = url;
						a.textContent = 'Download ' + resolution + 'p';
						span.appendChild(a);
						container.appendChild(div);
						// listen to click event
						div.addEventListener('click', downloadVideoNatively, false);
					}
				});
			}
			clearTimeout(timer1); // we created the button, stop setup() loop
			clearInterval(timer2); // we will setInterval, clear previous one first.

			timer2 = setInterval(function() {
				var node = find("p[class^='QueueInfoContent__stats']");
				if (node === null) {
					return;
				}
				var oldButton = find('.dailymotion_downloader');
				if (oldButton !== null && oldButton.innerHTML === container.innerHTML) {
					return;
				}
				//oldButton && console.log(oldButton.outerHTML); // for debugging only.
				//console.log(button.outerHTML); // for debugging only.
				oldButton && oldButton.remove();
				if (find('.dailymotion_downloader') === null) {
					node.parentNode.insertBefore(container, node);
				}
			}, 500);
		} catch (error) {
			// log the error
			console.error("[DailyMotion Download] Error retrieving video meta data:", error);
		}
	} else {
		// try again later
		timer1 = setTimeout(setup, 500,(typeof new_data === "object" ? new_data : false));
	}
}

function getDailyMotionId(url) {
	//return url.substring(url.lastIndexOf('/') + 1); // old buggy method.
	var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
	if (m !== null) {
		if (m[4] !== undefined) {
			return m[4];
		}
		return m[2];
	}
	return null;
}

function xhr_video_info() {
	var url = window.location.href;
	var video_id = getDailyMotionId(url);
	if (video_id === null){
		debugLog("DEBUG: xhr_video_info() couldnt find video id");
		return ;
	}
	GM_xmlhttpRequest({
		method: "GET",
		url: "https://www.dailymotion.com/player/metadata/video/" + video_id,
		onerror: function(oEvent) {
			alert("Error " + oEvent.target.status + " occurred while receiving the document.");
		},
		onload: function(response) {
			if (response.readyState !== 4 || response.status !== 200) return;
			setup(JSON.parse(response.responseText));
		}
	});
}

// start looking for video player
setup();

// f*cking dailymotion does not update window.__PLAYER_CONFIG__ object.
$(window).bind('popstate', function() {
	debugLog("DEBUG: popstate event: calling xhr_video_info()");
	xhr_video_info();
});

var window_history_pushState = window.history.pushState;
window.history.pushState = function() {
	window_history_pushState.apply(window.history, arguments);
	debugLog("DEBUG: pushstate event: calling xhr_video_info()");
	xhr_video_info();
}
//console.log(container);