このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/541625/1619078/MyDownloaderBox.js
このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。
このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。
このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。
このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。
このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。
このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。
(ユーザースタイル管理ツールは設定済みなのでインストール!)
// ==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);
}
}