Greasy Fork

Greasy Fork is available in English.

批量下载微博原图、视频、livephoto

一键打包下载微博中一贴的原图、视频、livephoto,收藏时本地自动备份

当前为 2020-05-07 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        批量下载微博原图、视频、livephoto
// @name:zh     批量下载微博原图、视频、livephoto
// @name:en     Batch Download Src Image From Weibo Card
// @version     1.8.2
// @description   一键打包下载微博中一贴的原图、视频、livephoto,收藏时本地自动备份
// @description:zh  一键打包下载微博中一贴的原图、视频、livephoto,收藏时本地自动备份
// @description:en  Batch download weibo's source image
// @supportURL  https://imcoder.site/a/detail/HuXBzyC
// @match       https://weibo.com/*
// @match       https://d.weibo.com/*
// @match       http://*.sinaimg.cn/*
// @match       https://*.sinaimg.cn/*
// @match       http://*.sinaimg.com/*
// @match       https://*.sinaimg.com/*
// @connect     sinaimg.cn
// @connect     weibocdn.com
// @connect     weibo.com
// @connect     miaopai.com
// @connect     tbcache.com
// @connect     youku.com
// @grant       GM.xmlHttpRequest
// @grant       GM_xmlHttpRequest
// @grant       GM_download
// @grant       GM_notification
// @grant       GM_setClipboard
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @require     https://code.jquery.com/jquery-latest.min.js
// @require     https://cdn.bootcss.com/toastr.js/2.1.3/toastr.min.js
// @require     https://cdn.bootcss.com/jszip/3.1.5/jszip.min.js
// @author      Jeffrey.Deng
// @namespace   http://greasyfork.icu/users/129338
// ==/UserScript==

// @weibo       http://weibo.com/3983281402
// @blog        https://imcoder.site
// @date        2019.12.26

// @更新日志
// V 1.8.2      2020.05.07     1.修复只有一张图片时,无法下载livephoto的问题
// V 1.8        2020.04.30     1.收藏时自动备份收藏到本地缓存(只备份图片链接),这样博主删除微博仍能找到内容
// V 1.6        2020.04.29     1.打印链接直接用面板显示,感觉@indefined提供的代码
// V 1.5        2020.03.26     1.支持只打印链接,仅在控制台打印链接(按F12打开控制台console),【建议先按F12打开控制台console,在点按钮】
// V 1.4        2020.03.26     1.支持只下载链接,按钮【打包下载】:下载文件和链接,【下载链接】:仅下载链接
// V 1.3        2020.01.26     1.修复bug
// V 1.0        2019.12.26     1.支持打包下载用户一次动态的所有原图
//                             2.支持下载18图
//                             3.支持下载livephoto
//                             4.支持下载视频
//                             5.支持下载微博故事
//                             6.右键图片新标签直接打开原图

