// ==UserScript==
// @name 批量下载微博原图、视频、livephoto
// @name:zh 批量下载微博原图、视频、livephoto
// @name:en Batch Download Src Image From Weibo Card
// @version 1.8.4
// @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 https://gf.qytechs.cn/users/129338
// ==/UserScript==
// @weibo http://weibo.com/3983281402
// @blog https://imcoder.site
// @date 2019.12.26
// @更新日志
// V 1.8.3 2020.05.12 1.优化收藏备份
// 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>');
$card_btn_list.append('<li class="WB_card_photos_show_fav_weibo_backup" title="查看当前微博是否有备份"><a>显示备份</a></li>');
$card_btn_list.append('<li class="WB_card_photos_rebuild_fav_weibo_backup" title="重新备份当前微博"><a>重新备份</a></li>');
}
};
var showPhotoLinksPopPanel = function ($wb_card, photos, wb_card_name, removeOnClickBody) {
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');
removeOnClickBody = removeOnClickBody !== false;
$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('复制全部链接成功');
});
if (removeOnClickBody) {
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('\n--★-- print -- ' + (wb_card_name || '照片链接') + ' ----★--');
console.table(JSON.parse(JSON.stringify(photos)), ['location', 'url']);
console.log('当url被省略可以复制下面的链接,也可从上面 >Array(' + photos.length + ') 查看');
$.each(photos, function (i, photo) {
console.log(photo.url);
});
return $pop;
}
var findWeiboCardMid = function ($wb_card, findForwardIfHas) {
let mid, isForward = $wb_card.attr("isforward") == '1' ? true : false,
isWeiboDelete = $wb_card.children('.WB_empty').length != 0;
if (isForward && findForwardIfHas) {
mid = $wb_card.attr('omid') || $wb_card.find('.WB_feed_detail .WB_detail .WB_feed_expand .WB_expand').find('.WB_handle').attr('mid');
} else {
mid = $wb_card.attr('mid');
if (!mid && isWeiboDelete) {
mid = $wb_card.children('.WB_empty').attr('mid');
}
}
return mid;
};
$("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 = "", photo_url;
$.each(photos, function (i, photo) {
if (!photo.fileName) {
photo.fileName = photo.url.substring(photo.url.lastIndexOf('/') + 1);
}
if (photo.location == 'videos' && photo.url.indexOf('f.video.weibocdn.com') != -1 && photo.url.indexOf('&Expires=') == -1 && !/\.mov$/.test(photo.fileName)) {
photo_url = 'http://f.video.weibocdn.com/' + photo.fileName + '?KID=unistore,video';
} else {
photo_url = photo.url;
}
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) {
showPhotoLinksPopPanel($wb_card, photos, names.folderName);
toastr.success("已打印");
return false;
}
}
}
};
if (options) {
$.extend(true, config, options);
}
batchDownload(config);
};
// 收藏备份
const KEY_FAV_BACKUP_GROUP = 'weibo_fav_backup_group';
var addFavWeiboBackup = function ($wb_card) { // 保存备份
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 = [], videos = [], 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') {
if (/\.mov$/.test(photo.fileName)) {
livePhotos.push(photo.fileName);
} else if (photo.url.indexOf('f.video.weibocdn.com') != -1 && photo.url.indexOf('&Expires=') == -1) {
videos.push(photo.fileName);
}
}
});
saveCard.user = user;
saveCard.photos = picNames;
saveCard.livePhotos = livePhotos;
saveCard.videos = videos;
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);
};
var getFavWeiboBackup = function (mid) { // 获取备份
if (!mid) {
toastr.error('未找到mid,代码需要改进');
return;
}
let card = GM_getValue(KEY_FAV_BACKUP_GROUP, {})[String(mid)], photos = [];
if (card) {
card.photos && $.each(card.photos, function(i, fileName) {
let photo = {url: 'https://wx3.sinaimg.cn/large/' + fileName, fileName: fileName, location: 'photos'};
photos.push(photo);
});
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'};
photos.push(photo);
});
card.videos && $.each(card.videos, function(i, fileName) {
let photo = {url: 'http://f.video.weibocdn.com/' + fileName + '?KID=unistore,video', fileName: fileName, location: 'videos'};
photos.push(photo);
});
card.photos = photos;
delete card.livePhotos;
delete card.videos;
if (!card.name) {
card.name = (card.text ? card.text.substr(0, 15) : mid);
}
}
return card;
}
var removeFavWeiboBackup = function (mid) { // 移除备份
if (!mid) {
toastr.error('未找到mid,代码需要改进');
return 400;
}
let fav_backup_group = GM_getValue(KEY_FAV_BACKUP_GROUP, {});
if (fav_backup_group[String(mid)]) {
delete fav_backup_group[String(mid)];
GM_setValue(KEY_FAV_BACKUP_GROUP, fav_backup_group);
toastr.success("删除收藏本地备份成功~");
return 200;
} else {
return 404;
}
};
var showFavWeiboRestoreBtn = function () {
$('.WB_cardwrap:not(.has-set-restore-btn)').find('> .WB_empty, .WB_expand > .WB_empty').find('.WB_innerwrap p')
.prepend('<button class="restore-backup-fav-weibo" style="margin-right:15px;cursor:pointer;" title="made by Jeffrey.Deng">查看备份</button>').closest('.WB_cardwrap:not(.has-set-restore-btn)').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, .WB_cardwrap:not(.has-set-restore-btn) .WB_expand > .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) {
if (!unsafeWindow.confirm('是否将收藏中的链接备份到缓存,以防止博主删除?')) {
return;
}
addFavWeiboBackup($wb_card);
} else {
let mid = findWeiboCardMid($wb_card, true);
if (!mid) {
toastr.error('未找到mid,代码需要改进');
return;
}
removeFavWeiboBackup(mid);
}
});
$('body').on('click', '.WB_cardwrap .WB_screen .layer_menu_list .WB_card_photos_show_fav_weibo_backup', function () {
let $wb_card = $(this).closest(".WB_cardwrap"),
mid = findWeiboCardMid($wb_card, true);
if (!mid) {
toastr.error('未找到mid,代码需要改进');
return;
}
let card = getFavWeiboBackup(mid);
if (card) {
showPhotoLinksPopPanel($wb_card, card.photos, card.name);
} else {
toastr.info('本地备份没有备份该收藏~');
}
});
$('body').on('click', '.WB_cardwrap .WB_screen .layer_menu_list .WB_card_photos_rebuild_fav_weibo_backup', function () {
let $wb_card = $(this).closest(".WB_cardwrap");
addFavWeiboBackup($wb_card);
});
$('body').on('click', '.WB_cardwrap .restore-backup-fav-weibo', function () {
var $self = $(this), $wb_card = $self.closest(".WB_cardwrap"), mid;
mid = findWeiboCardMid($wb_card, true);
if (!mid) {
toastr.error('未找到mid,代码需要改进');
return;
}
if (!$self.hasClass('has-restore')) {
let card = getFavWeiboBackup(mid);
if (card) {
let $pop;
$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>');
$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);
$pop = showPhotoLinksPopPanel($wb_card, card.photos, card.name, false);
$pop.attr('style', 'position:relative;top:0px;right:0px;margin:0 auto;padding:4px 20px 6px;width:75%;z-index:50;').find('.preview').attr('style', 'position:absolute;width:84%;');
$self.text('删除备份').addClass('has-restore').attr('disabled', 'disabled'); // 设置禁用,防止连续点击两下把备份删了
setTimeout(function(){
$self.removeAttr('disabled');
}, 800);
} else {
toastr.info('本地备份没有备份该收藏~');
}
} else {
if (!unsafeWindow.confirm('你确定要删除收藏备份吗?删除后不可恢复')) {
return;
}
removeFavWeiboBackup(mid);
$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);