您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
一个管理下载列表的库
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/541625/1619078/MyDownloaderBox.js
// ==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== class Downloader { constructor() { this.name = ''; this.src = ''; this.aborted = false; this.retryCount = 0; } download(name, src) {} 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) { this.name = name; this.src = src; this.onDownloadBegin(); 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(); if(this.gm) this.gm.abort(); this.download(this.name,this.src); } abort(){ super.abort(); if(this.gm) this.gm.abort(); } } // 使用 iframe 实现的下载器 class IframeDownloader extends Downloader { constructor() { super(); this.iframe = null; this.img = null; this.iframeContainer = $('body'); } download(name, src) { if(!GM_getValue('linsten iframe')) GM_setValue('linsten iframe','yes') this.img = {name:name,src:src}; this.onDownloadBegin(); this.Add_linstenerArgs() .then(()=>{return this.Add_iframe(src,this.iframeContainer)}) .then((f)=>{ this.onDownloadProgress("50%"); }) .then(()=>this.LinstenMyProgress()) .catch(()=>this.onDownloadError()); } retry() { this.abort() this.download(img.name,img.src); } abort() { this.iframe.remove(); this.iframe = null; } Add_iframe(url,container){ const f = $("<iframe></iframe>").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 = $('<a></a>').attr({ href:$('img').attr('src'), download:name }); a[0].click(); } } new IframeDownloader().Add_linstener(); 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 }; // 如果传入了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 = ` <button class="small-download-box">1/10</button> <style> .small-download-box{ border: none; border-radius: 5vmin; font-size: 5vmin; background-color: #fb8500; color: white; position: fixed; top: 0; left: 0; margin: 5vmin; } </style> <div class="downloadBox"> <span class="counter"> <a>总数:</a> <a>已完成:</a> </span> <span class="close">X</span> <form id="downloadOptions"> <div class="radio-option"> <input type="radio" id="gmOption" name="downloadType" value="gm" checked> <label for="gmOption">GM_downloand</label> </div> <div class="radio-option"> <input type="radio" id="iframeOption" name="downloadType" value="iframe"> <label for="iframeOption">iframe</label> </div> </form> <div class="item-container"> </div> </div> <style> .downloadBox{ margin: 5vmin; width: calc(100vmin - 10vmin - 10vmin); height: 80vmin; background-color: #023047; border-radius: 5vmin; padding: 5vmin; position:fixed; top:0; left: 0; } .downloadBox .item-container{ width:100%; max-height: 63vmin; overflow-y:scroll; } .downloadBox .close{ width: 6vmin; height: 6vmin; text-align: center; border-radius: 6vmin; float: right; line-height: 6vmin; color: #4a2f13; background-color: #d44b4b; display: inline-block; } .downloadBox .item-container::-webkit-scrollbar{width: 0px;} .downloadBox .counter{ color: white; } .downloadBox #downloadOptions{ margin: 2vmin 0 5vmin 0; color: #8ecae6; } .downloadBox #downloadOptions>div{ display:inline-block; } .downloadBox .item{ width: auto; height: auto; background-color: #fb8500; border-radius: 2.5vmin; list-style-type: none; margin: 0 0 5vmin 0; display: grid; grid-template-columns: 3fr 1fr 1fr; justify-content: center; align-items: center; grid-gap: 2vmin; --pd:1.5vmin; padding: var(--pd); } .downloadBox .item.end{ filter:grayscale(); } .downloadBox .item.ing{ filter: hue-rotate(65deg); } .downloadBox .item button:first-of-type{ background-color: #fb8500; text-align: left; } .downloadBox .item button{ width: 100%; height: 100%; color: white; font-size: 3.5vmin; border: none; background-color: #ffb703; border-radius: 1.5vmin; padding: 1vmin; } .downloadBox .item button:active{ filter: invert(); } @media(min-width:1080px){ .downloadBox .item button{ font-size : 2.5vmin; } } </style> ` $('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<imgs.length;i++){ this.AddImg(imgs[i]) } } AddImg(img){ const _this = this; const item = this.AddItem(img); item.retry.click(function(){ _this.AddImg(item.img); _this.RemoveItem(item); _this.count--; _this.UpdateCounter(); _this.StartDownload(); }) item.name.click(function(){ for(let i=0;i<_this.maxDownloadCount;i++){ _this.StartDownload(); } }) const downloader = item.downloader; downloader.onDownloadBegin = ()=>{item.obj.addClass("ing")}; downloader.onDownloadProgress = progress=>{ _this.UpdateProgress(item.progress,progress); }; downloader.onDownloadEnd = async ()=>{ _this.end++; item.obj.removeClass("ing"); item.obj.addClass("end"); _this.UpdateCounter(); await _this.AddDownloadingCount(-1); this.StartDownload(); }; img.downloader = downloader; this.count++; this.unDownload.Add(img); this.UpdateCounter(); } async StartDownload(){ if(this.count==this.end){return;} console.log(this.downloadingCount.value,this.maxDownloadCount); //alert(); if(this.downloadingCount.value>=this.maxDownloadCount){ //setTimeout(() => this.StartDownload(), 1000); return; } await this.AddDownloadingCount(1); const img = this.unDownload.Pop();console.log(img,this.unDownload.count) if(img) img.downloader.download(img.name,img.src); } AddItem(img){ const data = this.CreateItem(img); data.name.text(img.name); data.retry.data("data",data); this.box.obj.append(data.obj); return data; } RemoveItem(item){ item.downloader.abort(); item.obj.remove(); } CreateItem(img){ let li = ` <li class="item"> <button class="name">aaa.jpg</button> <button class="progress">0%</button> <button class="retry">Retry</button> </li> ` li = $(li); const downloader = new DownloaderFactory(this.downloadType); const data = { obj:li, name:li.find(".name"), progress:li.find(".progress"), retry:li.find(".retry"), img:img, downloader:downloader }; li.data("data",data); return data; } async AddDownloadingCount(n) { await this.downloadingCount.queue.then(() => { this.downloadingCount.value = this.downloadingCount.value + n; }); } UpdateProgress(obj,progress){ obj.text(progress); } UpdateCounter(){ this.box.counter.sum.text("总数:"+this.count); this.box.counter.end.text("已完成:"+this.end); this.Update_smallBox(this.count,this.end); } }