Greasy Fork is available in English.

Vk Media Downloader

Скачать музыку, видео с vk.com (ВКонтакте)

Pada tanggal 2018-12-02 09:12:42 UTC. Lihat %(latest_version_link).

// ==UserScript==
// @name           Vk Media Downloader
// @name:en        Vk Media Downloader
// @description    Скачать музыку, видео с vk.com (ВКонтакте)
// @description:en Download music, video from vk.com (Vkontakte)
// @namespace      https://greasyfork.org/users/136230
// @include        *://vk.com/*
// @include        *://*.vk-cdn.com/*
// @include        *://*.vk-cdn.net/*
// @include        *://*.userapi.com/*
// @include        *://*.vkuseraudio.net/*
// @include        *://*.vkuservideo.net/*
// @version        1.1.5
// @run-at         document-start
// @grant          none
// ==/UserScript==

var DEBUG = false;
var mediaData = {};
var consoleLog = function(){window.console.log.apply(this, arguments);},
	consoleError = function(){window.console.error.apply(this, arguments);},
	blank = function(){},
	cerr = consoleError,
	clog = (DEBUG ? consoleLog : blank);
var clog2 = consoleLog,
	vkDOMAINS = ['vk.com', 'vk-cdn.com', 'vk-cdn.net', 'userapi.com', 'vkuseraudio.net', 'vkuservideo.net'],
	storageKEY = 'vk-domains',
	scriptName = 'Vk Media Downloader',
	scriptVersion = '1.1.5';