(function (document, $) {

    $("head").append('<link rel="stylesheet" href="https://cdn.bootcss.com/toastr.js/2.1.3/toastr.min.css">');

    var common_utils = (function (document, $) {
        function parseURL(url) {
            var a = document.createElement('a');
            a.href = url;
            return {
                source: url,
                protocol: a.protocol.replace(':', ''),
                host: a.hostname,
                port: a.port,
                query: a.search,
                params: (function () {
                    var ret = {},
                        seg = a.search.replace(/^\?/, '').split('&'),
                        len = seg.length, i = 0, s;
                    for (; i < len; i++) {
                        if (!seg[i]) {
                            continue;
                        }
                        s = seg[i].split('=');
                        ret[s[0]] = s[1];
                    }
                    return ret;
                })(),
                file: (a.pathname.match(/\/([^\/?#]+)$/i) || [, ''])[1],
                hash: a.hash.replace('#', ''),
                path: a.pathname.replace(/^([^\/])/, '/$1'),
                relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [, ''])[1],
                segments: a.pathname.replace(/^\//, '').split('/')
            };
        }

        function ajaxDownload(url, callback, args, tryTimes) {
            tryTimes = tryTimes || 0;
            var GM_download = GM.xmlHttpRequest || GM_xmlHttpRequest;
            GM_download({
                method: 'GET',
                responseType: 'blob',
                url: url,
                onreadystatechange: function (responseDetails) {
                    if (responseDetails.readyState === 4) {
                        if (responseDetails.response != null && (responseDetails.status === 200 || responseDetails.status === 0)) {
                            callback(responseDetails.response, args);
                        } else {
                            if (tryTimes++ == 3) {
                                callback(null, args);
                            } else {
                                ajaxDownload(url, callback, args, tryTimes);
                            }
                        }
                    }
                },
                onerror: function (responseDetails) {
                    if (tryTimes++ == 3) {
                        callback(null, args);
                    } else {
                        ajaxDownload(url, callback, args, tryTimes);
                    }
                    console.log(responseDetails.status);
                }
            });
            /*try {
             var xhr = new XMLHttpRequest();
             xhr.open('GET', url, true);
             xhr.responseType = "blob";
             xhr.onreadystatechange = function(evt) {
             if (xhr.readyState === 4) {
             if (xhr.status === 200 || xhr.status === 0) {
             callback(xhr.response, args);
             } else {
             callback(null, args);
             }
             }
             };
             xhr.send();
             } catch (e) {
             callback(null, args);
             }*/
        }

        function fileNameFromHeader(disposition, url) {
            var result = null;
            if (disposition && /filename=.*/ig.test(disposition)) {
                result = disposition.match(/filename=.*/ig);
                return decodeURI(result[0].split("=")[1]);
            }
            return url.substring(url.lastIndexOf('/') + 1);
        }

        function downloadBlobFile(content, fileName) {
            if ('msSaveOrOpenBlob' in navigator) {
                navigator.msSaveOrOpenBlob(content, fileName);
            } else {
                var aLink = document.createElement('a');
                aLink.className = 'download-temp-node';
                aLink.download = fileName;
                aLink.style = "display:none;";
                var blob = new Blob([content]);
                aLink.href = window.URL.createObjectURL(blob);
                document.body.appendChild(aLink);
                if (document.all) {
                    aLink.click(); //IE
                } else {
                    var evt = document.createEvent("MouseEvents");
                    evt.initEvent("click", true, true);
                    aLink.dispatchEvent(evt); // 其它浏览器
                }
                window.URL.revokeObjectURL(aLink.href);
                document.body.removeChild(aLink);
            }
        }

        function downloadUrlFile(url, fileName) {
            var aLink = document.createElement('a');
            if (fileName) {
                aLink.download = fileName;
            } else {
                aLink.download = url.substring(url.lastIndexOf('/') + 1);
            }
            aLink.className = 'download-temp-node';
            aLink.target = "_blank";
            aLink.style = "display:none;";
            aLink.href = url;
            document.body.appendChild(aLink);
            if (document.all) {
                aLink.click(); //IE
            } else {
                var evt = document.createEvent("MouseEvents");
                evt.initEvent("click", true, true);
                aLink.dispatchEvent(evt); // 其它浏览器
            }
            document.body.removeChild(aLink);
        }

        function paddingZero(num, length) {
            return (Array(length).join("0") + num).substr(-length);
        }

        /*  Class: TaskQueue
         *  Constructor: handler
         *      takes a function which will be the task handler to be called,
         *      handler should return Deferred object(not Promise), if not it will run immediately;
         *  methods: append
         *      appends a task to the Queue. Queue will only call a task when the previous task has finished
         */
        var TaskQueue = function (handler) {
            var tasks = [];
            // empty resolved deferred object
            var deferred = $.when();

            // handle the next object
            function handleNextTask() {
                // if the current deferred task has resolved and there are more tasks
                if (deferred.state() == "resolved" && tasks.length > 0) {
                    // grab a task
                    var task = tasks.shift();
                    // set the deferred to be deferred returned from the handler
                    deferred = handler(task);
                    // if its not a deferred object then set it to be an empty deferred object
                    if (!(deferred && deferred.promise)) {
                        deferred = $.when();
                    }
                    // if we have tasks left then handle the next one when the current one
                    // is done.
                    if (tasks.length >= 0) {
                        deferred.fail(function () {
                            tasks = [];
                            return;
                        });
                        deferred.done(handleNextTask);
                    }
                }
            }

            // appends a task.
            this.append = function (task) {
                // add to the array
                tasks.push(task);
                // handle the next task
                handleNextTask();
            };
        };
        var context = {
            "ajaxDownload": ajaxDownload,
            "fileNameFromHeader": fileNameFromHeader,
            "downloadBlobFile": downloadBlobFile,
            "downloadUrlFile": downloadUrlFile,
            "parseURL": parseURL,
            "paddingZero": paddingZero,
            "TaskQueue": TaskQueue
        };
        return context;
    })(document, jQuery);

    var options = {
        "type": 2,
        "isNeedConfirmDownload": true,
        "useQueueDownloadThreshold": 0,
        "suffix": null,
        "callback": {
            "parseLocationInfo_callback": function (location_info, options) {
                return common_utils.parseURL(document.location.href);
            },
            "parseFiles_callback": function (location_info, options) {
                // file.url file.folder_sort_index
                // not folder_sort_index -> use fileName
                var files = [];
                return files;
            },
            "makeNames_callback": function (arr, location_info, options) {
                var names = {};
                var time = new Date().getTime();
                names.zipName = "pack_" + time;
                names.folderName = names.zipName;
                names.infoName = null;
                names.infoValue = null;
                names.prefix = time;
                names.suffix = options.suffix;
                return names;
            },
            "beforeFilesDownload_callback": function (files, names, location_info, options, zip, main_folder) {
            },
            "beforeFileDownload_callback": function (file, location_info, options, zipFileLength, zip, main_folder, folder) {
            },
            "eachFileOnload_callback": function (blob, file, location_info, options, zipFileLength, zip, main_folder, folder) {
            },
            "allFilesOnload_callback": function (files, names, location_info, options, zip, main_folder) {
            },
            "beforeZipFileDownload_callback": function (zip_blob, files, names, location_info, options, zip, main_folder) {
                common_utils.downloadBlobFile(zip_blob, names.zipName + ".zip");
            }
        }
    };

    var ajaxDownloadAndZipFiles = function (files, names, location_info, options) {
        // GM_notification("开始下载~", names.zipName);
        var notify_start = toastr.success("正在打包~", names.zipName, {
            "progressBar": false,
            "hideDuration": 0,
            "showDuration": 0,
            "timeOut": 0,
            "closeButton": false
        });
        if (files && files.length > 0) {
            var zip = new JSZip();
            var main_folder = zip.folder(names.folderName);
            var zipFileLength = 0;
            var maxIndex = files.length;
            var paddingZeroLength = (files.length + "").length;
            if (names.infoName) {
                main_folder.file(names.infoName, names.infoValue);
            }
            options.callback.beforeFilesDownload_callback(files, names, location_info, options, zip, main_folder);
            var downloadFile = function (file, resolveCallback) {
                return $.Deferred(function(dfd) {
                    var folder = file.location ? main_folder.folder(file.location) : main_folder;
                    var isSave = options.callback.beforeFileDownload_callback(file, location_info, options, zipFileLength, zip, main_folder, folder);
                    if (isSave !== false) {
                        common_utils.ajaxDownload(file.url, function (blob, file) {
                            var isSave = options.callback.eachFileOnload_callback(blob, file, location_info, options, zipFileLength, zip, main_folder, folder);
                            if (isSave !== false) {
                                if (file.fileName) {
                                    folder.file(file.fileName, blob);
                                } else {
                                    var suffix = names.suffix || file.url.substring(file.url.lastIndexOf('.') + 1);
                                    file.fileName = names.prefix + "_" + common_utils.paddingZero(file.folder_sort_index, paddingZeroLength) + "." + suffix;
                                    folder.file(file.fileName, blob);
                                }
                            }
                            dfd.resolveWith(file, [blob, folder, isSave]);
                        }, file);
                    } else {
                        dfd.resolveWith(file, [null, folder, false]);
                    }
                }).then(function(blob, folder, isSave){
                    zipFileLength++;
                    notify_start.find(".toast-message").text("正在打包~ 第 " + zipFileLength + " 张" + (isSave ? "" : "跳过"));
                    resolveCallback && resolveCallback();   // resolve延迟对象
                    if (zipFileLength >= maxIndex) {
                        var isDownloadZip = options.callback.allFilesOnload_callback(files, names, location_info, options, zip, main_folder);
                        if (isDownloadZip !== false) {
                            zip.generateAsync({type: "blob"}).then(function (content) {
                                options.callback.beforeZipFileDownload_callback(content, files, names, location_info, options, zip, main_folder);
                            });
                            // GM_notification({text: "打包下载完成!", title: names.zipName, highlight : true});
                            toastr.success("下载完成!", names.zipName, {"progressBar": false, timeOut: 0});
                        }
                        notify_start.css("display", "none").remove();
                    }
                });
            };
            if (maxIndex < options.useQueueDownloadThreshold) {
                // 并发数在useQueueDownloadThreshold内,直接下载
                for (var i = 0; i < maxIndex; i++) {
                    downloadFile(files[i]);
                }
            } else {
                // 并发数在useQueueDownloadThreshold之上,采用队列下载
                var queue = new common_utils.TaskQueue(function (file) {
                    if (file) {
                        var dfd = $.Deferred();
                        downloadFile(file, function () {
                            dfd.resolve();
                        });
                        return dfd;
                    }
                });
                for (var j = 0; j < maxIndex; j++) {
                    queue.append(files[j]);
                }
            }
        } else {
            toastr.remove(notify_start, true);
            toastr.error("未解析到图片!", "错误", {"progressBar": false});
        }
    };

    /** 批量下载 **/
    function batchDownload(config) {
        try {
            options = $.extend(true, options, config);
            var location_info = options.callback.parseLocationInfo_callback(options);
            var files = options.callback.parseFiles_callback(location_info, options);
            if (!(files && files.promise)) {
                files = $.when(files);
            }
            files.done(function (files) {
                if (files && files.length > 0) {
                    if (!options.isNeedConfirmDownload || confirm("是否下载 " + files.length + " 张图片")) {
                        if (options.type == 1) {
                            urlDownload(files, names, location_info, options);
                        } else {
                            var names = options.callback.makeNames_callback(files, location_info, options);
                            ajaxDownloadAndZipFiles(files, names, location_info, options);
                        }
                    }
                } else {
                    toastr.error("未找到图片~", "");
                }
            });
        } catch (e) {
            // GM_notification("批量下载照片 出现错误!", "");
            console.warn("批量下载照片 出现错误!, exception: ", e);
            toastr.error("批量下载照片 出现错误!", "");
        }

    }

    /** 下载 **/
    function urlDownload(photos, names, location_info, options) {
        GM_notification("开始下载~", names.zipName);
        var index = 0;
        var interval = setInterval(function () {
            if (index < photos.length) {
                var url = photos[index].url;
                var fileName = null;
                if (!names.suffix) {
                    fileName = names.prefix + "_" + (index + 1) + url.substring(url.lastIndexOf('.'));
                } else {
                    fileName = names.prefix + "_" + (index + 1) + "." + names.suffix;
                }
                common_utils.downloadUrlFile(url, fileName);
            } else {
                clearInterval(interval);
                return;
            }
            index++;
        }, 100);
    }

    //右键新标签打开图片直接打开原图
    function initRightClickOpenSource() {
        var url = document.location.toString();
        var m;
        if ((m = url.match(/^(https?:\/\/(?:(?:ww|wx|ws|tvax|tva)\d+|wxt|wt)\.sinaimg\.(?:cn|com)\/)([\w\.]+)(\/.+)(?:\?.+)?$/i))) {
            if (m[2] != "large") {
                document.location = m[1] + "large" + m[3];
            }
        }
    }

    /*** start main ***/

    //右键新标签打开图片直接打开原图
    initRightClickOpenSource();

    // 打包下载
    GM_addStyle('.download-link-pop {'+
        '    position: absolute;'+
        '    right: 4px;'+
        '    top: 40px;'+
        '    width: 70%;'+
        '    z-index: 100;'+
        '}'+
        '.download-link-pop > * {'+
        '    padding: 15px;'+
        '}'+
        '.download-link-pop .link-list ul li {'+
        '    border-top: 1px solid #eee;'+
        '    padding-top: 4px;'+
        '    padding-bottom: 4px;'+
        '}'+
        '.download-link-pop .link-list ul li:first-child {'+
        '    border-top: unset;'+
        '    padding-top: 0px;'+
        '}'+
        '.download-link-pop .link-list ul li:last-child {'+
        '    padding-bottom: 0px;'+
        '}'+
        '.download-link-pop .link-list li a{'+
        '    word-break: break-all;'+
        '}'+
        '.download-link-pop .copy-all-link {'+
        '    position: absolute;'+
        '    bottom: -1px;'+
        '    right: -42px;'+
        '    font-weight: bold;'+
        '    font-size: 12px;'+
        '    width: 35px;'+
        '    padding: 3px;'+
        '    background: #fff;'+
        '    border: 1px solid #ccc;'+
        '    border-left-width: 0px;'+
        '    border-bottom-right-radius: 3px;'+
        '    border-top-right-radius: 3px;'+
        '    text-align: center;'+
        '    cursor: pointer;'+
        '    visibility:hidden;'+
        '}'+
        '.download-link-pop:hover .copy-all-link {'+
        '    visibility:visible;'+
        '}'+
        '.download-link-pop .preview {'+
        '    visibility:hidden;'+
        '}'+
        '.download-link-pop .preview img {'+
        '    width: 100%;'+
        '}');

    var addDownloadBtnToWeiboCard = function ($wb_card) {
        var $card_btn_list = $wb_card.find(".WB_feed_detail .WB_screen .layer_menu_list ul:nth-child(1)");
        if ($card_btn_list.find(".WB_card_photos_download").length == 0) {
            $card_btn_list.append('<li class="WB_card_photos_download" title="下载文件和链接"><a>打包下载</a></li>');
            $card_btn_list.append('<li class="WB_card_photos_download WB_card_photos_download_only_download_url" title="仅下载链接"><a>下载链接</a></li>');
            $card_btn_list.append('<li class="WB_card_photos_download WB_card_photos_download_only_print_url" title="仅打印出链接"><a>打印链接</a></li>');
        }
    };

    $("body").on("click", ".WB_cardwrap .WB_screen .ficon_arrow_down", function () {
        addDownloadBtnToWeiboCard($(this).closest(".WB_cardwrap"));
    });
    $("body").on("click", ".WB_cardwrap .WB_screen .layer_menu_list .WB_card_photos_download", function () {
        var $self = $(this);
        var options = {"only_download_url": $self.hasClass('WB_card_photos_download_only_download_url'), "only_print_url": $self.hasClass('WB_card_photos_download_only_print_url')};
        if (options.only_print_url) {
            options.isNeedConfirmDownload = false;
        }
        unsafeWindow.downloadWeiboCardPhotos($self.closest(".WB_cardwrap"), options);
    });

    unsafeWindow.downloadWeiboCardPhotos = function (wb_card_node, options) {
        var $wb_card = $(wb_card_node);
        var config = {
            "$wb_card": $wb_card,
            "type": 2,
            "isNeedConfirmDownload": true, // 下载前是否需要弹出确认框
            "useQueueDownloadThreshold": 0,
            "only_download_url": false, // 是否仅下载链接,true: 只下链接,false:下载文件和链接
            "only_print_url": false, // 是否仅在打印出链接
            "suffix": null,
            "callback": {
                "parseFiles_callback": function (location_info, options) {
                    var $wb_detail = $wb_card.find(".WB_feed_detail .WB_detail");
                    var photo_parse_index = 0;
                    var video_parse_index = 0;
                    var photo_arr = [];
                    // 视频
                    var $wb_video = $wb_detail.find(".WB_media_wrap .media_box .WB_video");
                    if ($wb_video.length != 0) {
                        var feedVideo = {};
                        var feedVideoCoverImg = {};
                        var video_data_str = $wb_video.attr("action-data");
                        var isFeedVideo = video_data_str.match(/&?type=feedvideo\b/) ? true : false;
                        if (isFeedVideo) {
                            feedVideo.url = decodeURIComponent(video_data_str.match(/&video_src=([^&]+)/)[1]);
                            feedVideo.url.indexOf("//") == 0 && (feedVideo.url = "https:" + feedVideo.url);
                            feedVideo.fileName = feedVideo.url.match(/\/([^/?]+?(\.mp4)?)\?/)[1] + (RegExp.$2 ? "" : ".mp4");;
                            feedVideo.folder_sort_index = ++video_parse_index;
                            feedVideo.location = "videos";
                            feedVideoCoverImg.url = decodeURIComponent(video_data_str.match(/&cover_img=([^&]+)/)[1]);
                            feedVideoCoverImg.fileName = feedVideoCoverImg.url.match(/\/([^/]+)$/)[1];
                            if (feedVideoCoverImg.url.indexOf("miaopai.com") != -1 || feedVideoCoverImg.url.indexOf("youku.com") != -1 ) {
                                feedVideoCoverImg.url = feedVideoCoverImg.url;
                                feedVideoCoverImg.url.indexOf("//") == 0 && (feedVideoCoverImg.url = "https:" + feedVideoCoverImg.url);
                            } else {
                                feedVideoCoverImg.url = "https://wx3.sinaimg.cn/large/" + feedVideoCoverImg.fileName;
                            }
                            feedVideoCoverImg.folder_sort_index = ++photo_parse_index;
                            feedVideoCoverImg.location = "photos";
                            photo_arr.push(feedVideo);
                            photo_arr.push(feedVideoCoverImg);
                        }
                        var video_sources_str = $wb_video.attr("video-sources");
                        if (video_sources_str) {
                            // 取清晰度最高的
                            var video_source_list = video_sources_str.split("&").filter(function (line) {
                                return /^\d+=.+/.test(line);
                            }).sort(function (a, b) {
                                return parseInt(a.match(/^(\d+)=/)[1]) < parseInt(b.match(/^(\d+)=/)[1]) ? 1 : -1;
                            }).map(function (url) {
                                return decodeURIComponent(url.replace(/^\d+=/, ""));
                            });
                            if (video_source_list.length > 0) {
                                feedVideo.url = video_source_list[0];
                                feedVideo.fileName = feedVideo.url.match(/\/([^/?]+?(\.mp4)?)\?/)[1] + (RegExp.$2 ? "" : ".mp4");
                            }
                        }
                    }
                    // 微博故事
                    var $wb_story = $wb_detail.find(".WB_media_wrap .media_box .li_story");
                    if ($wb_story.length != 0) {
                        var weiboStoryVideo = {};
                        var weibo_story_data_str = $wb_story.attr("action-data");
                        if (/&gif_ourl=([^&]+)/.test(weibo_story_data_str)) {
                            weiboStoryVideo.url = decodeURIComponent(RegExp.$1);
                        } else if (/&gif_url=([^&]+)/.test(weibo_story_data_str)) {
                            weiboStoryVideo.url = decodeURIComponent(RegExp.$1);
                        }
                        if (weiboStoryVideo.url) {
                            weiboStoryVideo.fileName = weiboStoryVideo.url.match(/\/([^/?]+?(\.mp4)?)\?/)[1] + (RegExp.$2 ? "" : ".mp4");
                            weiboStoryVideo.folder_sort_index = ++photo_parse_index;
                            weiboStoryVideo.location = "videos";
                            photo_arr.push(weiboStoryVideo);
                        }
                    }
                    // 照片
                    var $dataNode = $wb_detail.find(".WB_media_wrap .media_box ul");
                    var pic_data_str = ($dataNode.children('li').length == 1 ? $dataNode.children('li') : $dataNode).attr("action-data");
                    var pic_ids_str_m = pic_data_str && pic_data_str.match(/&pic_ids=([^&]+)/);
                    if (pic_ids_str_m) {
                        // livephoto
                        var pic_video_ids = null;
                        var pic_video_ids_str_m = pic_data_str.match(/&pic_video=([^&]+)/);
                        if (pic_video_ids_str_m) {
                            pic_video_ids = pic_video_ids_str_m[1].split(",").map(function (pair) {
                                return pair.split(":")[1];
                            });
                        }
                        var pic_thumb_str = pic_data_str.match(/&thumb_picSrc=([^&]+)/) && RegExp.$1;
                        var parsePhotosFromIds = function (pic_ids, pic_video_ids) {
                            $.each(pic_ids, function (i, photo_id) {
                                var photo = {};
                                photo.photo_id = photo_id;
                                if (pic_thumb_str && pic_thumb_str.indexOf(photo_id + ".gif") != -1) {
                                    photo.url = "https://wx3.sinaimg.cn/large/" + photo_id + ".gif";
                                } else {
                                    photo.url = "https://wx3.sinaimg.cn/large/" + photo_id + ".jpg";
                                }
                                photo.folder_sort_index = ++photo_parse_index;
                                photo.location = "photos";
                                photo_arr.push(photo);
                            });
                            pic_video_ids && $.each(pic_video_ids, function (i, photo_video_id) {
                                var photo = {};
                                photo.video_id = photo_video_id;
                                photo.url = "https://video.weibo.com/media/play?livephoto=//us.sinaimg.cn/" + photo_video_id + ".mov&KID=unistore,videomovSrc";
                                photo.fileName = photo_video_id + ".mov";
                                photo.folder_sort_index = ++video_parse_index;
                                photo.location = "videos";
                                photo_arr.push(photo);
                            });
                        };
                        if (/over9pic=1&/.test(pic_data_str) && !/isloadedover9pids=1/.test(pic_data_str)) {
                            var deferred = $.Deferred();
                            var isForward = $wb_card.attr("isforward") == "1" ? true : false;
                            var mid;
                            if (!isForward) {
                                mid = $wb_card.attr("mid")
                            } else {
                                mid = $wb_card.find(".WB_feed_detail .WB_detail .WB_feed_expand .WB_expand .WB_handle").attr("mid");
                            }
                            $.get("https://weibo.com/aj/mblog/getover9pic", {
                                "ajwvr": 6,
                                "mid": mid,
                                "__rnd": new Date().getTime(),
                            }, function (response) {
                                let picIds = pic_ids_str_m[1].split(",");
                                response.data.pids.forEach(function(overPid) {
                                    if (picIds.indexOf(overPid) == -1) {
                                        picIds.push(overPid);
                                    }
                                });
                                parsePhotosFromIds(picIds, pic_video_ids);
                                deferred.resolve(photo_arr);
                            });
                            // $wb_detail.find(".WB_media_wrap .media_box ul .WB_pic .W_icon_tag_9p").trigger("click");
                            // setTimeout(function () {
                            //     parsePhotosFromIds($wb_detail.find(".WB_media_wrap .media_box ul").attr("action-data").match(/&pic_ids=([^&]+)&/)[1].split(","));
                            //     deferred.resolve(photo_arr);
                            // }, 1500);
                            return deferred; // 需要异步获取直接返回
                        } else {
                            parsePhotosFromIds(pic_ids_str_m[1].split(","), pic_video_ids);
                        }
                    } else {
                        var $wb_pics = $wb_detail.find(".WB_media_wrap .media_box ul .WB_pic img");
                        var regexp_search = /^(https?:\/\/(?:(?:ww|wx|ws|tvax|tva)\d+|wxt|wt)\.sinaimg\.(?:cn|com)\/)([\w\.]+)(\/.+)(?:\?.+)?$/i;
                        $.each($wb_pics, function (i, img) {
                            var photo = {};
                            var thumb_url = img.src;
                            photo.url = thumb_url;
                            var m = thumb_url.match(regexp_search);
                            if (m) {
                                if (m[2] != "large") {
                                    photo.url = m[1] + "large" + m[3];
                                }
                            }
                            photo.folder_sort_index = ++photo_parse_index;
                            photo.location = "photos";
                            photo_arr.push(photo);
                        });
                    }
                    return photo_arr;
                },
                "makeNames_callback": function (photos, location_info, options) {
                    var names = {};
                    var isForward = $wb_card.attr("isforward") == "1" ? true : false;   // 是否是转发
                    var cards = [];
                    names.infoName = "card_info.txt";
                    names.infoValue = "";
                    var findCardInfo = function ($wb_detail, isForward) {
                        var $user_home_link = $wb_detail.find(".W_fb").eq(0);
                        var $card_link = $wb_detail.find(".WB_from a").eq(0);
                        var user = {};
                        user.uid = $user_home_link.attr("usercard").match(/id=(\d+)/)[1];
                        user.nickname = $user_home_link.attr("nick-name") || $user_home_link.text();
                        user.home_link = $user_home_link.prop("href").replace(/\?.*/, "");
                        var card = {};
                        card.forward = isForward;
                        card.link = $card_link.prop("href").replace(/\?.*/, "");
                        card.id = card.link.match(/\d+\/([A-Za-z0-9]+)$/)[1];
                        card.mid = isForward ? $wb_detail.find(".WB_handle").attr("mid") : $wb_detail.closest(".WB_cardwrap").attr("mid");
                        card.date = $card_link.attr("title");
                        card.date_timestamp = $card_link.attr("date");
                        card.text = $wb_detail.find(".WB_text").eq(0).prop("innerText").replace(/[\u200b]+$/, "").replace(/^\s*|\s*$/g, "");
                        var textLines = card.text.split(/\s{4,}|\s*\n\s*/);
                        card.name = textLines[0];
                        if (card.name.length <= 5 && textLines.length > 1) {
                            card.name += textLines[1];
                        }
                        if (card.name.length > 30) {
                            card.name = card.name.substring(0, 30);
                        }
                        card.photo_count = photos.length;
                        if (!isForward) {
                            var tab_type_flag = $(".WB_main_c").find("div:nth-child(1)").attr("id");
                            if (tab_type_flag && /.*(favlistsearch|likelistoutbox)$/.test(tab_type_flag)) {
                                var $page_list = $(".WB_cardwrap .W_pages .layer_menu_list ul");
                                if ($page_list.length != 0) {
                                    var maxPage = parseInt($page_list.find("li:nth-child(1) > a").text().match(/第(\d+)页/)[1]);
                                    var currPage = parseInt($page_list.find(".cur > a").text().match(/第(\d+)页/)[1]);
                                    card.countdown_page = maxPage - currPage + 1;
                                }
                            }
                        }
                        names.infoValue += "-----------" + (isForward ? "forward card" : "card") + "--------------" + "\r\n";
                        $.each(card, function (key, value) {
                            names.infoValue += (isForward ? "forward_" : "") + "card_" + key + ":" + value + "\r\n";
                        });
                        names.infoValue += "-----------------------------------" + "\r\n";
                        $.each(user, function (key, value) {
                            names.infoValue += (isForward ? "forward_" : "") + "user_" + key + ":" + value + "\r\n";
                        });
                        names.infoValue += "-----------------------------------" + "\r\n";
                        card.user = user;
                        cards.push(card);
                        return card;
                    };
                    names.card = findCardInfo($wb_card.find(".WB_feed_detail .WB_detail"), false);   // 主贴的信息
                    if (isForward) {
                        // 转发的贴的信息
                        names.forwardCard = findCardInfo($wb_card.find(".WB_feed_detail .WB_detail .WB_feed_expand .WB_expand"), true);
                    }
                    names.zipName = cards[0].user.nickname + "_" + cards[0].user.uid + "_" + cards[0].id + "_" + (cards[0].name
                            .replace(/\.\./g, "")
                            .replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, "").replace(/[\u200b]+$/, "")
                            .replace(/(^[_-]+)|([_-]+$)/g, "")
                            .replace(/(^\s+)|(\s+$)/g, ""));
                    names.folderName = names.zipName;
                    names.prefix = null;
                    names.suffix = options.suffix;
                    return names;
                },
                "beforeFilesDownload_callback": function (photos, names, location_info, options, zip, main_folder) {
                    var photo_urls_str = "";
                    $.each(photos, function (i, photo) {
                        if (!photo.fileName) {
                            photo.fileName = photo.url.substring(photo.url.lastIndexOf('/') + 1);
                        }
                        var line = ((photo.location ? (photo.location + "/") : "" ) + photo.fileName) + "\t" + photo.url + "\r\n";
                        photo_urls_str += line;
                    });
                    main_folder.file("photo_url_list.txt", photo_urls_str);
                    options.failFiles = undefined;
                },
                "beforeFileDownload_callback": function (photo, location_info, options, zipFileLength, zip, main_folder, folder) {
                    if (options.only_download_url || options.only_print_url) {
                        return false;
                    } else {
                        return true;
                    }
                },
                "eachFileOnload_callback": function (blob, photo, location_info, options, zipFileLength, zip, main_folder, folder) {
                    if (blob == null) {
                        if (!options.failFiles) {
                            options.failFiles = [];
                        }
                        options.failFiles.push(photo);
                    }
                    return true;
                },
                "allFilesOnload_callback": function (photos, names, location_info, options, zip, main_folder) {
                    if (options.failFiles && options.failFiles.length > 0) {
                        toastr.error("共 " + options.failFiles.length + " 张下载失败,已记录在photos_fail_list.txt!", "", {
                            "progressBar": false,
                            timeOut: 0
                        });
                        var failPhotoListStr = "";
                        for (var i in options.failFiles) {
                            var failFile = options.failFiles[i];
                            failPhotoListStr += (failFile.location + "/" + failFile.fileName + "\t" + failFile.url + "\r\n");
                        }
                        main_folder.file("photos_fail_list.txt", failPhotoListStr);
                    }
                    if (options.only_print_url) {
                        const $pop = $('<div class="W_layer W_layer_pop download-link-pop"><div class="content link-list"><span class="copy-all-link" title="复制所有链接">复制</span><ul></ul></div><div class="content preview"><img></div></div>'),
                              $link_ul = $pop.find('.link-list ul'),
                              $preview_img = $pop.find('.preview img');
                        $wb_card.find('.WB_feed_detail').append($pop);
                        $.each(photos, function(i, photo) {
                            $link_ul.append(`<li><a href="${photo.url}" target="_blank" download="${photo.fileName}" data-location="${photo.location}" class="clearfix" title="${photo.location == 'photos' ? '点击下载,右键链接另存为' : '右键链接另存为'}">${photo.url}</a></li>`);
                        });
                        $link_ul.on({
                            'mouseenter': function() {
                                let $self = $(this);
                                if ($self.attr('data-location') == 'photos') {
                                    $preview_img.attr('src', $self.attr('href').replace('/large/', '/mw690/')).parent().css('visibility', 'visible');
                                }
                            },
                            'mouseleave': function() {
                                $preview_img.attr('src', '').parent().css('visibility', 'hidden');
                            },
                            'click': function(e) {
                                let $self = $(this), url = $self.attr('href'), fileName = $self.attr('download');
                                if ($self.attr('data-location') == 'photos') {
                                    let notify_download_media = toastr.success('正在下载图片~', '', {
                                        "progressBar": false,
                                        "hideDuration": 0,
                                        "showDuration": 0,
                                        "timeOut": 0,
                                        "closeButton": false,
                                    });
                                    // GM_download({'url': url, 'name': fileName, 'saveAs': true});
                                    common_utils.ajaxDownload(url, function (blob) {
                                        common_utils.downloadBlobFile(blob, fileName);
                                        notify_download_media.css("display", "none").remove();
                                    });
                                    // e.stopImmediatePropagation();
                                    return false;
                                }
                            }
                        }, 'li a');
                        $pop.on('click', '.copy-all-link', function() {
                            GM_setClipboard($link_ul[0].innerText);
                            toastr.success('复制全部链接成功');
                        });
                        function remove(ev) {
                            if(!ev.target.classList.contains('download-temp-node') && !$pop[0].contains(ev.target)){
                                $pop.remove();
                                $('body').off("click", remove);
                            }
                        }
                        $('body').on("click", remove);
                        console.log('--★-- print -- ' + names.folderName + ' ----★--');
                        console.table(JSON.parse(JSON.stringify(photos)), ['location', 'url']);
                        console.log('当url被省略可以复制下面的链接,也可从上面 >Array(' + photos.length + ') 查看');
                        $.each(photos, function (i, photo) {
                            console.log(photo.url);
                        });
                        toastr.success("已打印");
                        return false;
                    }
                }
            }
        };
        if (options) {
            $.extend(true, config, options);
        }
        batchDownload(config);
    };

    // 收藏备份
    const KEY_FAV_BACKUP_GROUP = 'weibo_fav_backup_group';
    var showFavWeiboRestoreBtn = function () {
        $('.WB_cardwrap:not(.has-set-restore-btn) .WB_empty .WB_innerwrap p').prepend('<button class="restore-backup-fav-weibo" style="margin-right:15px;cursor:pointer;">查看备份</button>').closest('.WB_cardwrap').addClass('has-set-restore-btn');
    }
    $('body').on('click', '.WB_cardwrap .W_pages', function() {
        setTimeout(function() {
            showFavWeiboRestoreBtn();
        }, 3000);
        setTimeout(function() {
            showFavWeiboRestoreBtn();
        }, 4500);
        setTimeout(function() {
            showFavWeiboRestoreBtn();
        }, 6000);
    });
    $('body').on('mouseenter', '.WB_cardwrap:not(.has-set-restore-btn) .WB_empty', function() {
        showFavWeiboRestoreBtn();
    });
    $('body').on('click', '.WB_cardwrap .WB_feed_handle a[action-type="fl_favorite"]', function () {
        var $self = $(this), $wb_card = $self.closest(".WB_cardwrap"), isHasFavorite = $self.attr('favorite') == '1';
        if (!isHasFavorite) {
            setTimeout(function() {
                if (!unsafeWindow.confirm('是否将收藏中的链接备份到缓存,以防止博主删除?')) {
                    return;
                }
                var options = {
                    "only_print_url": true,
                    "isNeedConfirmDownload": false,
                    "callback": {
                        "allFilesOnload_callback": function (photos, names, location_info, options, zip, main_folder) {
                            let saveCard = {}, user = {}, picNames = [], livePhotos = [], card = names.forwardCard || names.card;
                            saveCard.id = card.id;
                            saveCard.mid = card.mid;
                            saveCard.date = card.date;
                            saveCard.text = card.text;
                            user.uid = card.user.uid;
                            user.nickname = card.user.nickname;
                            $.each(photos, function (i, photo) {
                                if (photo.location == 'photos') {
                                    picNames.push(photo.fileName);
                                } else if (photo.location == 'videos' && /\.mov$/.test(photo.fileName)) {
                                    livePhotos.push(photo.fileName);
                                }
                            });
                            saveCard.user = user;
                            saveCard.photos = picNames;
                            saveCard.livePhotos = livePhotos;
                            let fav_backup_group = GM_getValue(KEY_FAV_BACKUP_GROUP, {});
                            fav_backup_group[String(saveCard.mid)] = saveCard;
                            GM_setValue(KEY_FAV_BACKUP_GROUP, fav_backup_group);
                            toastr.success("收藏备份到本地成功~");
                            return false;
                        },
                    }
                };
                unsafeWindow.downloadWeiboCardPhotos($wb_card, options);
            }, 700);
        } else {
            let mid, isForward = $wb_card.attr("isforward") == "1" ? true : false;
            if (!isForward) {
                mid = $wb_card.find(".WB_feed_detail .WB_detail").find(".WB_from a").eq(0).prop("href").replace(/\?.*/, "").match(/\d+\/([A-Za-z0-9]+)$/)[1];
            } else {
                mid = $wb_card.find(".WB_feed_detail .WB_detail .WB_feed_expand .WB_expand").find(".WB_from a").eq(0).prop("href").replace(/\?.*/, "").match(/\d+\/([A-Za-z0-9]+)$/)[1];
            }
            let fav_backup_group = GM_getValue(KEY_FAV_BACKUP_GROUP, {});
            delete fav_backup_group[String(mid)];
            GM_setValue(KEY_FAV_BACKUP_GROUP, fav_backup_group);
            toastr.success("删除收藏本地备份成功~");
        }
    });
    $('body').on('click', '.WB_cardwrap .restore-backup-fav-weibo', function () {
        var $self = $(this), $wb_card = $self.closest(".WB_cardwrap"), mid;
        mid = $wb_card.find('.WB_empty').attr('mid');
        if (!$self.hasClass('has-restore')) {
            let card = GM_getValue(KEY_FAV_BACKUP_GROUP, {})[String(mid)];
            if (card) {
                $wb_card.append('<div class="WB_feed_detail clearfix" style="padding-top:0px;"><div class="WB_detail"><div class="WB_info" style="display:inline-block;"><a target="_blank"></a></div>' +
                                '<div class="WB_from" style="display:inline-block;margin-left:10px"><a target="_blank"></a></div><div class="WB_text"><div><div></div>');
                let $pop = $('<div class="W_layer W_layer_pop download-link-pop" style="position:relative;top:0px;right:0px;margin:0 auto;padding:0px 20px 10px;z-index:50;"><div class="content link-list"><span class="copy-all-link" title="复制所有链接">复制</span><ul></ul></div>' +
                             '<div class="content preview" style="position: absolute;"><img></div></div>'),
                    $link_ul = $pop.find('.link-list ul'), $preview_img = $pop.find('.preview img');
                $wb_card.append($pop);
                $wb_card.find('.WB_detail .WB_info a').text(card.user.nickname).attr('href', '//weibo.com/u/' + card.user.uid);
                $wb_card.find('.WB_detail .WB_from a').text(card.date).attr('href', '//weibo.com/' + card.user.uid + '/' + card.id);
                $wb_card.find('.WB_detail .WB_text').text(card.text);
                card.photos && $.each(card.photos, function(i, fileName) {
                    let photo = {url: 'https://wx3.sinaimg.cn/large/' + fileName, fileName: fileName, location: 'photos'};
                    $link_ul.append(`<li><a href="${photo.url}" target="_blank" download="${photo.fileName}" data-location="${photo.location}" class="clearfix" title="${photo.location == 'photos' ? '点击下载,右键链接另存为' : '右键链接另存为'}">${photo.url}</a></li>`);
                });
                card.livePhotos && $.each(card.livePhotos, function(i, fileName) {
                    let photo = {url: 'https://video.weibo.com/media/play?livephoto=//us.sinaimg.cn/' + fileName + '&KID=unistore,videomovSrc', fileName: fileName, location: 'videos'};
                    $link_ul.append(`<li><a href="${photo.url}" target="_blank" download="${photo.fileName}" data-location="${photo.location}" class="clearfix" title="${photo.location == 'photos' ? '点击下载,右键链接另存为' : '右键链接另存为'}">${photo.url}</a></li>`);
                });
                $link_ul.on({
                    'mouseenter': function() {
                        let $self = $(this);
                        if ($self.attr('data-location') == 'photos') {
                            $preview_img.attr('src', $self.attr('href').replace('/large/', '/mw690/')).parent().css('visibility', 'visible');
                        }
                    },
                    'mouseleave': function() {
                        $preview_img.attr('src', '').parent().css('visibility', 'hidden');
                    },
                    'click': function(e) {
                        let $self = $(this), url = $self.attr('href'), fileName = $self.attr('download');
                        if ($self.attr('data-location') == 'photos') {
                            let notify_download_media = toastr.success('正在下载图片~', '', {
                                "progressBar": false,
                                "hideDuration": 0,
                                "showDuration": 0,
                                "timeOut": 0,
                                "closeButton": false,
                            });
                            // GM_download({'url': url, 'name': fileName, 'saveAs': true});
                            common_utils.ajaxDownload(url, function (blob) {
                                common_utils.downloadBlobFile(blob, fileName);
                                notify_download_media.css("display", "none").remove();
                            });
                            // e.stopImmediatePropagation();
                            return false;
                        }
                    }
                }, 'li a');
                $pop.on('click', '.copy-all-link', function() {
                    GM_setClipboard($link_ul[0].innerText);
                    toastr.success('复制全部链接成功');
                });
                $self.text('删除备份').addClass('has-restore').attr('disabled', 'disabled');
                setTimeout(function(){
                    $self.removeAttr('disabled');
                }, 600);
            } else {
                toastr.info("本地备份没有备份该收藏~");
            }
        } else {
            let fav_backup_group = GM_getValue(KEY_FAV_BACKUP_GROUP, {});
            delete fav_backup_group[String(mid)];
            GM_setValue(KEY_FAV_BACKUP_GROUP, fav_backup_group);
            toastr.success("删除收藏本地备份成功~");
            $self.remove();
        }
    });
    setTimeout(function() {
        var tab_type_flag = $(".WB_main_c").find("div:nth-child(1)").attr("id");
        if (tab_type_flag && /.*(favlistsearch)$/.test(tab_type_flag)) {
            showFavWeiboRestoreBtn();
        }
    }, 2000);
    setTimeout(function() {
        var tab_type_flag = $(".WB_main_c").find("div:nth-child(1)").attr("id");
        if (tab_type_flag && /.*(favlistsearch)$/.test(tab_type_flag)) {
            showFavWeiboRestoreBtn();
        }
    }, 4000);


})(document, jQuery);