// ==UserScript== // @name MyDownloaderBox // @version 2025.07.04 // @description 一个管理下载列表的库 // @author You // @grant none // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_download // ==/UserScript== function Proxy_MustAfterFoo(currentFoo, afterFoo, _this) { let afterFooCalled = false; // 拦截指定的 afterFoo 方法以更新状态 const originalAfterFoo = _this[afterFoo]; _this[afterFoo] = function (...args) { afterFooCalled = true; return originalAfterFoo.apply(this, args); }; // 创建 Proxy 来拦截对指定 currentFoo 方法的调用 const handler = { get: (target, prop, receiver) => { if (prop === currentFoo) { return function (...args) { afterFooCalled = false; // 重置状态 const result = target[prop].apply(this, args); // 检查指定的 afterFoo 方法是否被调用 if (!afterFooCalled) { throw new Error(`子类重写的 ${currentFoo} 方法中必须调用 ${afterFoo} 方法`); } return result; }; } return Reflect.get(target, prop, receiver); } }; return new Proxy(_this, handler); } class Downloader { constructor() { this.name = ''; this.src = ''; this.aborted = false; this.retryCount = 0; } download(name,src,foo) { this.name = name; this.src = src; this.onDownloadBegin(); foo(name,src).then(()=>this.onDownloadEnd()); } onDownloadBegin() {} onDownloadProgress(progress) {} onDownloadEnd() {} onDownloadError(error) {} retry() {this.retryCount++} abort() {this.aborted = true;} } // 使用 GM_download 实现的下载器 class GMDownloader extends Downloader { constructor() { super(); this.gm = null } download(name, src) { super.download(name,src,(name,src)=>{ }); this.gm = GM_download({ url:src, name:name, onload:()=>{console.log('end');this.onDownloadEnd()}, onprogress:progress=>{//console.log(progress); if(progress.totalSize > 0) { let p = (progress.loaded / progress.totalSize * 100).toFixed(0); console.log(p); this.onDownloadProgress(p + "%"); } }, onerror:error=>this.onDownloadError(error) }); } retry() { super.retry(); this.gm?.abort(); this.download(this.name,this.src); } abort(){ super.abort(); this.gm?.abort(); } } // 使用 iframe 实现的下载器 class IframeDownloader extends Downloader { constructor() { super(); this.iframe = null; this.iframeContainer = $('body'); } download(name, src) { super.download(name,src); if(!GM_getValue('linsten iframe')) GM_setValue('linsten iframe','yes') this.img = {name:name,src:src}; this.Add_linstenerArgs() .then(()=>{return this.Add_iframe(src,this.iframeContainer)}) .then((f)=>{ this.onDownloadProgress("50%"); }) .then(()=>this.LinstenMyProgress()) .catch(()=>this.onDownloadError()); } retry() { super.retry(); this.abort(); this.download(this.name,this.src); } abort() { super.abort(); this.iframe?.remove(); this.iframe = null; } Add_iframe(url,container){ const f = $("").attr('src',url).css({'width':1,'height':1}); container.append(f); this.iframe = f; return new Promise((rs,rj)=>{ f.on('load', ()=>rs(f)); f.on('error', rj); }) } Add_linstenerArgs(){ GM_setValue('src',this.img.src); GM_setValue('name',this.img.name); return Promise.resolve(); } LinstenMyProgress(){ let end = false; let check = setInterval(()=>{ if(GM_getValue('downloadEnd') && !end){ end = true; this.onDownloadProgress('100%'); this.onDownloadEnd(); this.Del_GM_value(); this.abort(); clearInterval(check) return; } },100); } Del_GM_value(){ GM_deleteValue('src'); GM_deleteValue('name'); GM_deleteValue('downloadEnd'); } Add_linstener(){ //console.log(GM_getValue('linsten iframe')) if(!GM_getValue('linsten iframe')) return; //console.log(GM_getValue('src')) const src = GM_getValue('src'); //console.log(window.location.href == GM_getValue('src')); if(window.location.href!=src) return; this.Download_by_atag(); GM_setValue('downloadEnd','yes') console.log(GM_getValue('downloadEnd')) } Download_by_atag(){ console.log(GM_getValue('name')) const name = GM_getValue('name'); const a = $('').attr({ href:$('img').attr('src'), download:name }); a[0].click(); } } new IframeDownloader().Add_linstener(); class BlobDownloader extends Downloader{ constructor() { super(); this.controller = null; } download(name, src){ super.download(name,src); const _this = this; this.name = name; this.src = src; this.UrlToBlob({url:src}) .then(blob=>{ let a = $('').attr({ download:name, href:blob }) a[0].click(); _this.onDownloadEnd(); }) .catch(er=>{ console.log(er); _this.onDownloadError(); }); } UrlToBlob(args){ const _this = this; return new Promise((resolve,reject)=>{ if(!args.url){reject("no url");} // 创建一个 AbortController 实例 _this.controller = new AbortController(); const signal = _this.controller.signal; fetch(args.url,{signal}) .then(response => { const contentLength = response.headers.get('Content-Length'); const total = parseInt(contentLength, 10); let loaded = 0; // 克隆响应以便分别读取流和获得 Blob const clonedResponse = response.clone(); const reader = clonedResponse.body.getReader(); // 更新进度的函数 function updateProgress({ done, value }) { if (done) { return; // 如果读取完毕,直接返回 } loaded += value.byteLength; // 累加已加载字节 const progress = (loaded / total) * 100; // 计算进度百分比 console.log(`Loading: ${progress.toFixed(0)}%`); _this.onDownloadProgress(progress.toFixed(0)+"%"); // 继续读取下一块数据 return reader.read().then(updateProgress); } // 开始读取流以更新进度 return reader.read().then(updateProgress).then(() => { // 完成后返回原始响应的 Blob return response.blob(); }); }) .then(blob => { _this.controller.abort(); _this.controller = null; const blobUrl = URL.createObjectURL(blob); resolve(blobUrl); }) .catch(error => { console.error('Error caching video:', error); reject(error); }); }); } retry() { super.retry(); this.abort() this.download(this.name,this.src); } abort(){ super.abort(); if(this.controller) this.controller.abort(); this.controller = null; } } class TestDownloader extends Downloader { constructor() { super(); } download(name, src) { const time = Math.random() * 10; // 修正 Math.random() 的使用 this.onDownloadBegin(); let i = 0; // 使用 let 声明变量 i,以便在 setInterval 回调中正确更新其值 let progressInterval = setInterval(() => { i += 100; // 更新 i 的值 const progress = Math.min((i / (time * 1000)) * 100, 100); // 计算进度百分比,确保不超过 100% this.onDownloadProgress(progress.toFixed(0) + '%'); if (i >= time * 1000) { // 判断是否达到或超过预计下载时间 this.onDownloadEnd(); clearInterval(progressInterval); // 清除定时器 } }, 100); } } // 下载器工厂类 class DownloaderFactory { constructor(type) { this.downloaders = { iframe: IframeDownloader, gm: GMDownloader, test: TestDownloader, blob: BlobDownloader }; // 如果传入了type参数,则直接返回对应的下载器实例 if (type !== undefined) { return this.createDownloader(type); } } createDownloader(type) { const DownloaderClass = this.downloaders[type]; if (!DownloaderClass) { throw new Error(`不支持的下载器类型: ${type}`); } return new DownloaderClass(); } } class ImgItem{ constructor({name,src}){ this.name = name; this.src = src; this.downloader = null; } } class ImgNode{ img = null; next = null; } class ImgLine{ hard = new ImgNode(); count = 0; ant = this.hard; Add(img){ this.ant.img = img; this.ant.next = new ImgNode(); this.ant = this.ant.next; this.count++; } Pop(){ if(this.count==0||this.hard==this.ant) return null; const img = this.hard.img; this.hard = this.hard.next; this.count--; return img; } } class DownloadBox{ maxDownloadCount = 3; unDownload = new ImgLine(); downloadingCount = {value:0,lock:false,queue:Promise.resolve()}; downloadType = "gm"; count = 0; end = 0; constructor(){ this.box = this.AddBox(); const _this = this; $('#downloadOptions').on('click', function(event) { const selectedOption = $('input[name="downloadType"]:checked').val(); console.log('Selected download type: ' + selectedOption); _this.downloadType = selectedOption; if(_this.downloadType=="iframe") _this.maxDownloadCount = 1 }); $('.downloadBox').hide() $('.small-download-box').click(function(){ $('.downloadBox').fadeIn() }) $('.downloadBox .close').click(()=>$('.downloadBox').fadeOut()) } Update_smallBox(sum,now){ $('.small-download-box').text(now+"/"+sum) } AddBox(){ let box = `
` $('body').append(box); return { obj:$('.downloadBox .item-container'), counter:{ sum:$('.downloadBox .counter a:first'), end:$('.downloadBox .counter a:last') } } } AddImgs(imgs){ if(!imgs.length){this.AddImg(imgs);return;} for(let i=0;i