Greasy Fork

Greasy Fork is available in English.

批量下载贴吧原图

一键打包下载贴吧中一贴的原图

当前为 2021-02-13 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            批量下载贴吧原图
// @name:zh         批量下载贴吧原图
// @name:en         Batch srcImage downloader for tieba
// @namespace       https://github.com/Jeffrey-deng/userscript
// @version         3.4.2
// @description     一键打包下载贴吧中一贴的原图
// @description:zh  一键打包下载贴吧中一贴的原图
// @description:en  Batch Download Src Image From Baidu Tieba
// @author          Jeffrey.Deng
// @supportURL      https://imcoder.site/a/detail/HuXBzyC
// @homepageURL     https://imcoder.site
// @weibo           http://weibo.com/3983281402
// @match           http://tieba.baidu.com/*
// @match           https://tieba.baidu.com/*
// @match           http://imgsrc.baidu.com/*
// @match           https://imgsrc.baidu.com/*
// @match           http://tiebapic.baidu.com/*
// @match           https://tiebapic.baidu.com/*
// @connect         baidu.com
// @connect         bdimg.com
// @require         https://cdn.bootcss.com/jquery/1.11.1/jquery.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
// @resource        toastr_css https://cdn.bootcss.com/toastr.js/2.1.3/toastr.min.css
// @grant           GM.xmlHttpRequest
// @grant           GM_xmlHttpRequest
// @grant           GM_notification
// @grant           GM_addStyle
// @grant           GM_getResourceText
// @grant           GM_registerMenuCommand
// ==/UserScript==

// @更新日志
// v.3.4.1      2020.7.11      1.修复jQuery下载失败问题
// v.3.3        2020.6.3       1.修复图片被删除但页面仍能看到却下载不到的问题
// v.3.1        2020.5.26      1.支持只下载楼主
//                             2.图片后缀名根据图片实际类型命名
// v.3.0        2020.5.21      1.支持下载多页
//                             2.支持下载被吞掉的图
// v.2.6.1      2019.12.16     1.修改压缩包名称为帖子的标题
//                               如果还是要以前的id作为压缩包名称,那么修改449行:"packNameBy": "title",
//                               将 packNameBy 的值 title 修改为 id, 再按 ctrl + s 保存。
// v.2.6        2.19.12.16     1.修改图片域名为tiebapic.baidu.com时下载图片显示“你查看的图片不存在的”的问题
// v 2.5.1      2019.12.11     1.修复格式化数字排序未生效的问题
// V 2.5        2019.12.2      1.修改为toastr提示方式
//                             2.采用队列下载
// V 2.4        2019.3.17      1.调整图片排序的命名,格式化数字(1显示为01),便于查看时顺序一样
//                             2.edge会闪退,原因不知,未修复
// V 2.3        2018.5.31      1.兼容edge
// V 2.2        2018.4.7       1.调整匹配图片策略
// V 2.1        2018.4.2       1.调用Tampermonkey API 实现跨域下载,无需修改启动参数
// V 2.0        2018.4.1       1.压缩包内增加贴子地址txt
//                             2.修复https不能下载
// V 1.9        2018.4.1       1.新增打包下载,图片重命名(需开启浏览器跨域)
// V 1.8        2018.3.31      1.修复BUG
//                             2.可自定义输入文件名后缀
// V 1.7        2017.6.9       1.修复魅族等贴吧下载图标不显示的问题
// V 1.6        2017.6.5       1.提高下载的图片正确率
// V 1.5        2017.6.4       1.增加右键新标签打开图片直接打开原图
// V 1.4        2017.6.3       1.更新对 https 的支持
//                             2.提高图片匹配成功率