(function(window){
	if( window.parent !== window.self )
	{
		clog("[window:child] url: ", location.href);
		clog("[window:child] readyState: ", document.readyState);
		DOMReady(function(){
			var i = executor();
			if( i )
				cerr("[window:child] invalid action, code: ", i, location.origin);
			else
				clog("[window:child] action succeed, code: ", i, location.origin);
		});
	}
	else if(location.hostname === 'vk.com' )
	{
		clog2("[window:parent] start " + scriptName + " v" + scriptVersion);
		document.addEventListener('readystatechange', main, false);
		window.addEventListener('keydown', handleKeys);
	}else{
		clog("[window:parent] origin: ", location.origin);
		clog("[window:parent] script stoped");
	}
})(window);
function DOMReady(callback)
{
	if(!callback) return;
	switch(document.readyState)
	{
		case "loading":
		document.addEventListener('DOMContentLoaded', callback);
		break;
		case "interactive":
		case "complete":
		callback();
		break;
	}
}
function executor()
{
	clog("[executor:document] readyState: ", document.readyState);
	if( window.parent === window.self )
		return 1;
	var s = location.hash, t, r, e;
	var l = ['namespace', 'type', 'media', 'mid', 'id', 'hd', 'fid'];
	if( (t = s.indexOf('#VkMD::')) !== 0 )
		return 2;
	s = s.slice(t + 7);
	try{
		t = JSON.parse(decodeURIComponent(s));
	}catch(er){
		cerr(er);
		return 3;
	}
	if( t.action !== 'request' || t.namespace !== 'VkMD' )
		return 4;
	if( t.id !== undefined )
		t.mid = t.id;
	else if( t.mid !== undefined )
		t.id = t.mid;
	switch(t.type)
	{
		case 'download':
		e = getLoc(t.src, 'pathname').match(/\.([^\.\/]+)$/)[1];
		var a = document.createElement('a');
		a.href = t.src;
		t.name = t.name || (t.md_title + (t.q ? '.' + t.q + 'p': ''));
		a.setAttribute('download', (t.artist ? (t.artist + ' - '): '') + t.name + '.' + e);
		document.body.appendChild(a);
		a.innerHTML = t.name;
		a.click();
		clog("[executor:download] ", a);
		a.parentNode.removeChild(a);
		// making response..
		s = {};
		for( e of l )
		{
			if( t[e] !== undefined )
				s[e] = t[e];
		}
		s.action = 'response';
		s.result = 1;
		s.id = s.mid;
		setTimeout(function(){
			window.parent.postMessage(s, '*');
		}, 200);
		break;
		case 'size':
		if( window.XMLHttpRequest )
			r = new XMLHttpRequest();
		else if( window.ActiveXObject )
			r = new ActiveXObject('Msxml3.XMLHTTP');
		if( !r )
			throw new Error("[iframe:size] can't create XMLHttpRequest");
		clog("[executor:size] ", r);
		r.open('HEAD', t.src, !0);
		r._data = {};
		for( e of l )
		{
			if( t[e] !== undefined )
				r._data[e] = t[e];
		}
		r._data.action = 'response';
		r.onload = function(e)
		{
			var t = e.target,
				n = t.getResponseHeader('Content-Length'),
				d = t._data;
			d.result = parseInt(n, 10);
			d.id = d.mid;
			window.parent.postMessage(d, '*');
		};
		r.send();
		break;
		default: return 5;
	}
	return 0;
}
function main(e)
{
	clog("main..", this.readyState);
	switch(this.readyState)
	{
		case 'interactive':
		new Promise(function(f, e){
			window.VkMD = createVkMD();
			f(window.VkMD);
		}).then(function(vkmd){
			window.tooltip = window.tooltip || createTooltip('1');
			return startMediaObserver();
		}).then(function(observer){
			return getId();
		}).then(function(id){
			on$(window, 'message', recieveMessage);
			return createIcon();
		}).then(function(icon){
			return createNewCss(icon);
		}).catch(function(e){
			cerr("[main] error: ", e);
		});
		break;
		case 'complete':
		[].forEach.call($$('.video_item'), function(p){
			video_item(p);
		});
		video_box_wrap(_$('#video_player'));
		break;
	}
}
function handleKeys(e)
{
	if( !e.altKey )
		return;
	var charCode = e.which || e.keyCode;
	var c = String.fromCharCode(charCode).toUpperCase();
	switch(c)
	{
		case 'A':
			getMediaURLsAll();
			console.log('[getMediaURLsAll]');
			break;
		case 'S':
			saveMediaData();
			console.log('[saveMediaData]');
			break;
		case 'P':
			viewProperties();
			break;
		default:
			console.error("error keypress, key ", charCode, c);
	}
}
function viewProperties(){}
function startMediaObserver()
{
	var cl0 = ['audio_row__actions', '_audio_row__actions'],
	cl1 = ['video_item', '_video_item'],
	cl2 = 'mv_playlist',
	cl3 = 'mv_info_narrow_column',
	cl4 = 'video_box_wrap';
	var MutationObserver = window.MutationObserver || window.WebkitMutationObserver,
	o = new MutationObserver(function(m, o){
		try{
		for( var n of m )
		{
			for( var p of n.addedNodes )
			{
				if( p.nodeType != 1 )
					continue;
				else if( hasClassAll(p, cl0) != -1 ) // 'audio_row__actions'
					audio_row(p);
				else if( hasClassAll(p, cl1) != -1 ) // 'video_item'
					video_item(p);
				else if( hasClass(p, cl2) ) //'mv_playlist'
					mv_playlist(p);
				else if( hasClass(p, cl3) ) //'mv_info_narrow_column'
					mv_recom(p);
				else if( p.id && p.id == 'video_player' )
				{
					var t = p;
					setTimeout(function(){
						video_box_wrap(t);
					}, 2000);
				}
			}
		}
		}catch(err){cerr(err);}
	});
	o.observe(_$('body'), {
		'childList': !0,
		'subtree': !0
	});
	return o;
}
function audio_row(p)
{
	var e = ce$('button', {
		'class': 'audio_row__action _audio_row__action audio_row__download',
		'title': 'Скачать аудиозапись',
	});
	p.appendChild(e);
	tooltip.attach({el: e, option: 'top-right', media: 'audio', shiftY: 5});
	on$(e, 'mouseover', audioSizeRequest);
	on$(e, 'click', audioDownloadRequest );
}
function video_item(p)
{
	var e = se$('<div id="download"><div class="icon icon_download" title="Скачать видеозапись"></div></div>');
	p.classList.add('video_can_download');
	_$('.video_thumb_actions', p).appendChild(e);
	var id = attr$(p, 'data-id');
	attr$(e, 'data-id', id);
	tooltip.attach({el: e, option: 'bottom-right', media: 'video', 'data-id': id});
	on$(e, 'mouseenter', videoSourceRequest);
	on$(e, 'click', stopEvent);
}
function mv_playlist(p)
{
	var l = $$('.mv_playlist_item_thumb', p), e, id;
	for( var el of l )
	{
		e = se$('<div class="mv_playlist_item_download"></div>');
		id = attr$(el.parentNode, 'data-vid');
		attr$(e, {
			'data-id': id,
			'data-media': 'video',
			'title': 'Скачать видеозапись'
		});
		el.appendChild(e);
		tooltip.attach({el: e, option: 'bottom-left', media: 'video', 'data-id': id});
		on$(e, 'mouseenter', videoSourceRequest);
		on$(e, 'click', stopEvent);
	}
}
function mv_recom(p)
{
	var l = $$('.mv_recom_item_thumb', p), e, id;
	for( var el of l )
	{
		e = se$('<div class="mv_recom_item_download"></div>');
		id = el.pathname.replace('/video', '');
		attr$(e, {
			'data-id': id,
			'data-media': 'video',
			'title': 'Скачать видеозапись'
		});
		el.appendChild(e);
		tooltip.attach({el: e, option: 'bottom-left', media: 'video', 'data-id': id});
		on$(e, 'mouseenter', videoSourceRequest);
		on$(e, 'click', stopEvent);
	}
}
function video_box_wrap(p)
{
	var el = _$('.videoplayer_controls', p), e, n, id;
	if( el && !_$('.videoplayer_btn_download', el) )
	{
		e = se$('<div class="videoplayer_controls_item videoplayer_btn videoplayer_btn_download" role="button" tabindex="0"></div>');
		n = _$('.videoplayer_btn_fullscreen', el);
		el.insertBefore(e, n ? n.nextSibling: n);
		id = p.parentNode.id.replace('video_box_wrap', '');
		attr$(e, {
			'data-id': id,
			'data-media': 'video',
		});
		tooltip.attach({el: e, option: 'top-right', media: 'video', 'data-id': id});
		on$(e, 'mouseenter', videoSourceRequest);
		on$(e, 'click', stopEvent);
	}
}
var forEach$ = Array.prototype.forEach;
function hasClassAll(e, l)
{
	for(var i = 0; i < l.length; ++i)
	{
		if( hasClass(e, l[i]) )
			return i;
	}
	return -1;
}
function createNewCss(icon)
{
	var s = css$(`
	.my-test-class,
	.audio_row__download ,
	._audio_row__download {
		background: url(${icon.color("#808080")}) no-repeat !important;
		position: relative;
		top: 5px;
	}
	.video_item.video_can_download #download{
		display: inline-block;
	}
	.my-test-class ,
	.video_thumb_actions .icon.icon_download {
		background: url(${icon.color('#ffffff')}) no-repeat !important;
	}
	.my-test-class ,
	.videoplayer_btn_download {
		background-image: url(${icon.color('#ffffff')});
		background-repeat: no-repeat;
		background-position: 3px;
		border-radius: 3px;
		left: 0;
		bottom: 0;
		z-index: 10;
		width: 18px;
		height: 18px;
		padding: 2px;
		transform: scale(1.1);
	}
	.mv_recom_item_download ,
	.mv_playlist_item_download {
		background-image: url(${icon.color('#ffffff')});
		background-repeat: no-repeat;
		background-color: #000;
		background-position: 3px;
		border-radius: 3px;
		position: absolute;
		left: 0;
		bottom: 0;
		z-index: 10;
		width: 18px;
		height: 18px;
		padding: 2px;
		opacity: 0.7;
	}
	.mv_recom_item_download:hover ,
	.mv_playlist_item_download:hover {
		opacity: 1 !important;
	}
	.media-hd:after{
		content: 'HD';
		padding-left: 3px;
		opacity: 0.7;
	}
	.vkmd-tooltip-section {
		cursor: pointer;
		padding: 5px;
		opacity: 0.8;
	}
	.vkmd-tooltip-section:hover {
		opacity: 1;
		border-style: solid;
		border-width: 1px;
		padding: 4px;
	}
	.vkmd-tooltip-section[data-media="audio"] {
		opacity: 1;
	}
	`);
	s.className = 'my-test-class';
	return s;
}
function recieveMessage(e)
{
	var n = getMessageData(e);
	if( !n )
		return;
	try{
	var t, q, d;
	clog("[recieveMessage] : ", n);
	switch(n.action)
	{
		case 'request':
		t = VkMD[n.media].__get__(n.id);
		switch(n.media)
		{
			case 'audio':
			if( t && t.src )
			{
				t = extend({}, t, n);
				t.href = mediaFakePage(t);
				callExecutor(t.href, t.id, t.type);
				return t;
			}
			VkMD.audio.__ajax__(n.id).then(function(r){
				var t = r[0][0];
				clog("[getAudio:response]: ", t);
				VkMD.audio.__set__(n.id, t);
				t = extend({}, t, n);
				t.href = mediaFakePage(t);
				callExecutor(t.href, t.id, t.type);
				return t;
			}).catch(function(er){
				cerr("error [VkMD.audio:ajax] ", er);
			});
			break;
			case 'video':
			q = VkMD.video.__get__(n.id, n.hd);
			if( q && q.src )
			{
				q = extend({}, q, n);
				for(let k of ['md_author', 'md_title', 'oid', 'vid', 'duration'])
					q[k] = t[k];
				q.href = mediaFakePage(q);
				callExecutor(q.href, q.id, q.type + q.hd);
				return q;
			}
			VkMD.video.__ajax__(n.id).then(function(r){
				var t = r, q;
				clog("[getVideo:response]: ", t);
				VkMD.video.__set__(n.id, t);
				q = extend({}, q, n);
				for(let k of ['md_author', 'md_title', 'oid', 'vid', 'duration'])
					q[k] = t[k];
				q.href = mediaFakePage(q);
				callExecutor(q.href, q.id, q.type + q.hd);
				return q;
			}).catch(function(er){
				cerr("error [VkMD.video:ajax] ", er);
			});
			break;
		}
		break;
		case 'response':
		switch(n.type)
		{
			case 'size':
			switch(n.media)
			{
				case 'audio':
				VkMD.audio.__set__(n.id, 'size', n.result);
				break;
				case 'video':
				VkMD.video.__set__(n.id, n.hd, 'size', n.result);
				break;
			}
			d = n.result/(1024*1024);
			tooltip.show();
			break;
			case 'download':
			break;
			default:
			throw new Error("[recieveMessage:response] undefined type of response");
		}
		setTimeout(function(){
			re$(_$('#frame_' + n.type + (n.hd || '') + '_' + n.id));
		}, 5000);
	}
	}catch(er){cerr(er);}
}
function getMessageData(e)
{
	var n, d;
	switch(typeof e.data)
	{
		case 'object':
		if( !e.data.media || !e.data.action || e.data.namespace != 'VkMD' )
			return null;
		n = extend({}, e.data);
		break;
		default:
		cerr("Error [recieveMessage] data: ", e.data);
		return null;
	}
	n.origin = e.origin;
	return n;
}
function callExecutor(url, id, type)
{
	addToList(url);
	return _$('body').appendChild(
		ce$('iframe',
		{src: url,id: 'frame_' + type + '_' + id,},
		{width: '1px',height: '1px',visibility: 'hidden',})
	);
}
var audioSizeRequest = createAudioHandler('size');
var audioDownloadRequest = createAudioHandler('download');
var videoSourceRequest = function(e){
	e.stopPropagation();
	e.preventDefault();
	var t = this,
		id = attr$(t, 'data-id');
	if( !id )
	{
		cerr("[videoSourceRequest] no data-id, element: ", t);
		return;
	}
	var src_req = VkMD.video.__get__(id, 'source_request');
	if( src_req )
		return;
	VkMD.video.__set__(id, 'source_request', true);
	getVideo(id).then(function(r){
		VkMD.video.__set__(id, r);
		tooltip.show();
	}).catch(function(er){
		cerr(er);
	});
};
var stopEvent = function(e){
	e.stopPropagation();
	e.preventDefault();
};
function createAudioHandler(msg)
{
	return function(ev){
		ev.stopPropagation();
		var el = this, fs, id, audio, match;
		if( this.id )
			id = attr$(this, 'data-id');
		else{
			while( el && !hasClass(el, 'audio_row') )
				el = el.parentNode;
			id = attr$(el, 'data-full-id');
			try {
				audio = JSON.parse(attr$(el, 'data-audio'));
				id = id + '_' + audio[13].match(/\/\/[0-9a-z]+/g).map(function(k){ return k.slice(2); }).join('_');
			} catch (err) {
				cerr(err);
			}
			attr$(this, 'data-id', id);
		}
		if( !VkMD.audio.__get__(id, msg + '_request') )
		{
			try{
			VkMD.audio.__set__(id, msg + '_request', true);
			}catch(err){cerr(err);}
			window.postMessage({
				type: msg,
				media: 'audio',
				id: id,
				namespace: 'VkMD',
				action: 'request',
			}, '*');
		}
		attr$(this, 'title', VkMD.audio.__getText__(id) );
		fs = VkMD.audio.__get__(id, 'size');
	};
}
function extend(e)
{
	e = e || {};
	for( var i = 1; i < arguments.length; ++i )
		if( arguments[i] ) for( var k in arguments[i] )
				if( arguments[i].hasOwnProperty(k) ) e[k] = arguments[i][k];
	return e;
}
function makeRequest(t)
{
	var n = extend({}, {
		method: 'GET',
		url: '/',
		data: null,
		headers: {},
	}, t);
	return new Promise(function(f, e){
		var req, s, k;
		clog("[makeRequest] ", extend({}, n));
		if(typeof n.data == 'object' )
		{
			s = '';
			for(k in n.data)
			{
				if( n.data.hasOwnProperty(k) )
					s += (k + '=' + n.data[k] + '&');
			}
			n.data = s.length ? s.slice(0, -1) : null;
		}
		if( window.XMLHttpRequest )
			req = new XMLHttpRequest();
		else if( window.ActiveXObject )
		{
			for( s of ['Msxml3.XMLHTTP', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.3.0'] )
			{
				try{
					req = new ActiveXObject(s);
				}catch(er){continue;}
			}
			if(!req)
				throw new Error("[makeRequest] can't create ActiveXObject");
		}else
			throw new Error("[makeRequest] can't create Request");
		req.open(n.method, n.url, !0);
		for(k in n.headers)
		{
			if( n.headers.hasOwnProperty(k) )
				req.setRequestHeader(k, n.headers[k]);
		}
		req.onload = function(response){
			var t = response.target, p;
			clog("[makeRequest:response] status: ", t.status, t.statusText);
			clog("[makeRequest:response] length: ", t.response.length);
			if( t.status == 200 )
				f(t.response);
			else{
				p = new Error(t.statusText);
				p.code = t.status;
				e(p);
			}
		}, req.onerror = function(response){
			e( new Error("[makeRequest:response] network error") );
		}, req.send(n.data);
		clog("[makeRequest] xhr: ", req);
	});
}
function getJSON(t)
{
	return makeRequest(t).then(function(r){
		var result = [], p = r.indexOf('<!json>'), p2, txt;
		while( p != -1 )
		{
			p2 = r.indexOf('<!>', p+7);
			if( p2 == -1 )
				break;
			result.push(JSON.parse(r.slice(p+7, p2)));
			p = r.indexOf('<!json>', p2);
		}
		clog("[getJSON] result.length: ", result.length);
		clog("[getJSON] result: ", result);
		return result;
	});
}
var keys$ = Object.getOwnPropertyNames;
function unmask$(u)
{
	VkMD.uid = window.vkId;
	if( typeof VkMD.audioUnmaskSource !== 'function' )
		createUnmask();
	var t = VkMD.audioUnmaskSource(u);
	return t;
}
	
function getAudioJSON(ids)
{
	return getJSON({
		method: 'POST',
		url: 'https://vk.com/al_audio.php',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded',
			'X-Requested-With': 'XMLHttpRequest',
		},
		data: {
			al: 1,
			act: 'reload_audio',
			ids: ids.join(','),
		},
	});
}
function getAudio(ids)
{
	var s = getAudioJSON(ids);
	s.then(function(r){
		var k = r[1] ? parseInt(keys$(r[1])[0], 10) : null;
		if( !k )
			throw new Error("[getAudio:response] vk.id not found");
		r[1] = k;
		return r;
	});
	var l = ['aid', 'oid', 'url', 'name', 'artist', 'duration'];
	ids.forEach(function(e, i){
		s = s.then(function(r){
			window.__textarea__ = window.__textarea__ || ce$('textarea');
			console.log('getAudio::r (0, ' + i + ')', r);
			var t = {}, a = r[0][i], k, u;
			for( k = 0; k < l.length; ++k )
			{
				u = l[k];
				__textarea__.innerHTML = a[k];
				t[u] = __textarea__.value;
			}
			t.src = unmask$(t.url);
			t.uid = r[1];
			t.mid = t.oid + '_' + t.aid;
			r[0][i] = t;
			return r;
		});
	});
	return s;
}
function getVideoJSON(id)
{
	return getJSON({
		method: 'POST',
		url: 'https://vk.com/al_video.php',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded',
			'X-Requested-With': 'XMLHttpRequest',
		},
		data: {
			act: 'show',
			al: 1,
			al_d: 0,
			autoplay: 0,
			list: '',
			module: '',
			video: id || '',
		},
	});
}
function getVideo(id)
{
	return getVideoJSON(id).then(function(r){
		var o = r[0].player.params[0];
		clog("[getVideo:response] raw data: ", o);
		return o;
	}).catch(function(e){
		cerr("error [getVideo:response]: ", e);
	}).then(function(r){
		var o = {}, k, m;
		const l = ['oid', 'vid', 'viewer_id', 'duration', 'md_title', 'md_author', 'add_hash', 'action_hash', 'embed_hash'];
		for(k of l)
			o[k] = r[k];
		o.quality = [];
		for(k in r)
		{
			if( (m = k.match(/url(\d+)/)) )
			{
				o.quality.push(m[1]);
				o[k] = {
					src: r[k],
					q: m[1],
				};
			}
		}
		return o;
	}).then(function(r){
		return r;
	}).catch(function(e){
		cerr("error [getVideo:response]: ", e);
	});
}
function getLoc(u, p)
{
	if( !u )
		return null;
	window.__link__ = window.__link__ || ce$('a');
	__link__.href = u;
	return __link__[p||'href'];
}
function getId()
{
	if( window.vkId )
		return Promise.resolve(window.vkId);
	return getAudio([]).then(function(r){
		clog("[getId] vk.id: ", r[1]);
		return (window.vkId = r[1]);
	}).catch(function(error){
		cerr("[getId] ", error);
	}).then(function(){
		if(window.vkId)
			return window.vkId;
		else if( window.vk && window.vk.id )
			return window.vkId = window.vk.id;
		return new Promise(function(resolve){
			var onload = function(){
				if( window.vk && window.vk.id )
					resolve(window.vkId = window.vk.id);
				else
					cerr("[getId] WTF!: vk.id not found");
				this.removeEventListener("load", onload);
			};
			DOMReady(function(){
				var scripts = $$("script:not([src])");
				for(var i = 0, len = scripts.length, script; i < len; ++i)
				{
					script = scripts[i];
					if( script.innerHTML.indexOf('var vk') != -1 )
						script.addEventListener("load", onload);
				}
			});
		});
	});
}
var map$ = Array.prototype.map;
var slice$ = Array.prototype.slice;
function getAudioId(e){
	var id = attr$(e, 'data-full-id');
	var audio;
	var match;
	try {
		audio = JSON.parse(attr$(e, 'data-audio'));
		id = id + '_' + audio[13].match(/\/\/[0-9a-z]+/g).map(function(k){ return k.slice(2); }).join('_');
	} catch (err) {
		cerr(err);
	}
	return id;
}
function getAllAudio(begin, end)
{
	var audio = $$('.audio_row'),
		len = audio.length,
		audioData = [],
		ids = [], i;
	audio = slice$.call(audio, begin||0, end || len);
	console.log("audios: ", audio.length, audio);
	len = audio.length;
	for( i = 0; i < len; i += 10 )
		ids.push(slice$.call(audio, i, i+10).map(getAudioId));
	return ids.reduce(function(s, id, i){
		return s.then(function(){
			return getAudio( id );
		}).then(function(r){
			id.forEach((_id, i) => {
				setMediaData(_id, r[0][i]);
			});
			audioData.push(r[0]);
			return audioData;
		}).catch(function(err){
			cerr("error [getAllAudio]: ids = ", i, id, err);
			return audioData;
		});
	}, Promise.resolve(audioData)).then(function(r){
		console.log("all audios: ", r);
		return r;
	});
}
function getAllVideo(begin, end)
{
	// var cl0 = ['audio_row__actions', '_audio_row__actions'],
	var cl1 = [
		'video_box_wrap',
		'video_item',
		'mv_playlist',
		'mv_info_narrow_column',
	];
	var videos = $$( '.' + cl1.join(', .') );
	begin = begin || 0;
	end = end || videos.length;
	videos = slice$.call(videos);
	console.log("videos: ", videos.length, videos);
	var videoData = [];
	var ids = [];
	for( var i = begin; i < end; i += 10 )
		ids.push(slice$.call(videos, i, i+10).map(v => attr$(v, 'data-id')));
	console.log('video ids: ', ids);
	return ids.reduce(function(s, id, i){
		return s
		.then(function(){
			return id.reduce(function(_s, _id, _i){
				return _s
				.then(function(){
					return getVideo(_id);
				}).then(function(r){
					setMediaData(_id, r);
					videoData.push(r);
					return videoData;
				}).catch(function(err){
					cerr("[getAllVideo] error: id = (", i, _i, ": ", _id, ")", err);
					return videoData;
				});
			}, Promise.resolve(videoData))
			.then(function(r){
				return new Promise((resolve) => {
					var msec = 3000;
					console.log(`waiting ${msec/1000} secs...`);
					setTimeout(() => {
						resolve(r);
					}, msec);
				});
			});
		});
	}, Promise.resolve(videoData));
}
function getMediaURLsAll()
{
	getAllAudio()
	.then(function(r){
		console.log("[getAllAudio] result: ", r);
		return r;
	}).catch(function(err){
		cerr("[getAllAudio] error: ", err);
	});
	getAllVideo()
	.then(function(r){
		console.log("[getAllVideos] all videos: ", r);
		return r;
	}).catch(function(err){
		cerr("[getAllVideo] error: ", err);
	});
}
function mediaFakePage(t)
{
	return t.src.split('?')[0] + '.html#VkMD::' + encodeURIComponent(JSON.stringify(t));
}

