// ==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.3
// @author Iron_man
// @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 = $('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');
$attr(a, {'download': name});
a.href = resource;
$('body').appendChild(a);
a.click();
a.parentNode.removeChild(a);
}
function $( selector, element )
{
element = element || document;
return element.querySelector(selector);
}
function $$( selector, element )
{
element = element || document;
return element.querySelectorAll(selector);
}
function $attr( element, attributes )
{
if( typeof attributes == 'string' )
return element.getAttribute(attributes);
for( var key in attributes )
element.setAttribute(key, attributes[key]);
return element;
}
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 = $('#video');
if( video )
source_str = video.innerHTML;
else
source_str = $('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 = $('.video_size');
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 = $('#video_file_' + RANDOM),
bytes = (parseInt(size_mb.match(/\d+/)[0], 10) || 0) * 1024 * 1024;
if( bytes )
$attr(video_file, {'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, $attr(t, '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 = $('td.video_name > h1'),
videoId = getVideoId(),
videoIdStr = (userOptions.val('addVideoId') && videoId ? " " + videoId : "");
if( fileName )
return fileName.innerHTML + videoIdStr + ".mp4";
fileName = $('meta[property="og:title"]');
if( fileName )
return $attr(fileName, 'content') + videoIdStr + ".mp4";
return "video_name_" + videoId + ".mp4";
}
function getVideoId()
{
var href = window.location.href;
try{
return decodeURIComponent(href).match(/video(id\s?\=\s?|)(\d+)/)[2];
}catch(e){ return ''; }
}
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 = $('#confirm_downlaod_window_' + RANDOM);
if( !confirmWnd )
{
confirmWnd = document.createElement('div');
$attr(confirmWnd, {
'id': 'confirm_downlaod_window_' + RANDOM,
'class': 'confirm_download_window',
});
$('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 = $('#confirm_downlaod_window_' + RANDOM) || makeConfirmWindow();
var fileNameDiv = $('#confirm-filename', confirmWnd),
fileSizeDiv = $('#confirm-filesize', confirmWnd);
fileNameDiv.innerHTML = 'Имя файла: ' + shortenFileName(fileName);
$attr(fileNameDiv, {'title': fileName});
fileSizeDiv.innerHTML = 'Размер файла: ' + bytesToMB(fileSize, 1) + ' Mb';
if( fileSize )
$attr(fileSizeDiv,{'title': bytesToKB(fileSize) + ' Kb'} );
$attr( $('#open-video-source-file', confirmWnd), {'title': fileUrl});
$attr( confirmWnd, {
'file-name': fileName,
'file-size': fileSize,
'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' )
{
this.style.display = 'none';
smartDownloadFile( $attr(this, 'file-name'), $attr(this, 'file-source'), $attr(this, 'file-size') );
}
else if( t.id === 'open-video-source-file' )
window.open( $attr(this, '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 = $('#video_download_iframe_' + RANDOM);
if( !iframe )
{
iframe = document.createElement('iframe');
$attr(iframe, {'id': 'video_download_iframe_' + RANDOM});
$('body').appendChild(iframe);
}
name = encodeURIComponent(name);
iframe.src = (source + '#' + name);
}
function closeIFrame()
{
var ifr = $('#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 = $('#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 = $('#download_window_' + RANDOM);
if( !dlWnd )
{
dlWnd = document.createElement('div');
$attr(dlWnd, {
'id': 'download_window_' + RANDOM,
'class': 'video_download_window',
});
dlWnd = $('body').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 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;
min-width: 250px;
}
`, id);
}
function removeAds()
{
var tbody = $('.main tbody');
if( tbody && tbody.children && tbody.children[0] )
tbody.children[0].innerHTML = '<td style="height:0px"></td>';
}
function setCancelPostvideo()
{
var player_container = $('#player_container');
if( player_container )
{
player_container.addEventListener('ended', function(event)
{
var postvideo_cancel = $('.vjs-postvideo-cancel');
if( postvideo_cancel )
setTimeout( function(){postvideo_cancel.click();}, 1000 );
}, true );
}
}
function detectPladformIFrames()
{
var iFrames = $$('iframe'), count = 0;
for( var i = 0; i < iFrames.length; ++i )
{
if( iFrames[i].src.indexOf('pladform.ru') != -1 )
{
$attr(iFrames[i], {
'id': 'pladform' + i,
'name': '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;
}
})();