// ==UserScript==
// @name poipiku图片下载
// @namespace https://github.com/coofo/someScript
// @version 0.2.4
// @license AGPL License
// @description poipiku图片下载的试做,需要key才能看的图片要输入key后才能下载
// @author coofo
// @supportURL https://github.com/coofo/someScript/issues
// @include /^https://poipiku\.com/\d+/\d+\.html/
// @include /^https://poipiku\.com/(\d+)(/?$|/?\?)/
// @include /^https://poipiku.com/IllustListPcV\.jsp\?.*ID=(\d+)/
// @require https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.7.12/sweetalert2.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js
// @require https://greasyfork.org/scripts/442002-coofoutils/code/coofoUtils.js?version=1107527
// @require https://greasyfork.org/scripts/453330-coofoutils-tampermonkeyutils/code/coofoUtils-tampermonkeyUtils.js?version=1106599
// @require https://greasyfork.org/scripts/453329-coofoutils-comicinfo/code/coofoUtils-comicInfo.js?version=1106598
// @connect img.poipiku.com
// @connect img-org.poipiku.com
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function (tools) {
'use strict';
//setting
let setting = tools.setting;
Object.assign(setting, {
def: {
/**
* 文件名格式(包括路径)
* ${userId} 用户ID
* ${userName} 用户名
* ${id} 插图ID
* ${page} 插图序号
* ${page2} 插图序号(2位)
* ${page3} 插图序号(3位)
* ${page4} 插图序号(4位)
*/
imageNameTemplate: "[poipiku]/[${userId}]${userName}/[${id}]-${page_index2}",
/**
* zip文件名格式(包括路径)
*/
zipNameTemplate: "[poipiku][${userId}]${userName}"
},
threadNum: 8,
/**
* 下载模式
* single:将图片文件单个下载(如果需要保存的文件有文件夹结构,则需要将tampermonkey下载模式调整为【浏览器API】)
* zip:将图片打成zip包下载
*/
downloadMode: "zip",
/**
* 下载失败重试次数
* @type {number}
*/
downloadRetryTimes: 2
});
//setting end
//设置按钮
GM_registerMenuCommand("文件名设置", function () {
let html = `图片名格式<br/><input id="imageNameTemplate" style="width: 90%;"><br/>
压缩包名格式<br/><input id="zipNameTemplate" style="width: 90%;"><br/>
<!--<button id="saveTemplate">保存</button><button id="resetTemplate">默认值</button>-->`;
Swal.fire({
title: '命名模板设置',
html: html,
footer: `<div><table border="1">
<tr><td>巨集</td><td>说明</td></tr>
<tr><td>\${userId}</td><td>用户ID</td></tr>
<tr><td>\${userName}</td><td>用户名</td></tr>
<tr><td>\${id}</td><td>插图ID</td></tr>
<tr><td>\${page}</td><td>插图序号</td></tr>
</table><br>
<table border="1">
<tr><td>后缀</td><td>说明(取到值时才生效,取不到则替换为空字符串)</td></tr>
<tr><td>empty</td><td>未取到时填充为空字符串</td></tr>
<tr><td>parenthesis</td><td>圆括号</td></tr>
<tr><td>squareBracket</td><td>方括号</td></tr>
<tr><td>curlyBracket</td><td>大括号</td></tr>
<tr><td>path</td><td>后加文件夹分隔符</td></tr>
<tr><td>index2</td><td>向前添0补全至2位</td></tr>
<tr><td>index3</td><td>向前添0补全至3位(以此类推)</td></tr>
</table></div>`,
confirmButtonText: '保存',
showDenyButton: true,
denyButtonText: `恢复默认`,
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
let templateSetting = {
imageNameTemplate: $('#imageNameTemplate').val(),
zipNameTemplate: $('#zipNameTemplate').val()
};
GM_setValue("templateSetting", templateSetting);
} else if (result.isDenied) {
GM_deleteValue("templateSetting");
}
});
let templateSetting = Object.assign({}, setting.def, GM_getValue("templateSetting", {}));
$('#imageNameTemplate').val(templateSetting.imageNameTemplate);
$('#zipNameTemplate').val(templateSetting.zipNameTemplate);
});
const userName = $("h2.UserInfoUserName a").html();
coofoUtils.commonUtils.assert.hasLength(userName, "未获取到userName");
if (tools.poipiku.utils.isUserPage()) {
//用户页面
$("a.GiftBtn").after('<a class="BtnBase" id="a_download" style="margin-left: 5px;" href="javascript: void(0);">⬇下载</a>');
$("#a_download").click(function () {
if (!tools.poipiku.utils.isLogin()) {
alert("请先登入");
return;
}
if (tools.runtime.nowDownloading) return;
tools.runtime.nowDownloading = true;
Object.assign(setting, setting.def, GM_getValue("templateSetting", {}));
let context = tools.runtime.downloadTask;
Object.assign(context, {
info: {
userName: userName
},
items: []
});
tools.runtime.downloadTask.showMsg("分析页面");
let btn = $("#a_download");
tools.runtime.downloadTask.showMsg = function (msg) {
btn.html(msg);
};
let itemList = $("a.IllustThumbImg");
for (let i = 0; i < itemList.length; i++) {
let detailUrl = $(itemList[i]).attr("href");
let match = detailUrl.match(/\/(\d+)\/(\d+)\.html$/);
if (match === null) continue;
context.info.userId = match[1];
let item = {
parent: context,
info: {
id: match[2]
},
images: []
};
context.items.push(item);
}
download(context);
});
} else if (tools.poipiku.utils.isDetailPage()) {
//详情页面
let span = $('div.IllustItemUser span');
// span.before('<span class="BtnBase UserInfoCmdFollow UserInfoCmdFollow_581115" style="margin-right: 10px;;padding: 0 10px 0 10px;flex: initial;" id="span_download">⬇下载</span>');
span.before('<span class="BtnBase UserInfoCmdFollow UserInfoCmdFollow_581115" style="margin-right: 10px;flex: 0 0 0;padding: 0 13px" id="span_download">⬇下载</span>');
$("#span_download").click(function () {
let getImgUrlFunction = tools.poipiku.api.getOrgImgUrl;
if (!tools.poipiku.utils.isLogin()) {
alert("请先登入");
return;
}
if (tools.runtime.nowDownloading) return;
tools.runtime.nowDownloading = true;
Object.assign(setting, setting.def, GM_getValue("templateSetting", {}));
let context = tools.runtime.downloadTask;
let btn = $("#span_download");
tools.runtime.downloadTask.showMsg = function (msg) {
btn.html(msg);
};
setting.pass = $("input.IllustItemExpandPass").val();
let url = window.location.href;
let match = url.match(tools.poipiku.regex.detailUrl);
Object.assign(context, {
info: {
userName: userName,
userId: match[1]
},
items: []
});
context.items.push({
parent: context,
info: {
id: match[2]
},
images: []
});
download(context);
});
}
let download = async function (context) {
//获取下载地址
let generateTask = coofoUtils.service.task.create();
tools.runtime.downloadTask.generateTask = generateTask;
context.items.forEach(item => {
generateTask.api.addTask(taskItem => tools.poipiku.downloadHelp.generateTask(taskItem, item), setting.downloadRetryTimes);
});
let generateTaskReturn = await generateTask.api.exec(setting.threadNum);
if (generateTaskReturn.retryTimesOutNum > 0) {
Swal.fire({
icon: 'error',
title: '下载出错',
text: '解析地址 ' + generateTaskReturn.completeNum + ' - ' + generateTaskReturn.retryTimesOutNum
});
return;
}
//执行下载操作
let downloadTask = coofoUtils.service.task.create();
tools.runtime.downloadTask.downloadTask = downloadTask;
context.items
.flatMap(item => item.images)
.forEach(image => downloadTask.api.addTask(taskItem => tools.poipiku.downloadHelp.downloadTask(taskItem, image), setting.downloadRetryTimes));
let downloadTaskReturn = await downloadTask.api.exec(setting.threadNum);
if (downloadTaskReturn.retryTimesOutNum > 0) {
Swal.fire({
icon: 'error',
title: '下载出错',
text: '下载 ' + downloadTaskReturn.completeNum + ' - ' + downloadTaskReturn.retryTimesOutNum
});
}
//创建zip
let zipFile = await new Promise(resolve => tools.poipiku.downloadHelp.generateZip(context, zipFile => resolve(zipFile)));
//触发下载
let zipFileName = coofoUtils.commonUtils.format.string.filePathByMap(tools.setting.zipNameTemplate, context.info) + ".zip";
coofoUtils.commonUtils.downloadHelp.toUser.asTagA4Blob(zipFile, zipFileName);
tools.runtime.downloadTask.showFinished(downloadTaskReturn.completeNum, 0);
}
})((function () {
const constants = {};
const cache = {};
const tools = {
setting: {pass: ""},
runtime: {
nowDownloading: false,
downloadTask: {
zip: null,
generateTask: null,
getGeneratedNum: function () {
if (this.generateTask == null) {
return 0;
}
let i = 0;
let list = this.generateTask.runtime.taskList;
for (let j = 0; j < list.length; j++) {
if (list[j].complete === true) {
i++;
}
}
return i;
},
downloadTask: null,
getDownloadedNum: function () {
if (this.downloadTask == null) {
return 0;
}
let i = 0;
let list = this.downloadTask.runtime.taskList;
for (let j = 0; j < list.length; j++) {
if (list[j].complete === true) {
i++;
}
}
return i;
},
showMsg: function (msg) {
console.log(msg);
},
refreshGenerateStatus: function () {
let completeNum = tools.runtime.downloadTask.getGeneratedNum();
let totalNum = tools.runtime.downloadTask.generateTask.runtime.taskList.length;
let digitNum;
if (totalNum > 1000) {
digitNum = 2;
} else if (totalNum > 100) {
digitNum = 1;
} else {
digitNum = 0;
}
let percent = coofoUtils.commonUtils.format.num.toThousands(completeNum / totalNum * 100, null, digitNum) + "%";
tools.runtime.downloadTask.showMsg("解析地址 " + percent);
},
refreshDownLoadStatus: function () {
let completeNum = tools.runtime.downloadTask.getDownloadedNum();
let totalNum = tools.runtime.downloadTask.downloadTask.runtime.taskList.length;
let digitNum;
if (totalNum > 1000) {
digitNum = 2;
} else if (totalNum > 100) {
digitNum = 1;
} else {
digitNum = 0;
}
let percent = coofoUtils.commonUtils.format.num.toThousands(completeNum / totalNum * 100, null, digitNum) + "%";
tools.runtime.downloadTask.showMsg("下载 " + percent);
},
showFinished: function (completeNum, retryTimesOutNum) {
let msg = "下载完成:" + completeNum;
if (retryTimesOutNum > 0) {
msg = msg + " - " + retryTimesOutNum;
}
this.showMsg(msg);
tools.runtime.downloadTask.generateTask = null;
tools.runtime.downloadTask.downloadTask = null;
}
}
},
poipiku: {
regex: {
userUrl: [/^https:\/\/poipiku\.com\/(\d+)(\/?$|\/?\?)/, /https:\/\/poipiku.com\/IllustListPcV\.jsp\?.*ID=(\d+)/],
detailUrl: /^https:\/\/poipiku\.com\/(\d+)\/(\d+)\.html/
},
utils: {
isLogin: function () {
return $("a.LoginButton").length <= 0;
},
isUserPage: function () {
let url = window.location.href;
for (let i = 0; i < tools.poipiku.regex.userUrl.length; i++) {
if (url.match(tools.poipiku.regex.userUrl[i]) != null) {
return true;
}
}
return false;
},
isDetailPage: function () {
let url = window.location.href;
return url.match(tools.poipiku.regex.detailUrl) != null;
},
tryGetImgUrlFromSmallUrl: function (url) {
let match = url.match(/^(.*)_\d+\.[0-9a-zA-Z]+$/);
if (match === null) {
return url;
}
return match[1];
},
tagImgItem: function (userId, id, color) {
if (this.isDetailPage()) {
$("#span_download").css("border", "2px solid " + color);
} else {
$("#a_download").css("border", "2px solid " + color);
let itemList = $("a.IllustThumbImg");
for (let i = 0; i < itemList.length; i++) {
let item = $(itemList[i]);
let match = item.attr("href").match(/\/(\d+)\/(\d+)\.html$/);
// if (match === null || match[1] !== userId || match[2] !== id) continue;
// item.css("border","4px solid red");
if (match !== null && match[1] === userId && match[2] === id) {
item.parent().css("border", "4px solid " + color);
return;
}
}
}
},
tagZeroImgItem: function (userId, id) {
this.tagImgItem(userId, id, "red");
},
tagWarnImgItem: function (userId, id) {
this.tagImgItem(userId, id, "orange");
}
},
api: {
/**
* 如果当前作品第一张图是直接能看的,该接口的返回中不包括第一张图,该接口预定将不再使用
* result_num 返回值统计
* 成功返回 1
* sign in 1
* follower 2
* 需要密码但是密码错误 -2
* 需要关注 -5
* 需要关注 -6
*/
getSmallImgUrl: function (uid, iid, onSuccess, onError, onComplete) {
let data = {
UID: uid,
IID: iid,
PAS: tools.setting.pass,
MD: 0,
TWF: 1
};
$.ajax({
url: "/f/ShowAppendFileF.jsp",
data: data,
type: 'post',
dataType: 'json',
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
success: function (request) {
// console.log(request);
let div = document.createElement("div");
div.innerHTML = request.html;
let imgUrls = [];
let imgs = $(div).find("img");
for (let i = 0; i < imgs.length; i++) {
imgUrls[i] = tools.poipiku.utils.tryGetImgUrlFromSmallUrl($(imgs[i]).attr("src"));
}
if (imgUrls.length <= 0) {
tools.poipiku.utils.tagZeroImgItem(uid, iid);
}
onSuccess(imgUrls);
},
error: onError,
complete: onComplete
});
},
/**
* result 返回值统计
* 成功返回 1
* sign in 1
* 需要密码但是密码错误 -3
* 需要关注 -3
* error_code
* 需要关注 -3
*/
getOrgImgUrl: function (id, td, onSuccess, onError, onComplete) {
let data = {
ID: id,
TD: td,
AD: -1,
PAS: tools.setting.pass
};
$.ajax({
url: "/f/ShowIllustDetailF.jsp",
data: data,
type: 'post',
dataType: 'json',
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
success: function (request) {
if (request.error_code === -3) {
tools.poipiku.utils.tagWarnImgItem(id, td);
tools.poipiku.api.getSmallImgUrl(id, td, onSuccess, onError, onComplete)
} else {
// console.log(request);
let div = document.createElement("div");
div.id = "temp";
document.body.appendChild(div);
div.innerHTML = request.html;
let imgs = $("#temp img");
// console.log(imgs);
let imgUrls = [];
for (let i = 0; i < imgs.length; i++) {
imgUrls[i] = $(imgs[i]).attr("src");
}
if (imgUrls.length <= 0) {
tools.poipiku.utils.tagZeroImgItem(id, td);
}
document.body.removeChild(div);
onSuccess(imgUrls);
}
},
error: onError,
complete: onComplete
});
}
},
downloadHelp: {
generateTask: function (taskItem, item) {
tools.poipiku.api.getOrgImgUrl(item.parent.info.userId, item.info.id, imgUrls => {
for (let i = 0; i < imgUrls.length; i++) {
item.images.push({
parent: item,
info: {
page: i + 1,
suffix: "." + coofoUtils.commonUtils.format.file.getSuffix(imgUrls[i])
},
imgUrl: imgUrls[i],
file: null
});
}
taskItem.success();
tools.runtime.downloadTask.refreshGenerateStatus();
}, () => {
taskItem.failed();
});
},
downloadTask: function (taskItem, image) {
coofoUtils.tampermonkeyUtils.downloadHelp.toBlob.asBlob(image.imgUrl, responseDetails => {
if (responseDetails.status === 200) {
image.file = responseDetails.response;
taskItem.success();
tools.runtime.downloadTask.refreshDownLoadStatus();
} else {
console.error(responseDetails);
setTimeout(() => taskItem.failed(), 500);
}
})
},
generateZip: function (context, onFinished) {
let zip = new JSZip();
context.items
.flatMap(item => item.images)
.forEach(image => {
if (image.file !== null) {
let info = Object.assign({}, image.parent.parent.info, image.parent.info, image.info);
let name = coofoUtils.commonUtils.format.string.filePathByMap(tools.setting.imageNameTemplate, info) + image.info.suffix;
zip.file(name, image.file);
}
});
zip.generateAsync({type: "blob", compression: "STORE"}).then(onFinished);
},
}
}
};
return tools;
})());