// ==UserScript== // @name 批量下载微博原图、视频、livephoto // @name:zh 批量下载微博原图、视频、livephoto // @name:en Batch Download Src Image From Weibo Card // @version 1.5 // @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 http://*.sinaimg.cn/* // @match https://*.sinaimg.cn/* // @match http://*.sinaimg.com/* // @match https://*.sinaimg.com/* // @grant GM_xmlHttpRequest // @grant GM.xmlHttpRequest // @grant GM_notification // @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 https://greasyfork.org/users/129338 // @downloadURL none // ==/UserScript== // @weibo http://weibo.com/3983281402 // @blog https://imcoder.site // @date 2019.12.26 // @更新日志 // 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(''); 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.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 = []; 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(); 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('