MyDownloaderBox

一个管理下载列表的库

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/541625/1619078/MyDownloaderBox.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
	}
}