function createUnmask()
{
	"use strict";
    function i() {
        return window.wbopen && ~(window.open + "").indexOf("wbopen")
    }
    function o(t) {
        if (!i() && ~t.indexOf("audio_api_unavailable")) {
            var e = t.split("?extra=")[1].split("#"),
                o = "" === e[1] ? "" : a(e[1]);
            if (e = a(e[0]), "string" != typeof o || !e) return t;
            o = o ? o.split(String.fromCharCode(9)) : [];
            for (var s, r, n = o.length; n--;) {
                if (r = o[n].split(String.fromCharCode(11)), s = r.splice(0, 1, e)[0], !l[s]) return t;
                e = l[s].apply(null, r)
            }
            if (e && "http" === e.substr(0, 4)) return e
        }
        return t
    }
    function a(t) {
        if (!t || t.length % 4 == 1) return !1;
        for (var e, i, o = 0, a = 0, s = ""; i = t.charAt(a++);) i = r.indexOf(i), ~i && (e = o % 4 ? 64 * e + i : i, o++ % 4) && (s += String.fromCharCode(
            255 & e >> (-2 * o & 6)));
        return s
    }
    function s(t, e) {
        var i = t.length,
            o = [];
        if (i) {
            var a = i;
            for (e = Math.abs(e); a--;) e = (i * (a + 1) ^ e + a) % i, o[a] = e
        }
        return o
    }
	VkMD.audioUnmaskSource = o;
    var r = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=",
        l = {
            v: function(t) {
                return t.split("").reverse().join("")
            },
            r: function(t, e) {
                t = t.split("");
                for (var i, o = r + r, a = t.length; a--;) i = o.indexOf(t[a]), ~i && (t[a] = o.substr(i - e, 1));
                return t.join("")
            },
            s: function(t, e) {
                var i = t.length;
                if (i) {
                    var o = s(t, e),
                        a = 0;
                    for (t = t.split(""); ++a < i;) t[a] = t.splice(o[i - 1 - a], 1, t[a])[0];
                    t = t.join("")
                }
                return t
            },
            i: function(t, e) {
				return l.s(t, e ^ VkMD.uid)
            },
            x: function(t, e) {
                var i = [];
                return e = e.charCodeAt(0), each(t.split(""), function(t, o) {
                    i.push(String.fromCharCode(o.charCodeAt(0) ^ e))
                }), i.join("")
            }
        }
}

