Iron_man / Download Sibnet Video as MP4

// ==UserScript==
// @name        Download Sibnet Video as MP4
// @namespace   http://video.sibnet.ru
// @description Скачивание видео одним кликом, правильные названия видео при скачивании
// @include     *://video.sibnet.ru/*
// @include     *://cv*.sibnet.ru/*
// @include     *://dv*.sibnet.ru/*
// @connect     sibnet.ru
// @version     1.4.1
// @author      Iron_man
// @updateURL   https://openuserjs.org/meta/Iron_man/Download_Sibnet_Video_as_MP4.meta.js
// @copyright   2017, Iron_man (https://openuserjs.org/users/Iron_man)
// @license     MIT
// @grant       GM_xmlhttpRequest
// @grant       GM.xmlHttpRequest
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @run-at      document-start
// ==/UserScript==

(function(){
	console.log("Download Sibnet Videos start..");
	var userOptions = initOptions();
	userOptions.set({
		clickCancel : true,// автоматически нажать на отмену в конец видео
		removeAds : true,// удалить рекламу
		addVideoId : true,// добавить видео id в название файла
		useGMDownloader : false,// всегда использовать GM.xmlHttpRequest для скачивания файла (не рекомендуется)
		maxSize : 16 * 1024 * 1024,// (16 Mb) максимальный размер файла для скачивания с помощью GM.xmlHttpRequest
		delay : 500,// задержка в мс перед закрытием загрузочного iframe'a
		style: 0,// номер стиля диалогового окна
	});
	var RANDOM = '1669048',// Math.round(Math.random() * 1000000 + 1000000),
		isSourcePage = downloadFromSourcePage();
	if( !isSourcePage )
		document.addEventListener('DOMContentLoaded', start, false );
	function downloadFromSourcePage()
	{
		var href = window.location.href,
			match = href.match(/^((https?\:)?\/\/(([^\/\?\#\.]+)\.([^\/\?\#]+)))([^\?\#]+)([^\#]+)(.*)/);
		if( match[4] !== 'video' && match[8] && match[8].indexOf('.mp4') != -1 )
		{
			var fileName = match[8].slice(1),
				fileUrl = href.match(/([^\#]+)(.*)/)[1],
				sendMessage = function(){window.parent.postMessage( 'closeIFrame', '*' );},
				closeWindow = function(){window.close();},
				videoClose = function(){
					if( window.self !== window.parent )
						setTimeout( sendMessage, userOptions.val('delay') );
					else
						setTimeout( closeWindow, userOptions.val('delay') );
				};
			fileName = decodeURIComponent(fileName);
			document.addEventListener('readystatechange', function(event){
				if( this.readyState === 'interactive' )
				{
					downloadFile( fileName, fileUrl );
					var video = document.querySelector('video');
					if( video ) video.addEventListener('error', videoClose, false);
				}
				else if( this.readyState === 'complete' )
					videoClose();
			}, false);
			return true;
		}
		return false;
	}
	function downloadFile( name, resource )
	{
		var a = document.createElement('a'),
			body = document.body || document.getElementsByTagName('body')[0];
		a.setAttribute('download', name);
		a.href = resource;
		body.appendChild(a);
		a.click();
		a.parentNode.removeChild(a);
	}
	function start()
	{
		if( userOptions.val('removeAds') )
			removeAds();
		if( userOptions.val('clickCancel') )
			setCancelPostvideo();
		var pladform = detectPladformIFrames();
		if( pladform )
		{
			console.log('embeded video from pladform.ru');
			return;
		}
		var video_path = getVideoPath();// get video path name '/v/{numbers}/{videoid}.mpd' from current html source page
		console.log("video path: " + video_path);
		if( video_path )
		{
			GM.xmlHttpRequest({
				url: video_path,
				method: 'HEAD',
				onload: makeVideoLink,
			});
		}
		newCssClasses('dsv-style-id');
	}
	function getVideoPath()
	{
		var video, source_str, pos, end;
		video = document.getElementById('video');
		if( video )
			source_str = video.innerHTML;
		else
			source_str = document.body.innerHTML;
		pos = source_str.indexOf( "player.src([{src: \"" );
		if( pos == -1 )
			return null;
		pos = source_str.indexOf("/v/", pos);
		end = source_str.indexOf(".mpd", pos);
		if( end == -1 )
			return null;
		return source_str.substring(pos, end+4);
	}
	function makeVideoLink( xhr )
	{
		var video_link = xhr.finalUrl.replace('/manifest.mpd', '.mp4');// get downloadable video link
		console.log("video file: ", video_link);
		if( !video_link )
		{
			console.error("[makeVideoLink] can't find video source link");
			return;
		}
		var st = insertLink( video_link );// try to insert the link into 'video_size' element of html source page
		if( st !== 0 )
			confirmDownloadFile( video_link );
	}
	function insertLink( source_link, size_mb ) // insert hyper reference into video_size element  
	{
		var video_size = document.getElementsByClassName('video_size')[0];
		if( !video_size )
			return 1;
		size_mb = size_mb || video_size.innerHTML;
		video_size.innerHTML = '' +
		'<a id="video_file_' + RANDOM + '" class="video_file_active" href="' + source_link + '" ' +
			'title="Скачать">' + size_mb + '</a>';
		var video_file = document.getElementById('video_file_' + RANDOM),
			bytes = (parseInt(size_mb.match(/\d+/)[0], 10) || 0) * 1024 * 1024;
		if( bytes )
			video_file.setAttribute('file-size', bytes);
		video_file.addEventListener('click', handleDownloadFileEvent, false);
		return 0;
	}
	function handleDownloadFileEvent(event)
	{
		var t = event.target;
		if( event.ctrlKey )
			return;
		else if( !t.classList.contains('video_file_active') )
			return;
		event.preventDefault();
		if( t.classList.contains('video_file_active') )
			smartDownloadFile( getFileName(), t.href, t.getAttribute('file-size') );
	}
	function smartDownloadFile( name, source, size )
	{
		if( userOptions.val('useGMDownloader') || (size && size < userOptions.val('maxSize')) )
			GM_downloadFile(name, source);
		else
			makeIFrame( name, source );
	}
	function getFileName()
	{
		var fileName = document.querySelector('td.video_name > h1'),
			videoIdStr = (userOptions.val('addVideoId') ? " " + getVideoId() : "");
		if( fileName )
			return fileName.innerHTML + videoIdStr + ".mp4";
		fileName = document.querySelector('meta[property="og:title"]');
		if( fileName )
			return fileName.getAttribute('content') + videoIdStr + ".mp4";
		return "video_name_" + getVideoId() + ".mp4";
	}
	function getVideoId()
	{
		var href = window.location.href;
		return href.match(/video(id\=|)(\d+)/)[2];
	}
	function confirmDownloadFile( source )
	{
		var fileName = getFileName();
		makeConfirmWindow();
		setConfirmWindow( fileName, 0, source);
		GM.xmlHttpRequest({
			url: source,
			method: 'HEAD',
			context: {'url': source, 'name': fileName},
			onload: function(xhr){
				if( xhr.status !== 200 )
				{
					console.error("xhr.status: ", xhr.status, xhr.statusText );
					console.error("url: " + source);
					console.error("method: " + 'HEAD');
					return;
				}
				var fileSize = getContentLength( xhr.responseHeaders );
				setConfirmWindow( xhr.context.name, fileSize, xhr.context.url );
			}
		});
	}
	function makeConfirmWindow()
	{
		var confirmWnd = document.querySelector('#confirm_downlaod_window_' + RANDOM);
		if( !confirmWnd )
		{
			confirmWnd = document.createElement('div');
			confirmWnd.setAttribute('id', 'confirm_downlaod_window_' + RANDOM);
			confirmWnd.setAttribute('class', 'confirm_download_window');
			var body = document.body || document.getElementsByTagName('body')[0];
			body.appendChild(confirmWnd);
			var html = '' +
			'<div id="confirm-filename"></div>' +
			'<div id="confirm-filesize"></div>' +
			'<div id="confirm-bottom">' +
				'<button id="confirm-download-button-true" class="confirm-button">Скачать</button>' +
				'<button id="open-video-source-file" class="confirm-button">Открыть</button>' +
				'<button id="confirm-download-button-false" class="confirm-button">Отмена</button>' +
			'</div>' +
			/*
			'<div id="color-style-id" style="text-align: center;">' +
				'<button id="change-color-style" class="confirm-button">Сменить стиль</button>' +
			'</div>' +
			*/
			'';
			confirmWnd.innerHTML = html;
			confirmWnd.addEventListener('click', handleConfirmEvent, false);
		}
		confirmWnd.style.display = 'block';
		return confirmWnd;
	}
	function setConfirmWindow( fileName, fileSize, fileUrl )
	{
		var confirmWnd = document.querySelector('#confirm_downlaod_window_' + RANDOM) || makeConfirmWindow();
		var fileNameDiv = confirmWnd.querySelector('#confirm-filename'),
			fileSizeDiv = confirmWnd.querySelector('#confirm-filesize'),
			fileSourceBtn = confirmWnd.querySelector('#open-video-source-file');
		fileNameDiv.innerHTML = 'Имя файла: ' + shortenFileName(fileName);
		fileNameDiv.setAttribute('title', fileName);
		fileSizeDiv.innerHTML = 'Размер файла: ' + bytesToMB(fileSize, 1) + ' Mb';
		if( fileSize )
			fileSizeDiv.setAttribute('title', bytesToKB(fileSize) + ' Kb' );
		fileSourceBtn.setAttribute('title', fileUrl );
		confirmWnd.setAttribute('file-name', fileName);
		confirmWnd.setAttribute('file-size', fileSize);
		confirmWnd.setAttribute('file-source', fileUrl );
		confirmWnd.style.display = 'block';
	}
	function bytesToKB( bytes, precision )
	{
		if( bytes )
			return (bytes/1024).toFixed(precision || 0);
		return '--';
	}
	function shortenFileName( fileName )
	{
		this.maxLen = this.maxLen || 25;
		var nameLen = fileName.length;
		if( nameLen > this.maxLen )
		{
			var nameEnd = fileName.slice(-11);
			fileName = fileName.slice(0, (this.maxLen - nameEnd.length) );
			fileName += '...' + nameEnd;
		}
		return fileName;
	}
	function handleConfirmEvent(event)
	{
		var t = event.target;
		if( t.tagName !== 'BUTTON' )
			return;
		if( t.id === 'confirm-download-button-false' )
			this.style.display = 'none';
		else if( t.id === 'confirm-download-button-true' )
		{
			var fileName = this.getAttribute('file-name'),
				fileUrl = this.getAttribute('file-source'),
				fileSize = this.getAttribute('file-size');
			this.style.display = 'none';
			smartDownloadFile( fileName, fileUrl, fileSize );
		}
		else if( t.id === 'open-video-source-file' )
			window.open( this.getAttribute('file-source') );
		/*
		else if( t.id === 'change-color-style' )
		{
			var idx = userOptions.data.style.idx;
			userOptions.val('style', idx+1);
			resetCssClasses('dsv-style-id');
		}
		*/
	}
	function getContentLength( headersStr )
	{
		var headers = headersStr.split('\r\n');
		for( var i = 0, h; i < headers.length; ++i )
		{
			h = headers[i];
			if( h.indexOf('Content-Length') != -1 )
				return parseInt(h.match(/\d+/)[0], 10);
		}
		return 0;
	}
	function makeIFrame( name, source )
	{
		var iframe = document.querySelector('#video_download_iframe_' + RANDOM);
		if( !iframe )
		{
			iframe = document.createElement('iframe');
			iframe.setAttribute('id', 'video_download_iframe_' + RANDOM);
			var body = document.querySelector('body');
			body.appendChild(iframe);
		}
		name = encodeURIComponent(name);
		iframe.src = (source + '#' + name);
	}
	function closeIFrame()
	{
		var ifr = document.querySelector('#video_download_iframe_' + RANDOM);
		if( ifr )
			ifr.parentNode.removeChild(ifr);
	}
	function GM_downloadFile( name, source )
	{
		GM.xmlHttpRequest({
			url: source,
			method: 'GET',
			responseType: 'blob',
			onload: function(xhr){
				if( xhr.status !== 200 )
				{
					console.error("xhr.status: ", xhr.status);
					console.error("url: ", source);
					return;
				}
				console.log("source: " + source);
				console.log("name: " + name);
				var wURL = window.webkitURL || window.URL,
					resource = wURL.createObjectURL(xhr.response);
				downloadFile( name, resource );
				var video_file = document.querySelector('#video_file_' + RANDOM);
				if( video_file )
					video_file.classList.add('video_file_active');
				wURL.revokeObjectURL(resource);
			},
			onprogress: function(xhr){
				if( !xhr.lengthComputable )
					return;
				showDownloadWindow(xhr.total, xhr.loaded);
			}
		});
	}
	function showDownloadWindow(total, loaded)
	{
		var dlWnd = document.querySelector('#download_window_' + RANDOM);
		if( !dlWnd )
		{
			dlWnd = document.createElement('div');
			dlWnd.setAttribute('id', 'download_window_' + RANDOM);
			dlWnd.setAttribute('class', 'video_download_window');
			dlWnd = (document.body || document.getElementsByTagName('body')[0]).appendChild(dlWnd);
		}
		dlWnd.style.display = '';
		var html = bytesToMB(loaded, 1) + ' Mb / ' + bytesToMB(total, 1) + ' Mb' +
			' (' + (loaded/total*100).toPrecision(3) + '%)';
		dlWnd.innerHTML = html;
		if( total === loaded )
		{
			setTimeout(function(){
				dlWnd.style.display = 'none';
			}, 3000 );
		}
	}
	function bytesToMB( bytes, precision )
	{
		if( bytes )
			return (bytes/(1024*1024)).toFixed(precision || 0);
		return '--';
	}
	function addCssClass( cssClass, id )
	{
		var head = document.head || document.getElementsByTagName('head')[0],
			style = document.createElement('style');
		style.type = 'text/css';
		if( id )
			style.id = id;
		if( style.styleSheets )
			style.styleSheets.cssText = cssClass;
		else
			style.appendChild(document.createTextNode(cssClass));
		head.appendChild(style);
	}
	function resetCssClasses( id )
	{
		if( !id )
			return;
		var style = document.getElementById(id);
		if( style )
			style.parentNode.removeChild( style );
		newCssClasses( id );
	}
	function newCssClasses( id )
	{
		addCssClass(`
			.confirm-button {
				background-color: ${userOptions.val('style')['background-color']};
				color: #c7c7c7;
				font-size: 1.0em;
				border-radius: 5px;
				font-family: sans-serif;
				border: 2px solid #c7c7c7;
				cursor: pointer;
				margin: 0.4% 2%;
				padding: 0 3px;
				test-align: center;
			}
			.confirm-button:hover {
				background-color: ${userOptions.val('style')['background-color-hover']};
				border-color: white;
				color: white;
			}
			#confirm-bottom {
				padding: 3px 0 2px 0;
			}
			#confirm-bottom {
				text-align: center;
			}
			.confirm_download_window,
			.video_download_window {
				position: fixed;
				bottom: 20px;
				right: 20px;
				z-index: 9999;
				background-color: ${userOptions.val('style')['background-color']};
				color: white;
				//box-shadow: 5px 5px 5px #555555;
				font-size: 1.0em;
				font-family: sans-serif;
				border-radius: 5px;
				padding: 2px 10px;
				display:;
				cursor: default;
				border: 2px solid #c7c7c7;
			}
		`, id);
	}
	function removeAds()
	{
		var tbody = document.querySelector('.main tbody');
		if( tbody && tbody.children && tbody.children[0] )
			tbody.children[0].innerHTML = '<td style="height:0px"></td>';
	}
	function setCancelPostvideo()
	{
		var player_container = document.getElementById('player_container');
		if( player_container )
		{
			player_container.addEventListener('ended', function(event)
			{
				var postvideo_cancel = document.getElementsByClassName('vjs-postvideo-cancel')[0];
				if( postvideo_cancel )
					setTimeout( function(){postvideo_cancel.click();}, 1000 );
			}, true );
		}
	}
	function detectPladformIFrames()
	{
		var iFrames = document.getElementsByTagName('iframe'), count = 0;
		for( var i = 0; i < iFrames.length; ++i )
		{
			if( iFrames[i].src.indexOf('pladform.ru') != -1 )
			{
				iFrames[i].setAttribute('name', 'pladform' + i);
				iFrames[i].setAttribute('id', 'pladform' + i);
				++count;
			}
		}
		return count;
	}
	function recieveMessage(event)
	{
		if(event.origin.search(/(cv|dv).*\.sibnet\.ru/) != -1 ){
			console.log( "origin: " + event.origin );
			if( event.data === 'closeIFrame' )
				setTimeout( closeIFrame, userOptions.val('delay') );
		}else{
			//console.info('[recieveMessage] message from ' + event.origin + ' is ignored');
		}
	}
	window.addEventListener('message', recieveMessage, false );
	function initOptions()
	{
		function _setDef(){this.val = this.def;}
		var wndStyle = [];
		function addColor( bg, bgh )
		{
			wndStyle.push({
				'background-color': bg,
				'background-color-hover': bgh,
			});
		}
		addColor('#16a085', '#058f74' );
		addColor('#0b72aa', '#095d8c' );
		addColor('#2091d8', '#2080c7' );
		addColor('#3678cc', '#2668bc' );
		var retVal = {
			data: {
				'clickCancel' : {
					val: null,
					def: true,// автоматически нажать на отмену в конец видео
					setDef: _setDef,
				},
				'removeAds' : {
					val: null,
					def: true,// удалить рекламу
					setDef: _setDef,
				},
				'addVideoId' : {
					val: null,
					def: true,// добавить видео id в название файла
					setDef: _setDef,
				},
				'useGMDownloader' : {
					val: null,
					def: false,// всегда использовать GM.xmlHttpRequest для скачивания файла (не рекомендуется)
					setDef: _setDef,
				},
				'maxSize' : {
					val: null,
					def: 16 * 1024 * 1024,// (16 Mb) максимальный размер файла для скачивания с помощью GM.xmlHttpRequest
					setDef: _setDef,
					validator: function(v){
						return v < 268435456;// 256 * 1024 * 1024
					}
				},
				'delay' : {
					val: null,
					def: 500,// задержка в мс перед закрытием загрузочного iframe'a
					setDef: _setDef,
					validator: function(v){
						return v > 99;
					}
				},
				'style': {
					get val(){return wndStyle[this.idx];},
					set val(n){ this.idx = n%wndStyle.length;},
					def: 0,// номер стиля
					setDef: function(){
						this.idx = this.def;
					},
					validator: function(n){
						return n >= 0;
					}
				},
			},
			val: function( prop, v ){
				if( this.data[prop] )
				{
					if( v !== undefined )
					{
						if( this.data[prop].validator && this.data[prop].validator(v) )
							this.data[prop].val = v;
						else
							this.data[prop].val = v;
					}else
						return this.data[prop].val;
				}else
					return null;
			},
			setDefs: function(){
				for( var key in this.data )
					this.data[key].setDef();
			},
			set: function( opts ){
				for( var key in opts )
					this.val( key, opts[key] );
			},
		};
		retVal.setDefs();
		return retVal;
	}
	function remove(elm)
	{
		if( elm && elm.parentNode )
			return elm.parentNode.removeChild(elm);
	}
})();