您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Скачать фото/аудио/видео-файлы с соц. сети ВКонтакте.
// ==UserScript== // @name VKDownloadMedia // @description Скачать фото/аудио/видео-файлы с соц. сети ВКонтакте. // @namespace https://github.com/KJ86/VKDownloadMedia // @version 6.1.3 // @date 2019-10-14 // @author KJ86 // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODI4YTk5IiBkPSJtIDEwLDYgaCA0IHYgNiBoIDMgbCAtNSw2IC01LC02IGggMyB6IiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjwvc3ZnPg== // @homepage https://greasyfork.org/ru/scripts/7385-vkdownloadmedia // @supportURL https://vk.com/vkdownloadmedia // @include https://vk.com/* // @run-at document-end // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_addStyle // @connect self // @connect * // @nocompat Chrome // @noframes // ==/UserScript== (function () { 'use strict'; /** @var {Object} unsafeWindow */ /** @var {Function} GM_addStyle */ /** @var {Function} GM_download */ /** @var {Function} GM_xmlhttpRequest */ /** @var {Function} Promise */ /** @var {Function} geByClass */ /** @var {Function} geByClass1 */ /** @var {Function} ge */ /** @var {Function} se */ /** @var {Function} re */ /** @var {Function} ce */ /** @var {Function} addEvent */ /** @var {Function} cancelEvent */ /** @var {Function} domInsertAfter */ /** @var {Object} Videoview */ /** @var {Object} tooltips */ /** @var {Object} mvcur */ /** @var {Object} ap */ /** @var {Function} domInsertBefore */ /** @var {Function} matchesSelector */ /** @var {Function} showTooltip */ /** @var {Function} uiActionsMenu */ /** @var {Function} domClosest */ /** @var {Function} getProgressHtml */ /** @var {Function} setStyle */ /** @var {Function} showFastBox */ /** @var {Function} domQuery */ /** @var {Function} data */ /** @var {Function} each */ /** * @type {Window} */ var win = unsafeWindow || window; // Check supported browsers if (typeof win.Promise === 'undefined') return; /** * @function */ var DOMNodeInserted = (function () { var _callbacks = []; var element = document.documentElement; var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; if (MutationObserver) { (new MutationObserver(function (mutations) { var addedNodes = []; mutations.forEach(function (mutation) { if (mutation.addedNodes.length) { for (var i = 0, l = mutation.addedNodes.length; i < l; i++) { if (mutation.addedNodes[i].nodeType === Node.ELEMENT_NODE) { addedNodes.push(mutation.addedNodes[i]); } } } }); if (addedNodes.length) { fire(addedNodes); } })).observe(element, { childList: true, subtree: true }); } else { // Fallback element.addEventListener('DOMNodeInserted', function (e) { if (e.target.nodeType === Node.ELEMENT_NODE) { fire([e.target]); } }, false); } function fire(addedNodes) { addedNodes.forEach(function (node) { for (var i = 0, l = _callbacks.length; i < l; i++) { var selector = _callbacks[i].selector; var callback = _callbacks[i].callback; var isOnce = _callbacks[i].isOnce; if (matchesSelector(node, selector)) { callback.call(_callbacks, node); if (isOnce) { _callbacks.splice(i, 1); return; } } var found = node.querySelectorAll(selector); if (found.length) { for (var j = 0, l2 = found.length; j < l2; j++) { callback.call(_callbacks, found[j]); if (isOnce) { _callbacks.splice(i, 1); return; } } } } }); } return function (selector, callback, isOnce) { _callbacks.push({ selector: selector, callback: callback, isOnce: !!isOnce }); }; })(); /** * @namespace */ var VKDM = { /** * @param {Array|String} [mask] * @returns {Promise} */ audioUnmaskSource: function fn(mask) { if (typeof fn.module === 'undefined') { var ORIGIN = 'https://m.vk.com'; fn.module = request({ url: ORIGIN, _isAjax: false }).then(function (xhr) { var match = xhr.response.match(/src="(.+?common\..+?js.*?)"/); var commonJsSrc = match && match[1]; if (commonJsSrc) { return request({ url: ORIGIN + commonJsSrc, _isAjax: false }).then(function (xhr) { var modules = {}; // Inject and exec eval(xhr.response.replace(/^!function.*?\{/, '$& var a0 = arguments[0]; for (var p in a0) {if (a0[p].toString().indexOf(".audioUnmaskSource=") !== -1) {a0[p](null, modules);break;}} return;')); if ('audioUnmaskSource' in modules) { return modules; } return Promise.reject(); }); } return Promise.reject(); }); } return fn.module.then(function (modules) { if (mask) { if (Array.isArray(mask)) { return mask.map(function (item) { return modules.audioUnmaskSource(item); }); } return modules.audioUnmaskSource(mask); } }); }, /** * @param ids * @returns {Promise} */ audioRequestData: function (ids) { return request({ url: '/al_audio.php', method: 'POST', data: { 'act': 'reload_audio', 'al': '1', 'ids': ids }, _isMobile: true }).then(function (xhr) { var items; try { var json = JSON.parse(xhr.response); items = json.payload[1][0]; } catch (e) { // Deprecated try { if (xhr.response.indexOf('<!>') !== -1) { items = JSON.parse(xhr.response.split('<!>')[5].replace('<!json>', '')); } } catch (e) {} } if (Array.isArray(items)) { return items; } return Promise.reject(); }); }, audioShowActionTooltip: function (btn) { var audioRow = domClosest('audio_row', btn); var getTText = function () { var duration = data(audioRow, 'vkdm_audio_row_duration'); var fileSize = data(audioRow, 'vkdm_audio_row_file_size'); var fileSizeMByte = '...'; var bitrate = '...'; if (duration && fileSize) { fileSizeMByte = (fileSize / 1024 / 1024).toFixed(1) + ' МБ'; bitrate = '~' + (fileSize * 8 / duration / 1000).toFixed(0) + ' кбит/с'; } return 'Скачать аудиозапись<br>Битрейт: ' + bitrate + '<br>Размер: ' + fileSizeMByte; }; if (!data(audioRow, 'vkdm_audio_row_duration') && !btn.classList.contains('vkdm_ajax_in_progress')) { var audioData = JSON.parse(audioRow.getAttribute('data-audio')); var hashes = audioData[13].split('/'); var ids = audioRow.getAttribute('data-full-id') + '_' + hashes[2] + '_' + hashes[5]; btn.classList.add('vkdm_ajax_in_progress'); VKDM.audioRequestData(ids).then(function (items) { VKDM.audioUnmaskSource(items[0][2]).then(function (url) { request({ url: url, method: 'HEAD' }).then(function (xhr) { var contentLength = xhr.getResponseHeader('content-length'); data(audioRow, 'vkdm_audio_row_duration', parseInt(items[0][5])); data(audioRow, 'vkdm_audio_row_file_size', parseInt(contentLength)); if (document.body.contains(btn)) { btn.classList.remove('vkdm_ajax_in_progress'); geByClass1('tt_text', btn.tt.container).innerHTML = getTText(); tooltips.rePositionTT(btn.tt); } }); }); }); } showTooltip(btn, { text: getTText, black: 1, shift: [7, 5, 0], needLeft: true }); }, downloadAudio: function (btn) { var audioRow = domClosest('audio_row', btn); var audioData = JSON.parse(audioRow.getAttribute('data-audio')); var hashes = audioData[13].split('/'); var ids = audioRow.getAttribute('data-full-id') + '_' + hashes[2] + '_' + hashes[5]; VKDM.audioRequestData(ids).then(function (items) { VKDM.audioUnmaskSource(items[0][2]).then(function (url) { GM_download(url, ce('div', {innerHTML: items[0][4] + ' – ' + items[0][3]}).textContent + getExtension(url)); }); }); }, downloadPhotoAlbumsList: function () { var isFastBoxClosed = false; var downloadListBtnWrapID = getRandomID(); var fileName = document.title.replace(/"/g, ''); var progress = se(getProgressHtml('', 'pr_medium')); setStyle(progress, { 'opacity': 1, 'padding-top': '11px', 'padding-bottom': '12px' }); showFastBox({ title: 'Скачать альбом', dark: 1, hideButtons: 1, onShow: function () { var items = []; var match = location.pathname.match(/album(-?[0-9]+)_([0-9]+)/); var ownerId = match[1]; var albumId = match[2]; var count = 1000; switch (albumId) { case '0': albumId = 'profile'; break; case '00': albumId = 'wall'; break; case '000': albumId = 'saved'; break; } void function getAllItems(offset) { if (isFastBoxClosed === true) return; var params = [ 'owner_id=' + ownerId, 'album_id=' + albumId, 'count=' + count, 'offset=' + offset, 'access_token=08ee532d08ee532d08ee532dbc08a6df23008ee08ee532d54ae800969245a7851077ff5', 'v=5.53' ]; request.getJSONP('https://api.vk.com/method/photos.get?' + params.join('&'), function (data) { items = items.concat(data.items); if (items.length === data.count) { var srcArr = []; items.forEach(function (item) { var nSizes = []; for (var prop in item) { var match = prop.match(/photo_(\d+)/); if (match) { nSizes.push(match[1]); } } if (nSizes.length) { srcArr.push(item['photo_' + Math.max.apply(null, nSizes)]); } }); var downloadListBtnWrap = ge(downloadListBtnWrapID); if (downloadListBtnWrap) { var url = createFile('text/plain;charset=utf-8', srcArr.join('\r\n')); downloadListBtnWrap.innerHTML = '<a href="' + url + '" class="flat_button secondary" download="' + fileName + '.txt">Список URL</a>'; } } else { getAllItems(offset + count); } }); }(0); }, onBeforeHide: function () { isFastBoxClosed = true; each(domQuery('#' + downloadListBtnWrapID + ' a'), function (i, el) { (window.URL || window.webkitURL).revokeObjectURL(el.href); }); } }, ( '<div style="text-align: center;">' + '<h4 class="subheader">' + fileName + '</h4>' + '<div id="' + downloadListBtnWrapID + '">' + progress.outerHTML + '</div>' + '</div>' )); }, downloadCurrentAudioPlayList: function () { var audioIdsArr = []; var isFastBoxClosed = false; var downloadListBtnWrapID = getRandomID(); var playListTitle = ''; (function () { var items = []; var title = ''; if ('_list' in ap._currentPlaylist) { items = ap._currentPlaylist._list; title = ap._currentPlaylist._title; } else { for (var prop in ap._currentPlaylist) { if ('_list' in ap._currentPlaylist[prop]) { items = ap._currentPlaylist[prop]['_list']; title = ap._currentPlaylist[prop]['_title']; break; } } } each(items, function (i, el) { var hashes = el[13].split('/'); audioIdsArr.push(el[1] + '_' + el[0] + '_' + hashes[2] + '_' + hashes[5]); }); playListTitle = (title || 'playlist') + ' (' + items.length + ')'; })(); if (audioIdsArr.length) { var progress = se(getProgressHtml('', 'pr_medium')); setStyle(progress, { 'opacity': 1, 'padding-top': '11px', 'padding-bottom': '12px' }); showFastBox({ title: 'Скачать плейлист', dark: 1, hideButtons: 1, onShow: function () { var dataArr = []; void function getAudioLinks(ids) { if (isFastBoxClosed === true) return; VKDM.audioRequestData(ids).then(function (items) { if (!Array.isArray(items)) { setTimeout(function () { getAudioLinks(ids); }, 10000); return; } items.forEach(function (item) { if (item[2]) { dataArr.push({ mask: item[2], name: item[4] + ' – ' + item[3], duration: item[5] }); } }); if (audioIdsArr.length) { getAudioLinks(audioIdsArr.splice(0, 10).join(',')); } else { var textFileData = []; var m3uFileData = ['#EXTM3U']; VKDM.audioUnmaskSource(dataArr.map(function (el) { return el.mask; })).then(function (urlArr) { dataArr.forEach(function (el, i) { textFileData.push(urlArr[i]); m3uFileData.push('#EXTINF:' + el.duration + ', ' + el.name + '\r\n' + urlArr[i]); }); var textFileURL = createFile('text/plain;charset=utf-8', textFileData.join('\r\n')); var m3uFileURL = createFile('audio/x-mpegurl;charset=utf-8', m3uFileData.join('\r\n')); var downloadListBtnWrap = ge(downloadListBtnWrapID); if (textFileURL && m3uFileURL) { downloadListBtnWrap.innerHTML = ( '<a href="' + m3uFileURL + '" class="flat_button secondary" download="' + playListTitle + '.m3u8">M3U</a> ' + '<a href="' + textFileURL + '" class="flat_button secondary" download="' + playListTitle + '.txt">Список URL</a>' ); } else { downloadListBtnWrap.textContent = 'Не удалось создать файл.'; } }); } }); }(audioIdsArr.splice(0, 10).join(',')); }, onBeforeHide: function () { isFastBoxClosed = true; each(domQuery('#' + downloadListBtnWrapID + ' a'), function (i, el) { (window.URL || window.webkitURL).revokeObjectURL(el.href); }); } }, ( '<div style="text-align: center;">' + '<h4 class="subheader">' + playListTitle + '</h4>' + '<div id="' + downloadListBtnWrapID + '">' + progress.outerHTML + '</div>' + '</div>' )); } } }; // region Init // Add CSS rules GM_addStyle( '.audio_row .audio_row__action_download {background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODI4YTk5IiBkPSJtIDEwLDYgaCA0IHYgNiBoIDMgbCAtNSw2IC01LC02IGggMyB6IiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjwvc3ZnPg==");}' + '.audio_layer_container .audio_page__footer_download_playlist {float: right; cursor: pointer;}' + '.audio_layer_container .audio_page__footer_download_playlist:hover {text-decoration: underline;}' ); // Preload module VKDM.audioUnmaskSource('').then(null); // Adding audio download button DOMNodeInserted('.audio_row:not(.audio_claimed) .audio_row__actions', function (node) { // When module ready VKDM.audioUnmaskSource('').then(function () { var btn = se('<button aria-label="Скачать аудиозапись" data-action="download" class="audio_row__action audio_row__action_download"></button>'); addEvent(btn, 'click', function (e) { VKDM.downloadAudio(this); cancelEvent(e); }); addEvent(btn, 'mouseover', function () { VKDM.audioShowActionTooltip(this); }); domInsertBefore(btn, node.firstElementChild); }); }); // Adding video download button DOMNodeInserted('#video_player', function () { if (Videoview && Videoview.showVideo) { var addAction = function () { try { if (mvcur && mvcur.player) { var videoData = mvcur.player.getVars(); if (videoData) { var prop, match; var items = []; for (prop in videoData) { match = prop.match(/url(\d+)/); if (match) { items.push([parseInt(match[1], 10), videoData[prop]]); } } if (items.length) { var itemsHTML = items .sort(function (a, b) { if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0; }) .map(function (item) { return ( '<a class="ui_actions_menu_item" href="' + item[1] + '" tabindex="0" role="link">' + item[0] + 'p' + (item[0] >= 720 ? ' <span style="font-size:10px;color:#939393;vertical-align:top;">HD</span>' : '') + '</a>' ); }) .join(''); var actionEl = se( '<div class="ui_actions_menu_wrap _ui_menu_wrap " onmouseover="uiActionsMenu.show(this, event, {autopos: true, dy: 6});" onmouseout="uiActionsMenu.hide(this);">' + '<div class="ui_actions_menu_icons" tabindex="0" aria-label="Действия" role="button" onclick="uiActionsMenu.keyToggle(this, event);">' + '<span class="blind_label">Действия</span>' + '<a class="ui_actions_menu_more">Скачать</a>' + '</div>' + '<div class="ui_actions_menu _ui_menu" >' + itemsHTML + '</div>' + '</div>' ); geByClass('ui_actions_menu_item', actionEl).forEach(function (element) { addEvent(element, 'click', function (e) { e.originalEvent.preventDefault(); GM_download(this.href, ge('mv_title').textContent + getExtension(this.href)); }); }); domInsertAfter(actionEl, geByClass1('ui_actions_menu_more').parentNode.parentNode); } } } } catch (e) { } }; var _showVideo = Videoview.showVideo; Videoview.showVideo = function () { _showVideo.apply(this, arguments); addAction(); }; addAction(); } }, true); // Adding photo download button DOMNodeInserted('.photos_album_intro_info', (function () { var element = geByClass1('photos_album_intro_info'); if (element) { handler(element); } function handler(node) { if (!ge('vkdm_download_album')) { var act = se('<a id="vkdm_download_album" href="javascript:;">Скачать альбом</a>'); var actWrap = se('<span class="photos_album_info"></span>'); addEvent(act, 'click', function () { VKDM.downloadPhotoAlbumsList(); return false; }); actWrap.appendChild(act); node.appendChild(se('<span class="divide">|</span>')); node.appendChild(actWrap); } } return handler; })()); // Adding playlist download button DOMNodeInserted('#page_header .audio_page__footer_clear_playlist', function (node) { var act = se('<span class="audio_page__footer_download_playlist">Скачать плейлист</span>'); addEvent(act, 'click', function () { VKDM.downloadCurrentAudioPlayList(); }); domInsertAfter(act, node); }); // endregion Init // region Helpers /** * @param {Object} options * @returns {Promise} */ function request(options) { return new Promise(function (resolve, reject) { options.method = options.method || 'GET'; options.headers = options.headers || {}; if (options._isAjax !== false) { options.headers['x-requested-with'] = 'XMLHttpRequest'; } if (options._isMobile) { options.headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'; } // POST if (options.method.toUpperCase() === 'POST') { options.headers['content-type'] = 'application/x-www-form-urlencoded'; if (typeof options.data === 'object') { var dataArr = []; for (var p in options.data) { dataArr.push(encodeURIComponent(p) + '=' + encodeURIComponent(options.data[p])); } options.data = dataArr.join('&'); } } options.onload = function (xhr) { if (xhr.status === 200) { xhr.getResponseHeader = function (name) { var header; var headers = xhr.responseHeaders.split('\n'); for (var i = 0; i < headers.length; i++) { header = headers[i].split(':'); if (header[0].trim().toLowerCase() === name.toLowerCase()) { return header[1].trim(); } } return null; }; resolve(xhr); } else { reject(xhr); } }; options.onerror = function (xhr) { reject(xhr); }; GM_xmlhttpRequest(options); }); } /** * @param {String} url * @param {Function} [success] */ request.getJSONP = function (url, success) { var tempFuncName = 'vkdmFunc' + Date.now(); win[tempFuncName] = function (response) { success(response.response); delete win[tempFuncName]; }; document.body.appendChild(ce('script', { src: url + '&callback=' + tempFuncName, onload: function () { re(this); }, onerror: function () { re(this); } })); }; function createFile(type, data) { return (window.URL || window.webkitURL).createObjectURL(new Blob([data], {type: type})); } function getExtension(url) { return url.split('?')[0].match(/\.\w+$/)[0]; } function getRandomID(name) { var prefix = 'vkdm' + (name ? '_' + name + '_' : '_'); return prefix + Math.random().toString().slice(2, 10); } // endregion Helpers })();