function createVkMD()
{
	var retVal = {
		data: {
			'audio': {
				get time(){return 3e5;},
				ttHtml: getAudioTTHtml,
			},
			'video': {
				get time(){return 6e5;},
				ttHtml: getVideoTTHtml,
			},
			get time(){return 3e5;},
		},
		get audio(){return this.data.audio;},
		get video(){return this.data.video;},
		init: function(){
			Object.defineProperties(this.data.audio, get_properties('audio') );
			Object.defineProperties(this.data.video, get_properties('video') );
		},
	};
	retVal.init();
	return retVal;
}
function ttoverHandler(ev)
{
	ev.preventDefault();
	var t = ev.target;
	if( attr$(t, 'data-media') != 'video' )
		return;
	var q = attr$(t, 'data-quality'),
		id = attr$(t, 'data-id'),
		u = VkMD.video.__get__(id, 'url' + q);
	if( u.size_request )
		return;
	VkMD.video.__set__(id, 'url' + q, 'size_request', true);
	window.postMessage({
		media: 'video',
		action: 'request',
		type: 'size',
		id: id,
		hd: 'url' + q,
		namespace: 'VkMD',
	}, '*');
}
function ttclickHandler(ev)
{
	if (!ev.ctrlKey) {
		ev.stopPropagation();
		ev.preventDefault();
	}
	var _t = ev.target,
		t = _t,
		it = 0;
	while( t.tagName != 'SECTION' && it++ < 10 )
		t = t.parentNode;
	if( t.tagName != 'SECTION' )
	{
		console.error('[ttclickHandler] Error: <section/> not found, event.target: ', _t);
		return;
	}
	if( attr$(t, 'data-media') != 'video' )
		return;
	var q = attr$(t, 'data-quality'),
		id = attr$(t, 'data-id'),
		u = VkMD.video.__get__(id, 'url' + q);
	if( u.download_request )
		return;
	VkMD.video.__set__(id, 'url' + q, 'download_request', true);
	window.postMessage({
		media: 'video',
		action: 'request',
		type: 'download',
		id: id,
		hd: 'url' + q,
		namespace: 'VkMD',
	}, '*');
}
function getAudioTTHtml(key)
{
	var v = this[key], html = 'Cкачать аудиозапись', d, b, e, title;
	if( !v || !v.size )
		return html;
	html = '';
	d = v.size;
	e = v.src.match(/^((https?:)?\/\/(([^\/\?\#]+)(:\d+)?))([^\?\#]+)?([^\#]+)?(.*)$/);
	try{
		e = e[6];// pathname
		e = e.match(/\.([a-z0-9]+)$/)[1];
	} catch (error) {
		console.error('[getAudioTTHtml] error: ', error);
		e = 'mp3';
	}
	title = v.artist ? `${v.artist} - ${v.name}` : '';
	b = Math.floor(v.size/(128*v.duration));
	html = '<section class="vkmd-tooltip-section data-media="audio">' +
		`<a href="${v.src || null}" download="${title}.${e}" title="${title}.${e}" style="text-decoration: none; color: #fff;">${title}</a>` +
		'</section>';
	html += '<section class="vkmd-tooltip-section" data-media="audio">Размер файла: ' + (d/(1024*1024)).toFixed(1) + ' MB</section>';
	html += `<section class="vkmd-tooltip-section ${b && b > 300 ? 'media-hd' : ''}" data-media="audio">Качество: ~${b} kB/s</section>`;
	// html += '<section class="vkmd-tooltip-section' + (b && b > 300 ? ' media-hd': '') + '" data-media="audio">Качество: ~' + b + ' kB/s</section>';
	return html;
}
function getVideoTTHtml(key)
{
	var v = this[key], html = '', u, d, e, title;
	if( !v || !v.quality )
		return 'Скачать видеозапись';
	for(var k of v.quality)
	{
		k = parseInt(k, 10);
		u = v['url' + k];
		d = u.size || 0;
		try {
			e = getLoc(u.src, 'pathname').match(/\.([a-z0-9]+)$/)[1];
		} catch (error) {
			console.error('[getVideoTTHtml] error: ', error);
			e = 'mp4';
		}
		title = `${v.md_title || 'video'}.${k}p.${e}`;
		html += '' +
		'<section class="vkmd-tooltip-section' + (k > 480 ? ' media-hd': '' ) + '" ' +
			'data-media="video" data-id="' + key + '" data-quality="' + k + '"' +
			'title="' + (v.md_title ? v.md_title : '') + '"' +
		'>' +
		`<a href="${u.src}" download="${title}" title="${v.md_title || ''}" style="color: #fff; text-decoration: none;">` +
		+ k + 'p' + (d ? ' (' + (d/(1024*1024)).toFixed(1) + ' MB)': '') + '</a></section>';
	}
	return html;
}
function setMediaData(key, data)
{
	var type, url,
		mdata = mediaData[key] || {};
		mediaNum = mediaData.mediaNum || 0,
		totals = mediaData.totals || 0;
	if( data.aid !== undefined && data.src )
	{
		// audio
		if( !mediaData[key] )
		{
			++totals;
			++mediaNum;
		}
		type = getLoc(data.src, 'pathname').match(/\.([^\.\/]+)$/)[1];
		mediaData[key] = {
			saved: !!mdata.saved,
			url: data.src,
			name: `${data.artist} - ${data.name}.${type}`,
		};
	}
	else if( data.vid !== undefined && data.quality && data.quality.length )
	{
		// video
		if( !mediaData[key] )
		{
			totals += data.quality.length;
			++mediaNum;
		}
		mediaData[key] = {}
		for( var q of data.quality )
		{
			url = data[`url${q}`].src;
			type = getLoc(url, 'pathname').match(/\.([^\.\/]+)$/)[1];
			mediaData[key][q] = {
				saved: mdata[q] ? !!mdata[q].saved : false,
				url: data[`url${q}`].src,
				name: `${data.md_title}.${q}.${type}`,
			};
		}
	} else {
		console.error("[setMediaData] source url not found, media id: ", key, ", media data: ", data);
	}
	mediaData.totals = totals;
	mediaData.mediaNum = mediaNum;
	console.log("MEDIA DATA (totals, media): ", mediaData.totals, mediaData.mediaNum, mediaData);
}
function getAria2Text(data)
{
	return `${data.url} --out="${data.name.replace(/[\/]+/g, '-')}"`;
}
function saveMediaData()
{
	var keys = Object.keys(mediaData),
		data = null,
		savedNum = mediaData.savedNum || 0,
		qualities = null,
		text = '';
	for( var key of keys )
	{
		data = mediaData[key];
		if( typeof data !== 'object' )
			continue;
		else if( data && !data.url )
		{
			// video
			qualities = Object.keys(data);
			var q = qualities[qualities.length - 1];
			text = `${text}${getAria2Text(data[q])}\r\n`;
			data[q].saved = true;
			++savedNum;
			/*
			for( var q of qualities )
			{
				if( !data[q].saved )
				{
					text = `${text}${getAria2Text(data[q])}\r\n`;
					data[q].saved = true;
					++savedNum;
				}
			}
			*/
		}
		else if( data && data.url )
		{
			// audio
			text = `${text}${getAria2Text(data)}\r\n`;
			data.saved = true;
			++savedNum;
		}
	}
	mediaData.savedNum = savedNum;
	console.log("MEDIA DATA (saved) : ", mediaData.savedNum, mediaData);
	if( text )
		saveTextFile(text, 'vk.media.downloader.urls.txt');
}
function saveTextFile(text, filename)
{
	var blob = new Blob([text], {type: 'text/plain'}),
		wu = window.URL || window.webkitURL,
		resource = wu.createObjectURL(blob);
	setTimeout(function(){
		wu.revokeObjectURL(resource);
	}, 1e3);
	var link = document.createElement('a');
	link.href = resource;
	link.download = filename;
	document.body.appendChild(link);
	link.click();
	document.body.removeChild(link);
}
function get_properties(t)
{
	var f, tt;
	switch(t)
	{
		case 'audio':
		f = function(ids){ return getAudio(ids); };
		tt = 'Скачать аудиозапись';
		break;
		case 'video':
		f = function(id){ return getVideo(id); };
		tt = 'Скачать видеозапись';
		break;
		default:
		throw new Error("[createVkMD:get_properties] invalid key: " + m);
	}
	return {
		'__ajax__': {
			value: function(ids){
				if(typeof ids === 'string' || ids.length === undefined )
					ids = [ids];
				return f(ids);
			},
			enumerable: false,
			configurable: true,
		},
		'__get__': {
			value: function(key, key2, key3){
				var v = this[key];
				if( key2 !== undefined && v )
					v = v[key2];
				if( key3 !== undefined && v )
					v = v[key3];
				return v;
			},
			enumerable: false,
			configurable: true,
		},
		'__set__': {
			value: function(key, val, val2, val3){
				var v;
				if( !val )
					return;
				else if(typeof val == 'object')
				{
					this[key] = extend(this[key], val);
					this[key]._time = Date.now();
					console.log('__set__ media[' + key + '] ', this[key], val);
					setMediaData(key, val);
				}else{
					v = this[key] = this[key] || {};
					if( val3 === undefined )
						v[val] = val2;
					else if( typeof val2 == 'string' )
					{
						v = this[key][val] = this[key][val] || {};
						v[val2] = val3;
					}
				}
			},
			enumerable: false,
			configurable: true,
		},
		'__outdated__': {
			value: function(key){
				var v = this[key];
				return !v || (Date.now() - v._time) > this.time;
			},
			enumerable: false,
			configurable: true,
		},
		'__getText__': {
			value: function(key){
				var v = this.__get__(key), d;
				if( v && v.size )
				{
					d = v.size/(1024*1024);
					return tt + ' (' + d.toFixed(1) + ' MB, ~'
						+ Math.floor(v.size/(v.duration*128)) + ' kB/s)';
				}
				return tt;
			},
			enumerable: false,
			configurable: true,
		},
	};
}
function createIcon()
{
	return {
		index: 824,
		data: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQzMy41IDQzMy41IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0MzMuNSA0MzMuNTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnIGlkPSJmaWxlLWRvd25sb2FkIj4KCQk8cGF0aCBkPSJNMzk1LjI1LDE1M2gtMTAyVjBoLTE1M3YxNTNoLTEwMmwxNzguNSwxNzguNUwzOTUuMjUsMTUzeiBNMzguMjUsMzgyLjV2NTFoMzU3di01MUgzOC4yNXoiIGZpbGw9IiM4MDgwODAiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K",
		prefix: "data:image/svg+xml;utf8;base64,",
		color: function(c){
			if( !c || c.length != 7 )
				c = '#808080';
			c = btoa('"' + c + '"');
			return this.prefix + this.data.slice(0, this.index) + c + this.data.slice(this.index + c.length);
		},
	};
}
function on$(n, e, c, b){n.addEventListener(e, c, !!b);}
//function off$(n, e, c, b){n.removeEventListener(e, c, !!b);}
function _$(s, n){return (n||document).querySelector(s);}
function $$(s, n){return (n||document).querySelectorAll(s);}
function e$(s, n){return typeof s == 'string' ? _$(s, n): s;}
//function e$$(s, n){return typeof s == 'string' ? $$(s, n): s;}
function re$(e){if(e && e.parentNode) return e.parentNode.removeChild(e); return e;}
function se$(t){return ce$('div', null, null, t).firstChild;}
function hasClass(e, c){return e.classList.contains(c);}
function ce$(t, a, s, h){
	var e = document.createElement(t), k;
	for(k in a)
		e.setAttribute(k, a[k]);
	for(k in s)
		e.style[k] = s[k];
	e.innerHTML = h || '';
	return e;
}
function attr$(e, a, n)
{
	if( !e )
		return;
	switch(typeof a)
	{
		case 'string':
		switch(typeof n)
		{
			case 'undefined':
			return e.getAttribute(a);
			case 'function':
			e[a] = n;
			break;
			default:
			e.setAttribute(a, n);
		}
		break;
		case 'object':
		for(var k in a)
			attr$(e, k, a[k]);
		break;
	}
}
function css$(cssClass, id)
{
	var s = id ? _$('#' + id) : null;
	if( s )
	{
		s.innerHTML = cssClass;
		return s;
	}else{
		s = ce$('style');
		s.type = 'text/css';
		if(id) s.id = id;
		s.appendChild(document.createTextNode(cssClass));
		return _$('head').appendChild(s);
	}
}
function coords$(element){return e$(element).getBoundingClientRect();}
function documentEl(doc){return (doc||document).documentElement;}
function createTooltip(n)
{
	var retVal = {
		_t: null,
		show: function(t){
			clearTimeout(this.timerId);
			var ttEl = this.ttel;
			if( !t && !this._t) return;
			ttEl.classList.remove('vkmd-hidden');
			t = t || this._t;
			this._t = t;
			ttHTML(ttEl, t);
			setPos(ttEl, t.el, t);
		},
		hide: function(){
			var el = this._t,
				ttEl = this.ttel;
			this.timerId = setTimeout(function(){
				ttEl.classList.add('vkmd-hidden');
			}, 350);
		},
		attach: function(t){
			if( t.attached )
				return;
			var that = this;
			t.mouseenter = t.mouseenter || function(){that.show(t);};
			t.mouseleave = t.mouseleave || function(){that.hide();};
			t.el = e$(t.el);
			on$(t.el, 'mouseenter', t.mouseenter, false);
			on$(t.el, 'mouseleave', t.mouseleave, false);
			t.attached = true;
		},
		/*
		detach: function(t){
			if( !t.attached )
				return;
			t.el = e$(t.el);
			off$(t.el, 'mouseenter', t.mouseenter, false);
			off$(t.el, 'mouseleave', t.mouseleave, false);
			t.mouseenter = null;
			t.mouseleave = null;
			t.attached = false;
		},
		*/
		init: function(id){
			id = 'vkmd-tooltip-' + (id || 0);
			var ttEl = _$('#' + id);
			if( ttEl ) return;
			ttEl = se$('<div id="' + id + '" class="vkmd-tooltip-top vkmd-hidden">' +
				'<div class="vkmd-tooltip-content"></div></div>');
			_$('body').appendChild(ttEl);
			css$(`
			[id^="vkmd-tooltip"] {
				position: fixed;
				z-index: 1000;
				background-color: #000;
				color: #fff;
				opacity: 0.8;
				padding: 5px;
				border-radius: 5px;
				border-color: #d0d0d0;
				border-style: solid;
				border-width: thin;
				min-width: 50px;
				transition: opacity 0.3s;
			}
			.vkmd-hidden {
				/*display: none !important;*/
				opacity: 0;
			}
			`, 'vkmd-tooltip-css');
			this.ttel = ttEl;
			var that = this;
			this.stopTimer = function(evt){clearTimeout(that.timerId);};
			this.dispatchHide = function(evt){that.hide();};
			on$(ttEl, 'click', this.stopTimer, false);
			on$(ttEl, 'mouseenter', this.stopTimer, false);
			on$(ttEl, 'mouseleave', this.dispatchHide, false);
			this.sl = '#' + id;
		},
	};
	retVal.init(n);
	return retVal;
}
function setPosition(tt, el, opt)
{
	var el_crd = coords$(el),
		tt_crd = coords$(tt),
		clientWidth = documentEl().clientWidth,
		clientHeight = documentEl().clientHeight,
		tmp;
	opt.shiftX = opt.shiftX || 0;
	switch(opt.posX)
	{
		case 'left':
		tmp = el_crd.left - (opt.relX == 'inner' ? 0 : tt_crd.width) + opt.shiftX;
		tt.style.left = (tmp < 0 ? 0 : tmp) + 'px';
		break;
		case 'right':
		tmp = el_crd.right - (opt.relX == 'inner' ? tt_crd.width : 0) + opt.shiftX;
		tt.style.left = ((tmp + tt_crd.width) > clientWidth ? clientWidth - tt_crd.width : tmp) + 'px';
		break;
		case 'center':
		tt.style.left = (el_crd.left + (el_crd.width - tt_crd.width)/2 + opt.shiftX) + 'px';
		break;
	}
	opt.shiftY = opt.shiftY || 0;
	switch(opt.posY)
	{
		case 'top':
		tmp = el_crd.top - (opt.relY == 'inner' ? 0 : tt_crd.height) - opt.shiftY;
		tt.style.top = (tmp < 0 ? 0 : tmp) + 'px';
		break;
		case 'bottom':
		tmp = el_crd.bottom - (opt.relY == 'inner' ? tt_crd.height : 0) - opt.shiftY;
		tt.style.top = ((tmp + tt_crd.height) > clientHeight ? clientHeight - tt_crd.height : tmp) + 'px';
		break;
		case 'center':
		tt.style.top = (el_crd.top + (el_crd.height - tt_crd.height)/2 - opt.shiftY) + 'px';
		break;
	}
}
function setPos(tt, el, opt)
{
	if( opt.option )
		opt = extend(opt, getCoordOption(opt.option));
	setPosition(tt, el, opt);
}
function getCoordOption(o)
{
	var opt = {};
	if( o == 'center' )
	{
		opt.posX = 'center';
		opt.posY = 'center';
		return opt;
	}
	// top-left, top-right, bottom-left, bottom-right
	o = o.split('-');
	setCoordOption(opt, 'x', o[1], 'inner');
	setCoordOption(opt, 'y', o[0], 'outer');
	return opt;
}
function setCoordOption(opt, axis, pos, rel)
{
	opt = opt || {};
	switch(axis.toLowerCase())
	{
		case 'x':
		opt.posX = pos;
		opt.relX = rel || '';
		break;
		case 'y':
		opt.posY = pos;
		opt.relY = rel || '';
		break;
	}
}
function ttHTML( tt, t )
{
	var el = _$('.vkmd-tooltip-content', tt);
	if( t && el )
	{
		try{
			el.innerHTML = VkMD[t.media].ttHtml(attr$(t.el, 'data-id'));
			on$( el, 'mouseover', ttoverHandler );
			on$( el, 'click', ttclickHandler, true );
		}catch(e){
			cerr(e);
			el.innerHTML = 'Error';
		}
	}
}
function addToList(url)
{
	if( location.hostname !== 'vk.com' )
		return -1;
	var origin = getLoc(url, 'origin'),
		hostname = getLoc(url, 'hostname'),
		domain = hostname.split('.').slice(-2).join('.');
	var storage = localStorage.getItem(storageKEY), domains, list;
	try{
		storage = JSON.parse(storage || '{}');
	}catch(e){
		localStorage.removeItem(storageKEY);
		storage = {};
	}
	if( !(domains = storage.domains) )
		domains = storage.domains = [];
	if( !(list = storage[domain]) )
		list = storage[domain] = [];
	if( domains.indexOf(domain) == -1 )
	{
		domains.push(domain);
		clog2("[vkDomainStorage] added new domain: ", domain);
		clog2("[vkDomainStorage] domain list: ", storage.domains);
		if( !localStorage.getItem('vk-warning-off') && vkDOMAINS.indexOf(domain) == -1 )
		{
			var r = confirm("" +
			scriptName + " v" + scriptVersion + "\r\n" +
			"ВНИМАНИЕ: обнаружен домен, отсутствующий в списке включений\r\n" +
			"Название домена: " + domain + "\r\n" +
			"Для правильной работы скрипта необходимо добавить его в список включений:\r\n" +
			"// @include\t*://*." + domain + "/*\r\n" +
			"Больше не показывать это сообщение?");
			if( r )
				localStorage.setItem('vk-warning-off', true);
		}
	}
	if( list.indexOf(origin) == -1 )
	{
		list.push(origin);
		clog2("[vkDomainStorage] added new origin (at:" + domain + "): ", origin);
	}
	localStorage.setItem(storageKEY, JSON.stringify(storage));
	return 0;
}