// ==UserScript== // @name 漫播字幕下载 // @namespace manbo.taozhiyu.github.io // @version 0.5 // @description 下载漫播字幕 // @author 涛之雨 // @match https://www.kilamanbo.com/manbo/pc/detail* // @match https://manbo.kilakila.cn/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); }; } }); })();