Bilibili-Evolved/min/download-video.min.js
2019-05-10 22:44:01 +08:00

1 line
9.6 KiB
JavaScript

(()=>{return(e,t)=>{const{getFriendlyTitle:s}=t.import("title");const n={entity:null,aid:undefined,cid:undefined};let o=null;class r{constructor(){this.menuClasses=["quality","action","progress"];this.currentMenuClass="quality"}get menuPanel(){return document.querySelector(".download-video-panel")}addMenuClass(){this.menuPanel.classList.remove(...this.menuClasses);this.menuPanel.classList.add(this.currentMenuClass);return this.currentMenuClass}resetMenuClass(){[this.currentMenuClass]=this.menuClasses;this.addMenuClass()}nextMenuClass(){const e=this.menuClasses.indexOf(this.currentMenuClass)+1;const t=this.menuClasses[e>=this.menuClasses.length?0:e];this.currentMenuClass=t;this.addMenuClass();return t}addError(){this.menuPanel.classList.add("error")}removeError(){this.menuPanel.classList.remove("error");this.resetMenuClass()}async getUrl(e){if(e){return`https://api.bilibili.com/x/player/playurl?avid=${n.aid}&cid=${n.cid}&qn=${e}&otype=json`}else{return`https://api.bilibili.com/x/player/playurl?avid=${n.aid}&cid=${n.cid}&otype=json`}}}class a extends r{async getUrl(e){if(e){return`https://api.bilibili.com/pgc/player/web/playurl?avid=${n.aid}&cid=${n.cid}&qn=${e}&otype=json`}else{return`https://api.bilibili.com/pgc/player/web/playurl?avid=${n.aid}&cid=${n.cid}&qn=&otype=json`}}}class i{constructor(e,t,s){this.quality=e;this.internalName=t;this.displayName=s}async downloadInfo(){const e=new c(this);await e.fetchVideoInfo();return e}static get availableFormats(){return new Promise((e,t)=>{n.entity.getUrl().then(s=>{const n=new XMLHttpRequest;n.addEventListener("load",()=>{const s=JSON.parse(n.responseText);if(s.code!==0){t("获取清晰度信息失败.")}const o=s.data||s.result||s;const r=o.accept_quality;const a=o.accept_format.split(",");const l=o.accept_description;const c=[];while(r.length>0){const e=new i(r.pop(),a.pop(),l.pop());c.push(e)}e(c)});n.addEventListener("error",()=>t(`获取清晰度信息失败.`));n.withCredentials=true;n.open("GET",s);n.send()})})}}class l{constructor(e,t,s,n){this.length=e;this.size=t;this.url=s;this.backupUrls=n}}class c{constructor(e,t){this.format=e;this.fragments=t||[];this.progress=null;this.totalSize=null;this.workingXhr=null;this.fragmentSplitFactor=6*5}fetchVideoInfo(){return new Promise((e,t)=>{n.entity.getUrl(this.format.quality).then(s=>{const n=new XMLHttpRequest;n.addEventListener("load",()=>{const s=JSON.parse(n.responseText.replace(/http:/g,"https:"));const o=s.data||s.result||s;if(o.quality!==this.format.quality){t("获取下载链接失败, 请确认当前账号有下载权限后重试.")}const r=o.durl;this.fragments=r.map(e=>new l(e.length,e.size,e.url,e.backup_url));e(this.fragments)});n.withCredentials=true;n.open("GET",s);n.send()})})}updateProgress(){const e=this.progressMap?[...this.progressMap.values()].reduce((e,t)=>e+t,0)/this.totalSize:0;if(e>1||e<0){console.error(`[下载视频] 进度异常: ${e}`,this.progressMap.values())}this.progress&&this.progress(e)}cancelDownload(){if("forEach"in this.workingXhr){this.workingXhr.forEach(e=>e.abort())}else{logError("Cancel Download Failed: forEach in this.workingXhr not found.")}}downloadFragment(e){const t=[];this.workingXhr=[];this.progressMap=new Map;this.updateProgress();const s=Math.round(e.size/this.fragmentSplitFactor);let n=0;const o=e=>[...this.progressMap.keys()].indexOf(e)+1;while(n<e.size){const r=Math.min(e.size-1,Math.round(n+s));const a=`bytes=${n}-${r}`;const i=r-n+1;t.push(new Promise((t,s)=>{const n=new XMLHttpRequest;n.open("GET",e.url);n.responseType="arraybuffer";n.withCredentials=false;n.addEventListener("progress",e=>{console.log(`[下载视频] 视频片段${o(n)}下载进度: ${e.loaded}/${i} bytes loaded, ${a}`);this.progressMap.set(n,e.loaded);this.updateProgress()});n.addEventListener("load",()=>{if((""+n.status)[0]==="2"){t(n.response)}else{s(`请求失败.`)}});n.addEventListener("abort",()=>s("下载已取消."));n.addEventListener("error",()=>{console.error(`[下载视频] 视频片段${o(n)}下载失败: ${a}`);this.progressMap.set(n,0);this.updateProgress();n.open("GET",e.url);n.setRequestHeader("Range",a);n.send()});n.setRequestHeader("Range",a);this.progressMap.set(n,0);n.send();this.workingXhr.push(n)}));n=Math.round(n+s)+1}return Promise.all(t)}copyUrl(){const e=this.fragments.map(e=>e.url).reduce((e,t)=>e+"\r\n"+t);GM_setClipboard(e,"text")}exportData(e=false){const t=JSON.stringify({fragments:this.fragments,title:s(true),totalSize:this.fragments.map(e=>e.size).reduce((e,t)=>e+t)});if(e){GM_setClipboard(t,"text")}else{const e=document.createElement("a");const s=new Blob([t],{type:"text/json"});const n=URL.createObjectURL(s);e.setAttribute("href",n);e.setAttribute("download",`cid${unsafeWindow.cid}.json`);document.body.appendChild(e);e.click();e.remove();URL.revokeObjectURL(n)}}extension(e){return(e||this.fragments[0]).url.indexOf(".flv")!==-1?".flv":".mp4"}makeBlob(e,t=null){return new Blob(Array.isArray(e)?e:[e],{type:this.extension(t)===".flv"?"video/x-flv":"video/mp4"})}cleanUpOldBlobUrl(){const e=document.querySelector("a#video-complete").getAttribute("href");if(e&&!document.querySelector(`.link[href="${e}"]`)){URL.revokeObjectURL(e)}[...document.querySelectorAll(".toast-card-header")].filter(e=>e.innerText.includes("下载视频")).forEach(e=>e.querySelector(".toast-card-dismiss").click())}downloadSingle(e){const[t]=e;const n=this.makeBlob(t);const o=s()+this.extension();return[n,o]}async downloadMultiple(e){const t=new JSZip;const n=s();if(e.length>1){e.forEach((e,s)=>{const o=this.fragments[s];t.file(`${n} - ${s+1}${this.extension(o)}`,this.makeBlob(e,o))})}else{const[s]=e;t.file(`${n}${this.extension()}`,this.makeBlob(s))}const o=await t.generateAsync({type:"blob"});const r=n+".zip";return[o,r]}async download(){const e=[];this.totalSize=this.fragments.map(e=>e.size).reduce((e,t)=>e+t);for(const t of this.fragments){const s=await this.downloadFragment(t);e.push(s)}if(e.length<1){throw new Error("下载失败.")}let t=null;let s=null;if(e.length===1){[t,s]=this.downloadSingle(e)}else{[t,s]=await this.downloadMultiple(e)}this.cleanUpOldBlobUrl();const n=URL.createObjectURL(t);this.progress&&this.progress(0);return{url:n,filename:s}}}async function d(){const e=["/www.bilibili.com/bangumi"];if(e.some(e=>document.URL.includes(e))){const{BatchExtractor:e}=await t.importAsync("batchDownload");const s=new e;document.getElementById("download-video").classList.add("batch");document.getElementById("video-action-batch-data").addEventListener("click",async()=>{if(!o){return}n.entity.resetMenuClass();const e=Toast.info("获取链接中...","批量下载");const t=await s.collectData(o,e);if(!t){return}GM_setClipboard(t,{type:"text/json"});Toast.success("已复制批量数据到剪贴板.","复制批量数据",3e3)});document.getElementById("video-action-batch-download-data").addEventListener("click",async()=>{if(!o){return}n.entity.resetMenuClass();const e=Toast.info("获取链接中...","批量下载");const t=await s.collectData(o,e);if(!t){return}const r=document.createElement("a");const a=new Blob([t],{type:"text/json"});const i=URL.createObjectURL(a);r.setAttribute("href",i);r.setAttribute("download",`export.json`);document.body.appendChild(r);r.click();r.remove();URL.revokeObjectURL(i)})}}async function u(){const e=await SpinQuery.select(()=>(unsafeWindow||window).aid);const t=await SpinQuery.select(()=>(unsafeWindow||window).cid);n.aid=e;n.cid=t;if(document.URL.indexOf("bangumi")!==-1){n.entity=new a}else{n.entity=new r}return Boolean(e&&t)}async function p(){let e=await i.availableFormats;o=e[0];const s=async()=>{await u();e=await i.availableFormats;const t=$("ol.video-quality");t.html("");e.forEach(e=>{$(`<li>${e.displayName}</li>`).on("click",()=>{o=e;n.entity.nextMenuClass()}).prependTo(t)})};if(Observer.videoChange){Observer.videoChange(s)}else{Observer.childList("#bofqi",s)}const r=()=>o.downloadInfo().catch(e=>{n.entity.addError();$(".video-error").text(e)});async function a(){if(!o){return}n.entity.nextMenuClass();const e=await r();e.progress=(e=>{$(".download-progress-value").text(`${fixed(e*100)}`);$(".download-progress-foreground").css("transform",`scaleX(${e})`)});document.querySelector(".download-progress-cancel>span").onclick=(()=>e.cancelDownload());const t=await e.download().catch(e=>{n.entity.addError();$(".video-error").text(e)});if(!t){return}const s=document.getElementById("video-complete");s.setAttribute("href",t.url);s.setAttribute("download",t.filename);s.click();const a=`下载完成. <a class="link" href="${t.url}" download="${t.filename.replace(/"/g,"&quot;")}">再次保存</a>`;Toast.success(a,"下载视频");n.entity.resetMenuClass()}async function l(){if(!o){return}const e=await r();e.copyUrl();Toast.success("已复制链接到剪贴板.","复制链接",3e3);n.entity.resetMenuClass()}document.querySelector("#video-action-download").addEventListener("click",a);document.querySelector("#video-action-copy").addEventListener("click",l);document.querySelector("#video-action-copy-data").addEventListener("click",async()=>{if(!o){return}const e=await r();e.exportData(true);Toast.success("已复制数据到剪贴板.","复制数据",3e3);n.entity.resetMenuClass()});document.querySelector("#video-action-download-data").addEventListener("click",async()=>{if(!o){return}const e=await r();e.exportData(false);n.entity.resetMenuClass()});t.applyStyle("downloadVideoStyle");const c=document.querySelector(".download-video-panel");const p=()=>$(".download-video-panel").toggleClass("opened");$("#download-video").on("click",e=>{if(!c.contains(e.target)){p()}});$(".video-error").on("click",()=>{$(".video-error").text("");n.entity.removeError()});await SpinQuery.select(()=>document.querySelector(".download-video-panel"));n.entity.addMenuClass();d()}return{widget:{content:t.data.downloadVideoHtml.text,condition:u,success:p}}}})();