(function (factory) {
    factory(document, jQuery);
    // console.time('ready_init_use_time');
    // $().ready(function(){
    //     console.timeEnd('ready_init_use_time');
    //     factory(document, jQuery);
    // });
})(function (document, $) {

    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,
                clearUrl = url.replace(/[&\?]?download_timestamp=\d+/, ''),
                retryUrl = clearUrl + (clearUrl.indexOf('?') === -1 ? '?' : '&') + 'download_timestamp=' + new Date().getTime();
            GM_download({
                method: 'GET',
                responseType: 'blob',
                url: url,
                onreadystatechange: function (responseDetails) {
                    if (responseDetails.readyState === 4) {
                        if (responseDetails.status === 200 || responseDetails.status === 304 || responseDetails.status === 0) {
                            var blob = responseDetails.response, size = blob && blob.size;
                            if (size && (size / 1024 >= 5)) {
                                callback(blob, args);
                            } else if (tryTimes++ == 3) {
                                callback(blob, args);
                            } else {
                                ajaxDownload(retryUrl, callback, args, tryTimes);
                            }
                        } else {
                            if (tryTimes++ == 3) {
                                callback(null, args);
                            } else {
                                ajaxDownload(retryUrl, callback, args, tryTimes);
                            }
                        }
                    }
                },
                onerror: function (responseDetails) {
                    if (tryTimes++ == 3) {
                        callback(null, args);
                    } else {
                        ajaxDownload(retryUrl, 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.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.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 = [];
                        });
                        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]);
                    }
                }).done(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 {
            notify_start.css("display", "none").remove();
            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 + " 张图片")) {
                        var names = options.callback.makeNames_callback(files, location_info, options);
                        options.location_info = location_info;
                        options.files = files;
                        options.names = names;
                        if (options.type == 1) {
                            urlDownload(files, names, location_info, options);
                        } else {
                            ajaxDownloadAndZipFiles(files, names, location_info, options);
                        }
                    }
                } else {
                    toastr.error("未找到图片~", "");
                }
            }).fail(function (message) {
                toastr.error(message, "错误");
            });
        } 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 = null;
        if (!(m = url.match(/^https?:\/\/(imgsrc|tiebapic)\.baidu\.com\/forum\/pic\/item\/.+/i))) {
            if ((m = url.match(/^(https?):\/\/(imgsrc|imgsa|tiebapic|\w+\.hiphotos)\.(?:bdimg|baidu)\.com\/(?:forum|album)\/.+\/(\w+\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i))) {
                document.location = m[1] + "://" + (m[2] == "tiebapic" ? "tiebapic" : "imgsrc") + ".baidu.com/forum/pic/item/" + m[3];
            }
        }
    }

    /*** start main ***/

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

    var getReplyAuthorUid = function ($reply) { // 获取回复UID
        let userInfoStr = $reply.find('.d_name').attr('data-field');
        return (userInfoStr && userInfoStr.match(/"user_id":(\d+)/) && RegExp.$1) || 0;
    }

    var getPostAuthorUid = function () { // 获取楼主UID
        return getReplyAuthorUid($('#j_p_postlist').children('.j_l_post').first());
    }

    // css
    GM_addStyle(GM_getResourceText('toastr_css'));
    // 添加按钮
    var $lis_nav = $('#tb_nav').find('ul').eq(0).find('li'),
        li_count = $lis_nav.length,
        $li_right = $lis_nav.eq(li_count - 1),
        html = '';
    if ($li_right.hasClass('none_right_border')) {
        var isStarTie = $li_right.hasClass('star_nav_tab');
        if (isStarTie) {
            html = '<li class="none_right_border star_nav_tab" style="cursor: pointer"><div class="star_nav_tab_inner"><div class="space">' +
                '<a title="点击下载本页图片" class="star_nav_ico star_nav_ico_photo" id="batchDownloadBtn"><i class="icon"></i>下载</a></div></div></div>';
        } else {
            html = '<li class="none_right_border j_tbnav_tab" style="cursor: pointer"><div class="tbnav_tab_inner"><p class="space">' +
                '<a title="点击下载本页图片" class="nav_icon icon_jingpin  j_tbnav_tab_a" id="batchDownloadBtn"  location="tabplay" >下载</a></p></div></div>';
        }
        $li_right.removeClass('none_right_border').after(html);
    } else {
        html = '<li class="j_tbnav_tab" style="cursor: pointer"><a class="j_tbnav_tab_a" id="batchDownloadBtn">下载</a> </li>';
        $li_right.after(html);
    }
    // 仪表盘控制栏添加按钮
    GM_registerMenuCommand('下载图片', function() {
        tiebaImagesDownload();
    });
    GM_registerMenuCommand('只下楼主', function() {
        tiebaImagesDownload({"onlyLz": true});
    });

    $('#batchDownloadBtn').click(function () {
        tiebaImagesDownload({"onlyLz": (document.location.href.indexOf('see_lz=1') !== -1 && confirm("是否只下载楼主的图片"))});
    });

    var tiebaImagesDownload = unsafeWindow.tiebaImagesDownload = function (options) {
        var config = {
            "type": 2,
            "minWidth": 100,
            "suffix": null,
            "packNameBy": "title", // "id" or "title"
            "baiduLoadPhotosApi": "https://tieba.baidu.com/photo/bw/picture/guide",
            "findPhotoByApi": true,
            "onlyLz": false,
            "callback": {
                "parseFiles_callback": function (location_info, options) {
                    let pn = location_info.params.pn || 1,
                        authorUid = getPostAuthorUid(),
                        findPhotosByPage = function () {
                            let photo_arr = [],
                                $part_nodes_one = $('.d_post_content,.d_post_content_main').find("img");
                            //var part_nodes_two = $('.d_post_content_main,.post_bubble_middle,.d_post_content').find("img");
                            $.each($part_nodes_one, function (i, img) {
                                let $img = $(img);
                                if (options.onlyLz) { // 只下楼主
                                    let replyUid = getReplyAuthorUid($img.closest('.j_l_post'));
                                    if (replyUid != authorUid) {
                                        return;
                                    }
                                }
                                // 如果是广告图片则跳过
                                if (img.parentNode.tagName == "A" && img.parentNode.className.indexOf("j_click_stats") != -1) {
                                    return true;
                                }
                                if (img.clientWidth >= options.minWidth) {
                                    if ($img.hasClass("BDE_Image") || $img.hasClass("d_content_img")) {
                                        var photo = {};
                                        photo.location = "";
                                        var thumb_url = img.src;
                                        photo.folder_sort_index = photo_arr.length + 1;
                                        // 如果是用户上传的图片
                                        if ($img.attr("pic_type") == "0") {
                                            var urlMatcher = thumb_url.match(/^(https?):\/\/([a-zA-Z]+)\..*\/([^/]+)$/);
                                            photo.url = urlMatcher[1] + "://" + (urlMatcher[2] == "tiebapic" ? "tiebapic" : "imgsrc") + ".baidu.com/forum/pic/item/" + urlMatcher[3];
                                            photo.id = urlMatcher[3].match(/^[^.]+/)[0];
                                        }
                                        // 如果是用户引用的图片
                                        else {
                                            var m = thumb_url.match(/^(https?):\/\/(imgsrc|imgsa|tiebapic|\w+\.hiphotos)\.(?:bdimg|baidu)\.com\/(?:forum|album)\/.+\/((\w+)\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i);
                                            // 如果引用的是贴吧图片
                                            if (m !== null) {
                                                photo.url = m[1] + "://" + (m[2] == "tiebapic" ? "tiebapic" : "imgsrc") + ".baidu.com/forum/pic/item/" + m[3];
                                                photo.id = m[4];
                                            } else {
                                                photo.url = thumb_url;
                                            }
                                        }
                                        photo.size = $img.attr("size") || 0;
                                        photo.location = "photos";
                                        photo_arr.push(photo);
                                    }
                                }
                            });
                            return photo_arr;
                        };
                    let notify_photo_data_loading = toastr.success("正在请求图片数据~", "", {
                        "progressBar": false,
                        "hideDuration": 0,
                        "showDuration": 0,
                        "timeOut": 0,
                        "closeButton": false
                    });
                    return $.Deferred(function (finalDfd) {
                        if (options.findPhotoByApi) {
                            let photo_arr = [], curr_load_count = 0, loadQueue = new common_utils.TaskQueue(function (startPicId) {
                                return $.Deferred(function (dfd) {
                                    $.get(options.baiduLoadPhotosApi, {
                                        'tid': location_info.file,
                                        'see_lz': options.onlyLz ? 1 : 0, // 只下楼主
                                        'from_page': 0,
                                        // 'alt': 'jview',
                                        'next': 50,
                                        'prev': 0,
                                        'pic_id': startPicId,
                                        '_': new Date().getTime(),
                                    }, function (resp) {
                                        let data = resp.data;
                                        if (data && data.pic_list) {
                                            let pic_amount = data.pic_amount,
                                                pic_list = data.pic_list,
                                                lastPicId,
                                                startPushPic = false;
                                            $.each(pic_list, function (key, pic) {
                                                let original = pic.img.original, photo;
                                                switch (true) {
                                                    case original.id == startPicId:
                                                        startPushPic = true;
                                                        break;
                                                    case !startPicId:
                                                        startPushPic = true;
                                                    case startPushPic:
                                                        photo = {};
                                                        photo.location = "photos";
                                                        photo.folder_sort_index = photo_arr.length + 1;
                                                        photo.id = original.id;
                                                        photo.url = (original.waterurl && original.waterurl.replace(/^http:\/\//, 'https://')) ||
                                                            (`https://imgsrc.baidu.com/forum/pic/item/${original.id}.jpg`);
                                                        photo.size = original.size;
                                                        photo_arr.push(photo);
                                                        curr_load_count++;
                                                        lastPicId = original.id;
                                                }
                                            });
                                            if (lastPicId && curr_load_count < pic_amount) {
                                                loadQueue.append(lastPicId);
                                            } else {
                                                // 队列下载结束
                                                // 对比页面数据和api返回数据,两者合并结果,并尝试按页面显示顺序排序
                                                let combine_photo_arr = [],
                                                    page_photo_arr = findPhotosByPage().filter(function(photo) {
                                                        return photo.size != 0; // 有些页面图片没写size,所以这里过滤了没写size的,暂时先这样处理
                                                    }).map(function(photo) {
                                                        let has_delete = true;
                                                        if (photo.id) {
                                                            for (let p of photo_arr) {
                                                                // 由于同样一张图片,id有两个,这里采用对比文件大小的方式来确定是否同一张图片
                                                                if (p.id == photo.id || (p.size != 0 && p.size == photo.size)) {
                                                                    has_delete = false;
                                                                    break;
                                                                }
                                                            }
                                                        }
                                                        photo.has_delete = has_delete;
                                                        return photo;
                                                    }),
                                                    pageLength = page_photo_arr.length,
                                                    serverLength = photo_arr.length,
                                                    hasDeleteLength = page_photo_arr.filter(function(photo) {
                                                        return photo.has_delete;
                                                    }).length;
                                                if (hasDeleteLength > 0) {
                                                    let start_left_index = 0, start_right_index = 0, unshift_length = 0, i, j,
                                                        photo_url_arr = photo_arr.map(function (photo) {
                                                            return photo.url;
                                                        });
                                                    pn > 1 && $.each(page_photo_arr, function(i, photo) {
                                                        let index = photo_url_arr.indexOf(photo.url);
                                                        if (index != -1) {
                                                            start_right_index = index;
                                                            start_left_index = i;
                                                            return false;
                                                        } else {
                                                            unshift_length++;
                                                        }
                                                    });
                                                    if (start_right_index > 0) {
                                                        combine_photo_arr.push.apply(combine_photo_arr, photo_arr.slice(0, start_right_index));
                                                    }
                                                    if (start_left_index > 0) {
                                                        combine_photo_arr.push.apply(combine_photo_arr, page_photo_arr.slice(start_left_index - unshift_length, start_left_index));
                                                    }
                                                    for (i = start_left_index, j = start_right_index; i < pageLength && j < serverLength;) {
                                                        let photo, left = page_photo_arr[i], right = photo_arr[j];
                                                        if (left.id === right.id || (left.size != 0 && left.size == right.size)) {
                                                            photo = right;
                                                            i++;
                                                            j++;
                                                        } else {
                                                            if (left.has_delete) {
                                                                photo = left;
                                                                i++;
                                                            } else {
                                                                photo = right;
                                                                j++;
                                                            }
                                                        }
                                                        combine_photo_arr.push(photo);
                                                    }
                                                    if (i <= pageLength - 1) {
                                                        combine_photo_arr.push.apply(combine_photo_arr, page_photo_arr.slice(i, pageLength));
                                                    }
                                                    if (j <= serverLength - 1) {
                                                        combine_photo_arr.push.apply(combine_photo_arr, photo_arr.slice(j, serverLength));
                                                    }
                                                    $.each(combine_photo_arr, function(i, photo) {
                                                        photo.folder_sort_index = i + 1;
                                                    });
                                                } else {
                                                    combine_photo_arr = photo_arr;
                                                }
                                                finalDfd.resolve(combine_photo_arr);
                                            }
                                            dfd.resolve();
                                        } else {
                                            dfd.reject('api返回错误');
                                        }
                                    }, 'json').fail(function () {
                                        dfd.reject('api返回错误');
                                    });
                                }).fail(function (msg) {
                                    console.warn(msg);
                                    options.findPhotoByApi = false;
                                    finalDfd.resolve(findPhotosByPage());
                                });
                            });
                            loadQueue.append(null);
                        } else {
                            finalDfd.resolve(findPhotosByPage());
                        }
                    }).always(function () {
                        notify_photo_data_loading.css("display", "none").remove();
                    });
                },
                "makeNames_callback": function (photos, location_info, options) {
                    var names = {},
                        tie_id = location_info.file,
                        pn = location_info.params.pn || 1,
                        title = $(".core_title_txt").attr("title"),
                        forum = ($('#container').find('.card_title a.card_title_fname').text() || '贴').replace(/^\s*|\s*$/g, '');
                    names.infoName = "tie_info.txt";
                    names.infoValue = "id:" + tie_id + "\r\n" +
                        "title:" + title + "\r\n" +
                        "url:" + location_info.source + "\r\n" +
                        "page:" + pn + "\r\n" +
                        "image_amount:" + photos.length + "\r\n";
                    names.zipName = (options.packNameBy == "id" ? ("tie_" + tie_id) : (forum + '_' + tie_id + '_' + title)) + ((options.findPhotoByApi || pn == 1) ? "" : ("_" + pn));
                    names.folderName = names.zipName;
                    names.prefix = tie_id + (options.findPhotoByApi ? "" : ("_" + common_utils.paddingZero(pn, 3)));
                    names.suffix = options.suffix;
                    names.tie = {
                        'id': tie_id,
                        'title': title,
                        'pn': pn,
                        'forum': forum
                    };
                    return names;
                },
                "beforeFilesDownload_callback": function (photos, names, location_info, options, zip, main_folder) {
                    const paddingZeroLength = (photos.length + "").length;
                    $.each(photos, function (i, photo) {
                        photo.fileName = names.prefix + "_" + common_utils.paddingZero(photo.folder_sort_index, paddingZeroLength) + "." + (names.suffix || photo.url.substring(photo.url.lastIndexOf('.') + 1));
                    });
                    options.failFiles = undefined;
                },
                "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);
                    } else if (!options.names.suffix && photo.location == 'photos' && blob.type && blob.type.indexOf('image/') === 0) {
                        // 如果没有指定后缀名,那么后缀根据content-type来判断
                        let suffixRegex = /\.[^.]+$/, suffix = ('.' + blob.type.replace('image/', '').replace('jpeg', 'jpg'));
                        photo.fileName = photo.fileName.replace(suffixRegex, suffix);
                        photo.url = photo.url.replace(suffixRegex, suffix);
                    }
                    return true;
                },
                "allFilesOnload_callback": function (photos, names, location_info, options, zip, main_folder) {
                    let photo_urls_str = "", failPhotoListStr = "";
                    // 链接列表文件
                    $.each(photos, function (i, photo) {
                        photo_urls_str += ((photo.location ? (photo.location + "/") : "" ) + photo.fileName) + "\t" + photo.url + "\r\n";
                    });
                    main_folder.file("photo_url_list.txt", photo_urls_str);
                    // 帮助文件
                    main_folder.file("帮助.txt", "有些图片可能下载下来是裂掉的缩略图,可以从photo_url_list.text中按文件名手动找到链接下载。");
                    // 失败链接列表
                    if (options.failFiles && options.failFiles.length > 0) {
                        toastr.error("共 " + options.failFiles.length + " 张下载失败,已记录在photos_fail_list.txt!", "", {"progressBar": false, timeOut: 0});
                        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) {
            $.extend(true, config, options);
        }
        batchDownload(config);
    };

});