VKDownloadMedia

Скачать фото/аудио/видео-файлы с соц. сети ВКонтакте.

اعتبارا من 01-05-2017. شاهد أحدث إصدار.

// ==UserScript==
// @name        VKDownloadMedia
// @description Скачать фото/аудио/видео-файлы с соц. сети ВКонтакте.
// @namespace   https://github.com/KJ86/VKDownloadMedia
// @version     5.3
// @date        2017-05-01
// @author      KJ86
// @icon        data:image/gif;base64,R0lGODlhDQANAIABAHKTtgAAACH5BAEAAAEALAAAAAANAA0AAAIYjAOZx+2n1pstgmlxrDabrnCeKD0hhTgFADs=
// @homepage    https://greasyfork.org/ru/scripts/7385-vkdownloadmedia
// @supportURL  https://vk.com/vkdownloadmedia
// @include     *
// @run-at      document-end
// @grant       none
// ==/UserScript==

(function () {
    'use strict';

    // ifarme handler
    if (window.self !== window.top) {
        var param = location.hash.match(/vkdm=(.*)/i);

        if (param !== null) {
            param = JSON.parse(decodeURIComponent(param[1]));

            if (typeof param.fileName !== 'undefined') { // Download file
                var fileExtension = function () {
                    var i, match;
                    var arr = param.url.split('.');

                    for (i = 0; i < arr.length; i++) {
                        match = arr[i].match(/(.+?)\?/);

                        if (match) {
                            return match[1];
                        }
                    }
                }();
                var a = document.createElement('a');

                a.href = param.url;
                a.download = param.fileName + '.' + fileExtension;

                document.body.appendChild(a).click();
                window.top.postMessage('VKDM:' + JSON.stringify(param), '*');
            } else if (typeof param.fileSize !== 'undefined') { // Get file size
                var xhr = new XMLHttpRequest();

                xhr.open('HEAD', param.url);
                xhr.onload = function () {
                    param.fileSize = xhr.getResponseHeader('content-length');

                    window.top.postMessage('VKDM:' + JSON.stringify(param), '*');
                };
                xhr.send();
            }
        }

        return;
    }

    // Only on vk.com
    if (location.hostname !== 'vk.com') return;

    // Add CSS rules
    document.head.appendChild(ce('style', {
        type: 'text/css',
        textContent: ''
        + '.audio_row .audio_acts .audio_act[id^="vkdm_download_"]'
        + '{display: block; background: url(data:image/gif;base64,R0lGODlhCwANAIABAIKKmQAAACH5BAEAAAEALAAAAAALAA0AAAIXjIFoy72pnHzQQWDruTfwRHWM2IATxhQAOw==) no-repeat 50% 50%;}'
        + '.audio_layer_container .audio_page__footer_download_playlist'
        + '{float: right; cursor: pointer;}'
        + '.audio_layer_container .audio_page__footer_download_playlist:hover'
        + '{text-decoration: underline;}'
    }));

    // Add download button
    (function () {
        var isDwnlPlBtnAdd = false;
        var dwAudioBtn = ce('div', {
            className: 'audio_act',
            id: ''
        });

        dwAudioBtn.setAttribute('onmouseover', 'VKDM.audioShowActionTooltip(this)');
        dwAudioBtn.setAttribute('onclick', 'VKDM.downloadAudio(this)');

        setInterval(function () {
            // Audio
            var audioRows = domQuery('.audio_row:not(.candownload):not(.claimed)');

            if (audioRows.length) {
                each(audioRows, function (i, audioRow) {
                    var audioActs = audioRow.querySelector('.audio_acts');
                    var clone = dwAudioBtn.cloneNode(true);

                    clone.id = getRandomID('download');
                    clone.setAttribute('data-full-id', audioRow.getAttribute('data-full-id'));
                    audioActs.insertBefore(clone, audioActs.firstElementChild);
                    audioRow.classList.add('candownload');
                });
            }

            // Video
            if (ge('video_player') && !ge('mv_download')) {
                if (typeof mvcur !== 'undefined' && mvcur.player && mvcur.player.getVars) {
                    var videoData = mvcur.player.getVars();

                    if (videoData) {
                        var prop, match, items = [];

                        for (prop in videoData) {
                            match = prop.match(/url(\d+)/);

                            if (match) {
                                if (parseInt(match[1], 10) >= 720) {
                                    match[1] += ' <span style="font-size:smaller;color:#939393;">HD</span>';
                                }

                                items.push([prop, match[1], videoData[prop]]);
                            }
                        }

                        if (items.length) {
                            domInsertBefore(ce('div', {
                                className: 'mv_more fl_l',
                                id: 'mv_download',
                                innerHTML: 'Скачать'
                            }), ge('mv_more'));

                            new InlineDropdown('mv_download', {
                                items: items.reverse(),
                                withArrow: true,
                                keepTitle: true,
                                autoShow: true,
                                autoHide: 300,
                                headerLeft: -17,
                                headerTop: -11,
                                sublists: {},
                                onSelect: function (id, data) {
                                    VKDM.downloadVideo(data[2], ge('mv_title').textContent);
                                }
                            });
                        }
                    }
                }

            }

            // Photo
            var photosAlbumInfo = geByClass1('photos_album_info');

            if (photosAlbumInfo && !ge('vkdm_download_album')) {
                photosAlbumInfo.parentNode.appendChild(cf('<span class="divide">|</span><span class="photos_album_info"><a id="vkdm_download_album" onclick="VKDM.downloadPhotoAlbumsList(); return false;">Скачать альбом</a></span>'));
            }

            // Play List
            if (isDwnlPlBtnAdd === false && ap._currentPlaylist) {
                var dwnlPlBtn = ce('span', {
                    className: 'audio_page__footer_download_playlist _audio_page__footer_download_playlist',
                    innerHTML: 'Скачать плейлист'
                });

                dwnlPlBtn.setAttribute('onclick', 'VKDM.downloadCurrentAudioPlayList()');

                domInsertAfter(dwnlPlBtn, geByClass1('_audio_page__footer_clear_playlist'));

                if (dwnlPlBtn.parentNode) {
                    isDwnlPlBtnAdd = true;
                }
            }
        }, 300);
    })();

    // VKDM (Global)
    window.VKDM = {
        _audioUnmaskSource: function (mask) {
            var obj = {src: mask};

            try {
                AudioPlayerHTML5.prototype._setAudioNodeUrl(obj, mask);
            } catch(e) {};

            return obj.src;
        },

        audioShowActionTooltip: function (btn) {
            var getTTtext = function () {
                var duration = btn.getAttribute('data-audio-duration');
                var fileSizeByte = btn.getAttribute('data-file-size');
                var fileSizeMByte = 0;
                var bitrate = 0;

                if (duration && fileSizeByte) {
                    fileSizeMByte = (fileSizeByte / 1024 / 1024).toFixed(1);
                    bitrate = parseInt(fileSizeByte * 8 / duration / 1000);
                }

                return 'Скачать аудиозапись<br>Битрейт: ~' + bitrate + ' кбит/с<br>Размер: ' + fileSizeMByte + ' МБ';
            };

            if (!btn.hasAttribute('data-file-size') && !btn.classList.contains('vkdm_ajax_in_process')) {
                ajax.post('al_audio.php', {
                    act: 'reload_audio',
                    ids: btn.getAttribute('data-full-id')
                }, {
                    onDone: function (items) {
                        var urlMask = items[0][2];
                        var duration = items[0][5];

                        iframeTransport({
                            url: VKDM._audioUnmaskSource(urlMask),
                            fileSize: null
                        }, function (data) {
                            btn.classList.remove('vkdm_ajax_in_process');
                            btn.setAttribute('data-file-size', data.fileSize);
                            btn.setAttribute('data-audio-duration', duration);

                            geByClass1('tt_text', btn.tt.container).innerHTML = getTTtext();
                            tooltips.rePositionTT(btn.tt);
                        });
                    }
                });
                btn.classList.add('vkdm_ajax_in_process');
            }

            showTooltip(btn, {
                text: getTTtext,
                black: 1,
                shift: [7, 5, 0],
                needLeft: true
            });
        },

        downloadAudio: function (btn) {
            ajax.post('al_audio.php', {
                act: 'reload_audio',
                ids: btn.getAttribute('data-full-id')
            }, {
                onDone: function (items) {
                    iframeTransport({
                        url: VKDM._audioUnmaskSource(items[0][2]),
                        fileName: ce('div', {innerHTML: items[0][4] + ' &ndash; ' + items[0][3]}).textContent
                    });
                }
            });
        },

        downloadVideo: function (url, fileName) {
            iframeTransport({
                url: url,
                fileName: fileName
            });
        },

        downloadPhotoAlbumsList: function (el) {
            var items = [];
            var isFastBoxClosed = false;
            var downloadListBtnWrapID = getRandomID();
            var fileName = document.title.replace(/"/g, '');
            var match = location.pathname.match(/album(-?[0-9]+)_([0-9]+)/);
            var ownerId = match[1];
            var albumId = match[2];
            var count = 1000;
            var offset = 0;

            switch (albumId) {
                case '0': albumId = 'profile'; break;
                case '00': albumId = 'wall'; break;
                case '000': albumId = 'saved'; break;
            }

            var onDone = function () {
                var i, l, prop, match, nSizes;
                var srcArr = [];

                for (i = 0, l = items.length; i < l; i++) {
                    nSizes = [];

                    for (prop in items[i]) {
                        match = prop.match(/photo_(\d+)/);

                        if (match) {
                            nSizes.push(match[1]);
                        }
                    }

                    if (nSizes.length) {
                        srcArr.push(items[i]['photo_' + Math.max.apply(null, nSizes)]);
                    }
                }

                var downloadListBtnWrap = ge(downloadListBtnWrapID);

                if (downloadListBtnWrap) {
                    var url = createFile('text/plain;charset=utf-8', srcArr.join('\r\n'));

                    if (url) {
                        downloadListBtnWrap.innerHTML = ''
                        + '<a href="' + url + '" class="flat_button secondary" download="' + fileName + '.txt">.txt</a>&nbsp;'
                        + '<a href="' + url + '" class="flat_button secondary" download="' + fileName + '.urls">.urls</a>';
                    } else {
                        downloadListBtnWrap.textContent = 'Не удалось создать файл.';
                    }
                }
            };
            var getAllItems = function () {
                if (isFastBoxClosed === true) return;

                getJSONP('https://api.vk.com/method/photos.get?owner_id=' + ownerId + '&album_id=' + albumId + '&count=' + count + '&offset=' + offset + '&v=5.53', function (data) {
                    items = items.concat(data.items);

                    if (items.length === data.count) {
                        onDone();
                    } else {
                        offset += count;
                        getAllItems();
                    }
                });
            };

            showFastBox({
                title: 'VKDM - Скачать альбом',
                dark: 1,
                hideButtons: 1,
                onBeforeHide: function () {
                    isFastBoxClosed = true;

                    each(geByClass('flat_button', ge(downloadListBtnWrapID), 'a'), function (i, el) {
                        window.URL.revokeObjectURL(el.href);
                    });
                }
            }, ''
            + '<div style="text-align: center;">'
            + '<div style="color: #777;margin-bottom: 20px">'
            + 'Скачать список всех фотографий с альбома:'
            + '<br><b>' + fileName + '</b>'
            + '<br><i>(может занят продолжительное время)</i>'
            + '</div>'
            + '<div id="' + downloadListBtnWrapID + '"><img src="/images/upload.gif" style="margin-top: 10px; margin-bottom: 7px;" /></div>'
            + '</div>');
            getAllItems();
        },

        downloadCurrentAudioPlayList: function () {
            var dataArr = [];
            var audioIdsArr = [];
            var isFastBoxClosed = false;
            var downloadListBtnWrapID = getRandomID();
            var playListTitle = '';
            var getAudioLinks = function (ids) {
                if (isFastBoxClosed === true) return;

                ajax.post('al_audio.php', {
                    act: 'reload_audio',
                    ids: ids.join(',')
                }, {
                    onDone: function (items) {
                        if (items === '') {
                            setTimeout(function () {
                                getAudioLinks(ids);
                            }, 10000);

                            return;
                        }

                        each(items, function(i, el) {
                            dataArr.push({
                                url: VKDM._audioUnmaskSource(el[2]),
                                name: el[4] + ' – ' + el[3],
                                duration: el[5]
                            });
                        });

                        if (audioIdsArr.length) {
                            getAudioLinks(audioIdsArr.splice(0, 10));
                        } else {
                            var textFileData = [];
                            var m3uFileData = ['#EXTM3U'];

                            each(dataArr, function(i, el) {
                                textFileData.push(el.url);
                                m3uFileData.push('#EXTINF:' + el.duration + ', ' + el.name + '\r\n' + el.url);
                            });

                            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>&nbsp;'
                                + '<a href="' + textFileURL + '" class="flat_button secondary" download="' + playListTitle + '.txt">.txt</a>&nbsp;'
                                + '<a href="' + textFileURL + '" class="flat_button secondary" download="' + playListTitle + '.urls">.urls</a>';
                            } else {
                                downloadListBtnWrap.textContent = 'Не удалось создать файл.';
                            }
                        }
                    }
                });
            };

            (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) {
                    audioIdsArr.push(el[1] + '_' + el[0]);
                });

                playListTitle = (title || 'playlist') + ' (' + items.length + ')';
            })();

            if (audioIdsArr.length) {
                showFastBox({
                    title: 'VKDM - Скачать плейлист',
                    dark: 1,
                    hideButtons: 1,
                    onBeforeHide: function () {
                        isFastBoxClosed = true;

                        each(geByClass('flat_button', ge(downloadListBtnWrapID), 'a'), function (i, el) {
                            window.URL.revokeObjectURL(el.href);
                        });
                    }
                }, ''
                + '<div style="text-align: center;">'
                + '<div style="color: #777;margin-bottom: 20px">'
                + 'Скачать плейлист:'
                + '<br><b>' + playListTitle + '</b>'
                + '<br><i>(может занят продолжительное время)</i>'
                + '</div>'
                + '<div id="' + downloadListBtnWrapID + '"><img src="/images/upload.gif" style="margin-top: 10px; margin-bottom: 7px;" /></div>'
                + '</div>');
                getAudioLinks(audioIdsArr.splice(0, 10));
            }
        }
    };

    // Iframe transport handler
    window.addEventListener('message', function (e) {
        if (e.data.indexOf('VKDM:') !== -1) {
            var data = JSON.parse(e.data.replace('VKDM:', ''));
            var iframe = ge(data.iframeID);

            if (data.callback && typeof window[data.callback] === 'function') {
                window[data.callback].call(iframe, data);
                delete window[data.callback];
            }

            setTimeout(function () {
                re(iframe);
            }, 1000);
        }
    }, false);

    /*!
     * Helpers
     */
     function iframeTransport(params, callback) {
        var url = params.url;
        var iframeID = getRandomID('iframe');
        var data = extend({
            iframeID: iframeID
        }, params);

        if (typeof callback === 'function') {
            var callBackFuncName = 'vkdmFunc' + Date.now();

            data.callback = callBackFuncName;
            window[callBackFuncName] = callback;
        }

        var src = url.split(/\.([^\.]+?)\?/)[0] + '.html?#vkdm=' + encodeURIComponent(JSON.stringify(data));
        var iframe = ce('iframe', {id: iframeID, src: src, width: '1', height: '1'}, {visibility: 'hidden'});

        document.body.appendChild(iframe);
    }

    function getJSONP(url, success) {
        var tempFuncName = 'vkdmFunc' + Date.now();

        window[tempFuncName] = function (response) {
            success(response.response);
            delete window[tempFuncName];
        };

        document.body.appendChild(ce('script', {
            src: url + '&callback=' + tempFuncName,
            onload: function () {
                re(this);
            },
            onerror: function () {
                re(this);
            }
        }));
    }

    function createFile(type, data) {
        var url = '';

        if (window.URL && window.URL.createObjectURL) {
            url = window.URL.createObjectURL(new Blob([data], {type: type}));
        }

        return url;
    }

    function getRandomID(name) {
        var prefix = 'vkdm' + (name ? '_' + name + '_' : '_');

        return prefix + Math.random().toString().slice(2, 10);
    }
})();