// ==UserScript==
// @name 漫播字幕下载
// @namespace manbo.taozhiyu.github.io
// @version 0.4
// @description 下载漫播字幕
// @author 涛之雨
// @match https://www.kilamanbo.com/manbo/pc/detail*
// @require https://greasyfork.org/scripts/455943-ajaxhooker/code/ajaxHooker.js?version=1124435
// @require https://cdn.jsdelivr.net/npm/@zip.js/zip.js/dist/zip-full.min.js
// @require https://unpkg.com/sweetalert2@11.6.15/dist/sweetalert2.min.js
// @resource swalStyle https://unpkg.com/sweetalert2@11.7.2/dist/sweetalert2.min.css
// @require https://unpkg.com/layui@2.7.6/dist/layui.js
// @resource layuiStyle https://unpkg.com/layui@2.7.6/dist/css/layui.css
// @icon https://img.hongrenshuo.com.cn/h5/websiteManbo-pc-favicon-cb.ico
// @grant GM_getResourceText
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @run-at document-start
// @connect img.kilamanbo.com
// @license WTFPL
// ==/UserScript==
/* global ajaxHooker Swal zip*/
(function() {
'use strict';
let downloading=false;
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
return Promise.all(proms.map(
p=>
p.then(()=> {
d++;
progress_cb( (d * 100) / proms.length );
})
));
}
const downloadFile=(data,N)=>{
const id=btoa((Math.random()+"").slice(2)).substr(0,10);
const dom=document.createElement("a")
document.body.appendChild(Object.assign(dom, {
download: `${N}字幕打包(${new Date().toISOString()}).zip`,
id,
style: "display:none",
href: typeof data==="string"?data:URL.createObjectURL(data),
textContent: "Download zip file",
}));
dom.click();
dom.remove();
downloading=false
}
/**
* 请求文件blob
* op {url,l,method="get",r,p}
*/
const fetchFile=async(op)=>new Promise(function (resolve, reject) {
if(!op.u)reject(Error("链接错误,请联系作者"));
const conf={
method: "get",
url: op.u,
onload(resp){
resolve(resp.response)
},
onerror(resp){
console.error(op.u,"请求失败");
reject(Error("网络请求失败"));
},
responseType:'blob'
}
GM_xmlhttpRequest(conf);
});
const alert= Swal.mixin({
toast:true,
position: 'top',
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer);
toast.addEventListener('mouseleave', Swal.resumeTimer);
},
customClass : {
container: 'disableSelection',
}
});
const startZip=async(lists,name)=>{
const zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"));
alert.fire({
timer: 1000,
title: '正在下载,请稍后。\nF12在控制台中可以查看进度(吧)',
icon: 'info'
})
const bloblists=await Promise.all(lists.map(a=>fetchFile({u:a[1],n:a[0]}))).catch(e=>alert.fire({
title: '文件请求失败',
timer:0,
icon: 'error',
text:"错误信息:"+e.message
}))
if(!bloblists||!bloblists.length) {
alert.fire({
title: '文件请求失败',
icon: 'error',
text:"错误信息:暂无字幕文件"
})
downloading=false
return
}
const CSVBlob=new zip.TextReader("\ufeff文件名,下载链接\n"+lists.map(a=>`${a[0]},${a[1]}`).join("\n")+"\n\n(C)涛之雨 漫播字幕下载器生成\n打包时间:"+new Date().toISOString());
await allProgress([
zipWriter.add("filelist.csv", CSVBlob),
...lists.map((a,b)=>zipWriter.add(a[0]+".lrc", new zip.BlobReader(bloblists[b])))
],
(p) => console.log(`% Done = ${p.toFixed(2)}`)).catch(e=>{
alert.fire({
title: '打包zip发生错误',
icon: 'error',
text:"错误信息:"+e.message
})
downloading=false;
});
downloadFile(await zipWriter.close(),name)
downloading=false;
};
GM_addStyle(GM_getResourceText('swalStyle'))
GM_addStyle(GM_getResourceText('layuiStyle'))
let d=[];
ajaxHooker.hook(request => {
if (request.url.includes('dramaSetDetail')||request.url.includes('dramaDetail')) {
request.response = res => {
const a=JSON.parse(res.responseText);
d=d.concat((a?.data?.radioDramaResp?.setRespList||a?.data?.setRespList)?.map(a=>[a.subTitle||a.setTitle,a.setLrcUrl,a.setIdStr])||[])
const N=a?.data?.radioDramaResp?.title||a?.data?.title
setTimeout(function(){
document.querySelectorAll('.item').forEach(a=>{
a.oncontextmenu=(e)=>{
e.stopPropagation();
e.preventDefault();
const id=a.id?.match(/\d+/)?.[0]
if(!id) return Swal.fire('点击项ID获取失败','ID获取失败,可能为未兼容版本(不应该啊)','error');
console.log(id)
const v=d.find(a=>a[2]===id)
if(!v) return Swal.fire('数据获取失败',d?'【@'+id+'】数据获取失败,可能为未兼容版本(不应该啊)':'全局数据获取失败(不应该啊,要不你重启一下电脑试试?)','error');
console.log(v)
downloading=true;
alert.fire({
showConfirmButton:true,
showDenyButton: true,
confirmButtonText: '复制链接',
denyButtonText: '下载字幕',
denyButtonColor: '#4caf50',
timer: 5000,
title: '请选择下载方式?',
icon: 'question',
}).then(result=>{
if (result.isConfirmed) {
// 复制
GM_setClipboard(v[1]+"#"+v[0],"text");
Swal.fire('复制成功','文件链接已复制到剪贴板,
#号后面为原文件名','success');
} else if (result.isDenied) {
// 打包下载
return startZip([v],N)
}
downloading=false;
});
}
})
document.querySelector('.radio-info .title').oncontextmenu=(e)=>{
e.stopPropagation();
e.preventDefault();
if(d.length===0)return Swal.fire('数据获取失败','全局数据获取失败(不应该啊,要不你重启一下电脑试试?)','error');
if(downloading) return alert.fire({title: '已经存在下载队列',icon: 'error',text:"请等待之前的下载请求完成\n(其实也不是不可以并行,但是并不支持多线程。。。)"});
downloading=true;
alert.fire({
showConfirmButton:true,
showDenyButton: true,
confirmButtonText: '复制全部链接',
denyButtonText: '打包下载',
denyButtonColor: '#4caf50',
timer: 5000,
title: '请选择下载方式?',
icon: 'question',
}).then(result=>{
if (result.isConfirmed) {
// 复制
GM_setClipboard(d.map(a=>a[1]+"#"+a[0]).join("\n"),"text");
Swal.fire('复制成功','选中部分的文件链接已复制到剪贴板,
#号后面为原文件名','success');
} else if (result.isDenied) {
// 打包下载
return startZip(d.filter(a=>a[1]),N)
}
downloading=false;
});
}
},1000);
};
}
});
})();