From 5f16ee2bfa3d4f9d5b4682e5c9b59171c4979665 Mon Sep 17 00:00:00 2001 From: Tianpao Date: Sun, 28 Sep 2025 00:21:01 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A4=A7=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/Dex.ts | 54 +++++++-- backend/src/core.ts | 21 ++-- backend/src/main.ts | 6 +- backend/src/modloader/fabric.ts | 31 ++++- backend/src/modloader/forge.ts | 26 ++-- backend/src/modloader/index.ts | 25 +++- backend/src/modloader/minecraft.ts | 140 +++++++++++++++------- backend/src/modloader/neoforge.ts | 9 +- backend/src/platform/curseforge.ts | 7 +- backend/src/platform/index.ts | 3 +- backend/src/platform/modrinth.ts | 11 +- backend/src/utils/DeEarth.ts | 10 +- backend/src/utils/utils.ts | 55 +++++++++ backend/src/utils/yauzl.promise.ts | 4 +- front/package-lock.json | 10 ++ front/package.json | 1 + front/src-tauri/2 | 2 +- front/src-tauri/Cargo.lock | 98 ++++++++++++++- front/src-tauri/Cargo.toml | 1 + front/src-tauri/capabilities/default.json | 3 +- front/src-tauri/src/lib.rs | 1 + front/src/component/Main.vue | 74 +++++++++--- 22 files changed, 471 insertions(+), 121 deletions(-) diff --git a/backend/src/Dex.ts b/backend/src/Dex.ts index a98d765..f5452d5 100644 --- a/backend/src/Dex.ts +++ b/backend/src/Dex.ts @@ -1,8 +1,11 @@ import fs from "node:fs"; -import websocket from "ws"; +import websocket, { WebSocketServer } from "ws"; import { yauzl_promise } from "./utils/yauzl.promise.js"; import { pipeline } from "node:stream/promises"; import { platform, what_platform } from "./platform/index.js"; +import { DeEarth } from "./utils/DeEarth.js"; +import { mlsetup } from "./modloader/index.js"; +import { console } from "node:inspector"; interface Iinfo{ name:string @@ -10,23 +13,45 @@ interface Iinfo{ } export class Dex { - ws: websocket; + wsx!: WebSocketServer; in: any; - constructor(ws: websocket) { - this.ws = ws; + ws!: websocket; + constructor(ws: WebSocketServer) { + this.wsx = ws; + this.wsx.on('connection',(e)=>{ + this.ws = e + }) this.in = {} + console.log(this.ws) } public async Main(buffer: Buffer) { + const first = new Date().getTime() const info = await this._getinfo(buffer) const plat = what_platform(info) const mpname = this.in.name + const unpath = `./instance/${mpname}` await Promise.all([ this._unzip(buffer,mpname), - platform(plat).downloadfile(this.in,`./instance/${mpname}`) - ]) - this.ws.send(JSON.stringify({ status: "changed", result: undefined })); //改变状态 - + await platform(plat).downloadfile(this.in,unpath,this.ws) + ]) // 解压和下载 + this.ws.send(JSON.stringify({ + status: "changed", + result: undefined + })); //改变状态 + await new DeEarth(`${unpath}/mods`,`./.rubbish/${mpname}`).Main() + this.ws.send(JSON.stringify({ + status: "changed", + result: undefined + })); //改变状态(DeEarth筛选模组完毕) + const mlinfo = await platform(plat).getinfo(this.in) + await mlsetup(mlinfo.loader,mlinfo.minecraft,mlinfo.loader_version,unpath) //安装服务端 + const latest = new Date().getTime() + console.log(latest - first) + this.ws.send(JSON.stringify({ + status: "finish", + result: latest - first + })) //await this._unzip(buffer); } @@ -34,9 +59,12 @@ export class Dex { const important_name = ["manifest.json","modrinth.index.json"] let contain:string = "" const zip = await yauzl_promise(buffer); - zip.filter(e=>important_name.includes(e.fileName)).forEach(async e=>{ - this.in = JSON.parse((await e.ReadEntry).toString()) + zip.forEach(async e=>{ + if (important_name.includes(e.fileName)){ contain = e.fileName + this.in = JSON.parse((e.ReadEntrySync).toString()) + return; + } }) return contain; } @@ -44,7 +72,7 @@ export class Dex { private async _unzip(buffer: Buffer,instancename:string) { /* 解压Zip */ const zip = await yauzl_promise(buffer); - let index = 0; + let index = 1; for await (const entry of zip) { const ew = entry.fileName.endsWith("/"); if (ew) { @@ -56,10 +84,10 @@ export class Dex { const dirPath = `./instance/${instancename}/${entry.fileName.substring( 0, entry.fileName.lastIndexOf("/") - )}`; + ).replace("overrides/","")}`; await fs.promises.mkdir(dirPath, { recursive: true }); const stream = await entry.openReadStream; - const write = fs.createWriteStream(`./instance/${instancename}/${entry.fileName}`); + const write = fs.createWriteStream(`./instance/${instancename}/${entry.fileName.replace("overrides/","")}`); await pipeline(stream, write); } this.ws.send(JSON.stringify({ status: "unzip", result: { name: entry.fileName,total: zip.length, current:index } })); diff --git a/backend/src/core.ts b/backend/src/core.ts index a331331..ce22f50 100644 --- a/backend/src/core.ts +++ b/backend/src/core.ts @@ -7,25 +7,27 @@ import fs from "node:fs" import { pipeline } from "node:stream/promises"; import { Config, IConfig } from "./utils/config.js"; import { yauzl_promise } from "./utils/yauzl.promise.js"; +import { Dex } from "./Dex.js"; +import { mlsetup } from "./modloader/index.js"; export class Core { private config: IConfig; private readonly app: Application; private readonly server: Server; - public ws!: websocket; + public ws!: WebSocketServer; + private wsx!: websocket; private readonly upload: multer.Multer; private task: {} = {}; + dex: Dex; constructor(config: IConfig) { this.config = config this.app = express(); - this.upload = multer() this.server = createServer(this.app); - new WebSocketServer({ server: this.server }).on("connection", (ws) => { - this.ws = ws; + this.upload = multer() + this.ws = new WebSocketServer({ server: this.server }) + this.ws.on("connection",(e)=>{ + this.wsx = e }) - } - - async DeEarthX(buffer:Buffer){ - + this.dex = new Dex(this.ws) } express() { @@ -35,7 +37,8 @@ export class Core { if (!req.file) { return; } - this.DeEarthX(req.file.buffer) + this.dex.Main(req.file.buffer) //Dex + //this.dex.Main(req.file.buffer) res.json({ status:200, message:"task is peding" diff --git a/backend/src/main.ts b/backend/src/main.ts index e33e082..337c3c2 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,6 +9,7 @@ import { version_compare } from "./utils/utils.js"; import { Forge } from "./modloader/forge.js"; import { Minecraft } from "./modloader/minecraft.js"; import { Fabric } from "./modloader/fabric.js"; +import { NeoForge } from "./modloader/neoforge.js"; const core = new Core(config); @@ -21,7 +22,8 @@ core.start(); // async function Dex(buffer: Buffer) { // } -// new Forge("1.20.1","47.3.10").setup() +//new Forge("1.20.1","47.3.10").setup() +//await new NeoForge("1.21.1","21.1.1").setup() //await new Minecraft("forge","1.20.1","0").setup() // await new Minecraft("forge","1.16.5","0").setup() -await new Fabric("1.20.1","0.17.2").setup() \ No newline at end of file +//await new Fabric("1.20.1","0.17.2").setup() \ No newline at end of file diff --git a/backend/src/modloader/fabric.ts b/backend/src/modloader/fabric.ts index 2bfff0a..5726327 100644 --- a/backend/src/modloader/fabric.ts +++ b/backend/src/modloader/fabric.ts @@ -1,5 +1,6 @@ import got, { Got } from "got"; -import { fastdownload, xfastdownload } from "../utils/utils.js"; +import fs from "node:fs" +import { execPromise, fastdownload, xfastdownload } from "../utils/utils.js"; interface ILatestLoader{ url:string, @@ -16,9 +17,11 @@ export class Fabric{ minecraft: string; loaderVersion: string; got:Got - constructor(minecraft:string,loaderVersion:string) { + path: string; + constructor(minecraft:string,loaderVersion:string,path:string) { this.minecraft = minecraft; this.loaderVersion = loaderVersion; + this.path = path this.got = got.extend({ prefixUrl:"https://bmclapi2.bangbang93.com/", headers:{ @@ -27,16 +30,31 @@ export class Fabric{ }) } - async setup(){ + async setup():Promise{ + await this.getLaestLoader() await this.libraries() + await this.install() + await this.wshell() } + async install(){ + await execPromise(`java -jar fabric-installer.jar server -dir . -mcversion ${this.minecraft} -loader ${this.loaderVersion}`,{ + cwd:this.path + }) + } + + private async wshell(){ + const cmd = `java -jar fabric-server-launch.jar` + await fs.promises.writeFile(`${this.path}/run.bat`,`@echo off\n${cmd}`) //Windows + await fs.promises.writeFile(`${this.path}/run.sh`,`#!/bin/bash\n${cmd}`) //Linux + } + async libraries(){ const res = await this.got.get(`fabric-meta/v2/versions/loader/${this.minecraft}/${this.loaderVersion}/server/json`).json() const _downlist: [string,string][]= [] res.libraries.forEach(e=>{ const path = this.MTP(e.name) - _downlist.push([`https://bmclapi2.bangbang93.com/maven/${path}`,`./fabric/libraries/${path}`]) + _downlist.push([`https://bmclapi2.bangbang93.com/maven/${path}`,`${this.path}/libraries/${path}`]) }) await xfastdownload(_downlist) } @@ -46,11 +64,12 @@ export class Fabric{ const res = await this.got.get("fabric-meta/v2/versions/installer").json() res.forEach(e=>{ if(e.stable){ - downurl = `https://bmclapi2.bangbang93.com/maven/${new URL(e.url).pathname.slice(1)}` + //downurl = `https://bmclapi2.bangbang93.com/maven/${new URL(e.url).pathname.slice(1)}` + downurl = e.url return; } }) - await fastdownload([downurl,`./fabric/fabric-installer.jar`]) + await fastdownload([downurl,`${this.path}/fabric-installer.jar`]) } private MTP(string:string){ diff --git a/backend/src/modloader/forge.ts b/backend/src/modloader/forge.ts index aca30aa..14397b0 100644 --- a/backend/src/modloader/forge.ts +++ b/backend/src/modloader/forge.ts @@ -1,8 +1,9 @@ import got, { Got } from "got"; import fs from "node:fs" import fse from "fs-extra" -import { version_compare, xfastdownload } from "../utils/utils.js"; +import { execPromise, fastdownload, version_compare, xfastdownload } from "../utils/utils.js"; import { yauzl_promise } from "../utils/yauzl.promise.js"; +import { execSync } from "node:child_process"; interface Iforge{ data:{ @@ -27,9 +28,11 @@ export class Forge { minecraft: string; loaderVersion: string; got: Got; - constructor(minecraft:string,loaderVersion:string){ + path: string; + constructor(minecraft:string,loaderVersion:string,path:string){ this.minecraft = minecraft; this.loaderVersion = loaderVersion; + this.path = path this.got = got.extend({ prefixUrl: "https://bmclapi2.bangbang93.com", headers: { "User-Agent": "DeEarthX" }, @@ -39,6 +42,7 @@ export class Forge { async setup(){ await this.installer() await this.library() + await this.install() if (version_compare(this.minecraft,"1.18") === -1){ await this.wshell() } @@ -46,13 +50,13 @@ export class Forge { async library(){ const _downlist: [string,string][]= [] - const data = await fs.promises.readFile(`./forge/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`) + const data = await fs.promises.readFile(`${this.path}/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`) const zip = await yauzl_promise(data) for await(const entry of zip){ if(entry.fileName === "version.json" || entry.fileName === "install_profile.json"){ //Libraries JSON.parse((await entry.ReadEntry).toString()).libraries.forEach(async (e:any)=>{ const t = e.downloads.artifact.path - _downlist.push([`https://bmclapi2.bangbang93.com/maven/${t}`,`./forge/libraries/${t}`]) + _downlist.push([`https://bmclapi2.bangbang93.com/maven/${t}`,`${this.path}/libraries/${t}`]) }) } if(entry.fileName === "install_profile.json"){ //MOJMAPS @@ -61,13 +65,13 @@ export class Forge { const vjson = await this.got.get(`version/${this.minecraft}/json`).json() console.log(`${new URL(vjson.downloads.server_mappings.url).pathname}`) const mojpath = this.MTP(json.data.MOJMAPS.server) - _downlist.push([`https://bmclapi2.bangbang93.com/${new URL(vjson.downloads.server_mappings.url).pathname.slice(1)}`,`./forge/libraries/${mojpath}`]) + _downlist.push([`https://bmclapi2.bangbang93.com/${new URL(vjson.downloads.server_mappings.url).pathname.slice(1)}`,`${this.path}/libraries/${mojpath}`]) /* 获取MOJMAPS */ /*获取MAPPING*/ const mappingobj = json.data.MAPPINGS.server const path = this.MTP(mappingobj.replace(":mappings@txt","@zip")) - _downlist.push([`https://bmclapi2.bangbang93.com/maven/${path}`,`./forge/libraries/${path}`]) + _downlist.push([`https://bmclapi2.bangbang93.com/maven/${path}`,`${this.path}/libraries/${path}`]) /* 获取MAPPING */ } } @@ -75,15 +79,19 @@ export class Forge { await xfastdownload(downlist) } + async install(){ + await execPromise(`java -jar forge-${this.minecraft}-${this.loaderVersion}-installer.jar --installServer`,{cwd:this.path}) + } + async installer(){ const res = (await this.got.get(`forge/download?mcversion=${this.minecraft}&version=${this.loaderVersion}&category=installer&format=jar`)).rawBody; - await fse.outputFile(`./forge/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`,res); + await fse.outputFile(`${this.path}/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`,res); } private async wshell(){ const cmd = `java -jar forge-${this.minecraft}-${this.loaderVersion}.jar` - await fs.promises.writeFile("./forge/run.bat",`@echo off\n${cmd}`) //Windows - await fs.promises.writeFile("./forge/run.sh",`#!/bin/bash\n${cmd}`) //Linux + await fs.promises.writeFile(`${this.path}/run.bat`,`@echo off\n${cmd}`) //Windows + await fs.promises.writeFile(`${this.path}/run.sh`,`#!/bin/bash\n${cmd}`) //Linux } private MTP(string:string){ diff --git a/backend/src/modloader/index.ts b/backend/src/modloader/index.ts index 6f81649..43c18a6 100644 --- a/backend/src/modloader/index.ts +++ b/backend/src/modloader/index.ts @@ -1,15 +1,36 @@ +import { Fabric } from "./fabric.js"; +import { Forge } from "./forge.js"; +import { Minecraft } from "./minecraft.js"; +import { NeoForge } from "./neoforge.js"; + interface XModloader { - setup: Promise + setup(): Promise } -export function modloader(ml:string,mcv:string,mlv:string){ +export function modloader(ml:string,mcv:string,mlv:string,path:string){ + let modloader:XModloader switch (ml) { case "fabric": + modloader = new Fabric(mcv,mlv,path) + break; + case "fabric-loader": + modloader = new Fabric(mcv,mlv,path) break; case "forge": + modloader = new Forge(mcv,mlv,path) break; case "neoforge": + modloader = new NeoForge(mcv,mlv,path) break; default: + modloader = new Minecraft(ml,mcv,mlv,path) break; } + return modloader +} + +export async function mlsetup(ml:string,mcv:string,mlv:string,path:string){ + const minecraft = new Minecraft(ml,mcv,mlv,path); + console.log(ml) + await modloader(ml,mcv,mlv,path).setup() + await minecraft.setup() } \ No newline at end of file diff --git a/backend/src/modloader/minecraft.ts b/backend/src/modloader/minecraft.ts index 5a5afcf..1b89f98 100644 --- a/backend/src/modloader/minecraft.ts +++ b/backend/src/modloader/minecraft.ts @@ -1,72 +1,120 @@ import fs from "node:fs" +import pa from "node:path" import { fastdownload, version_compare } from "../utils/utils.js"; import { yauzl_promise } from "../utils/yauzl.promise.js"; import { pipeline } from "node:stream/promises"; import got from "got"; +import fsExtra from "fs-extra/esm"; -interface ILInfo{ - libraries:{ - downloads:{ - artifact:{ - path:string +interface ILInfo { + libraries: { + downloads: { + artifact: { + path: string } } }[] } -export class Minecraft{ +export class Minecraft { loader: string; minecraft: string; loaderVersion: string; - constructor(loader:string,minecraft:string,lv:string){ + path: string; + constructor(loader: string, minecraft: string, lv: string,path:string) { + this.path = path this.loader = loader; this.minecraft = minecraft; this.loaderVersion = lv; } - async setup(){ - switch (this.loader){ - case "forge": - await this.forge_setup(); - break; - } + async setup() { + switch (this.loader) { + case "forge": + await this.forge_setup(); + break; + case "neoforge": + await this.forge_setup(); + break; + case "fabric": + await this.fabric_setup(); + break; + case "fabric-loader": + await this.fabric_setup(); + break; + } + await this.eula() //生成Eula.txt } - async forge_setup(){ - if(version_compare(this.minecraft,"1.18") === 1){ - // 1.18.x + MC依赖解压 - const mcpath = `./forge/libraries/net/minecraft/server/${this.minecraft}/server-${this.minecraft}.jar` - await fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`,mcpath]) - const zip = await yauzl_promise(await fs.promises.readFile(mcpath)) - for await(const entry of zip){ - if(entry.fileName.startsWith("META-INF/libraries/")&&!entry.fileName.endsWith("/")){ - console.log(entry.fileName) - const stream = await entry.openReadStream; - const write = fs.createWriteStream(`./forge/libraries/${entry.fileName.replace("META-INF/libraries/","")}`); - await pipeline(stream, write); - } - } - // 1.18.x + 依赖解压 - }else{ - //1.18.x - MC依赖下载 - const lowv = `./forge/minecraft_server.${this.minecraft}.jar` - const dmc = fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`,lowv]) - const download:Promise = new Promise(async (resolve)=>{ - console.log("并行") - const json = await got.get(`https://bmclapi2.bangbang93.com/version/${this.minecraft}/json`,{ - headers:{ - "User-Agent": "DeEarthX" + async forge_setup() { + if (version_compare(this.minecraft, "1.18") === 1) { + // 1.18.x + MC依赖解压 + const mcpath = `${this.path}/libraries/net/minecraft/server/${this.minecraft}/server-${this.minecraft}.jar` + await fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`, mcpath]) + const zip = await yauzl_promise(await fs.promises.readFile(mcpath)) + for await (const entry of zip) { + if (entry.fileName.startsWith("META-INF/libraries/") && !entry.fileName.endsWith("/")) { + console.log(entry.fileName) + const stream = await entry.openReadStream; + const write = fs.createWriteStream(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`); + await pipeline(stream, write); } + } + // 1.18.x + 依赖解压 + } else { + //1.18.x - MC依赖下载 + const lowv = `${this.path}/minecraft_server.${this.minecraft}.jar` + const dmc = fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`, lowv]) + const download: Promise = new Promise(async (resolve) => { + console.log("并行") + const json = await got.get(`https://bmclapi2.bangbang93.com/version/${this.minecraft}/json`, { + headers: { + "User-Agent": "DeEarthX" + } + }) + .json() + json.libraries.forEach(async e => { + const path = e.downloads.artifact.path + await fastdownload([`https://bmclapi2.bangbang93.com/maven/${path}`, `${this.path}/libraries/${path}`]) + }) + resolve() }) - .json() - json.libraries.forEach(async e=>{ - const path = e.downloads.artifact.path - await fastdownload([`https://bmclapi2.bangbang93.com/maven/${path}`,`./forge/libraries/${path}`]) - }) - resolve() - }) - await Promise.all([dmc,download]) - //1.18.x - 依赖下载 + await Promise.all([dmc, download]) + //1.18.x - 依赖下载 + } } + + async fabric_setup() { + const mcpath = `${this.path}/server.jar` + await fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`, mcpath]) + // 依赖解压 + const zip = await yauzl_promise(await fs.promises.readFile(mcpath)) + for await (const entry of zip) { + // if (entry.fileName.startsWith("META-INF/libraries/") && entry.fileName.endsWith("/") &&entry.fileName !== "META-INF/libraries/") { + // fs.promises.mkdir(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`,{ + // recursive:true + // }) + // } + if (entry.fileName.startsWith("META-INF/libraries/") && !entry.fileName.endsWith("/")) { + // const stream = await entry.openReadStream; + // fs.promises.mkdir(pa.dirname(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`),{ + // recursive:true + // }) + // const write = fs.createWriteStream(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`); + // await pipeline(stream, write); + const out = entry.ReadEntrySync + await fsExtra.outputFile(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`,out) + } + } + // 依赖解压 + } + + async eula(){ + const context = ` + #By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA). + #Spawn by DeEarthX(QQgroup:559349662) Tianpao:(https://space.bilibili.com/1728953419) + eula=true + ` + await fs.promises.writeFile(`${this.path}/eula.txt`,context) } } \ No newline at end of file diff --git a/backend/src/modloader/neoforge.ts b/backend/src/modloader/neoforge.ts index ee33f53..c5551b0 100644 --- a/backend/src/modloader/neoforge.ts +++ b/backend/src/modloader/neoforge.ts @@ -1,18 +1,19 @@ import fse from "fs-extra" import { Forge } from "./forge.js"; -class NeoForge extends Forge{ -constructor(minecraft:string,loaderVersion:string){ - super(minecraft,loaderVersion); //子承父业 +export class NeoForge extends Forge{ +constructor(minecraft:string,loaderVersion:string,path:string){ + super(minecraft,loaderVersion,path); //子承父业 } async setup(){ await this.installer(); await this.library(); + await this.install(); } async installer(){ const res = (await this.got.get(`neoforge/version/${this.loaderVersion}/download/installer.jar`)).rawBody; - await fse.outputFile(`./forge/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`,res); + await fse.outputFile(`${this.path}/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`,res); } } \ No newline at end of file diff --git a/backend/src/platform/curseforge.ts b/backend/src/platform/curseforge.ts index 8d2b689..ef2ac12 100644 --- a/backend/src/platform/curseforge.ts +++ b/backend/src/platform/curseforge.ts @@ -1,6 +1,7 @@ import got from "got"; +import { WebSocket } from "ws"; import { join } from "node:path"; -import { fastdownload, Utils } from "../utils/utils.js"; +import { Wfastdownload, Utils } from "../utils/utils.js"; import { modpack_info, XPlatform } from "./index.js"; export interface CurseForgeManifest { @@ -28,7 +29,7 @@ export class CurseForge implements XPlatform { return result; } - async downloadfile(manifest: object, path: string): Promise { + async downloadfile(manifest: object, path: string, ws:WebSocket): Promise { const local_manifest = manifest as CurseForgeManifest; if (local_manifest.files.length === 0){ return; @@ -70,6 +71,6 @@ export class CurseForge implements XPlatform { } ); }); - await fastdownload(tmp as unknown as [string, string]); //下载文件 + await Wfastdownload(tmp as unknown as [string, string],ws); //下载文件 } } diff --git a/backend/src/platform/index.ts b/backend/src/platform/index.ts index fb3a5dd..e7a68b2 100644 --- a/backend/src/platform/index.ts +++ b/backend/src/platform/index.ts @@ -1,9 +1,10 @@ import { CurseForge } from "./curseforge.js"; import { Modrinth } from "./modrinth.js"; +import { WebSocket } from "ws"; export interface XPlatform { getinfo(manifest: object): Promise; - downloadfile(manifest: object,path:string): Promise; + downloadfile(manifest: object,path:string,ws:WebSocket): Promise; } export interface modpack_info { diff --git a/backend/src/platform/modrinth.ts b/backend/src/platform/modrinth.ts index 3212638..45da771 100644 --- a/backend/src/platform/modrinth.ts +++ b/backend/src/platform/modrinth.ts @@ -1,5 +1,6 @@ import fs from "node:fs"; -import { mr_fastdownload, Utils } from "../utils/utils.js"; +import { WebSocket } from "ws"; +import { Wfastdownload, Utils } from "../utils/utils.js"; import { modpack_info, XPlatform } from "./index.js"; import { join } from "node:path"; @@ -34,22 +35,22 @@ export class Modrinth implements XPlatform { } return result; } - async downloadfile(manifest: object,path:string): Promise { + async downloadfile(manifest: object,path:string,ws:WebSocket): Promise { const index = manifest as ModrinthManifest; - let tmp: [string, string, string][] = [] + let tmp: [string, string][] = [] index.files.forEach(async (e: { path: string; downloads: string[]; fileSize: number;}) => { if (e.path.endsWith(".zip")) { return; } const url = e.downloads[0].replace("https://cdn.modrinth.com",this.utils.modrinth_Durl) const unpath = join(path,e.path) - tmp.push([e.downloads[0],unpath,String(e.fileSize)]) + tmp.push([e.downloads[0],unpath]) // if (usemirror){ // tmp.push(["https://mod.mcimirror.top"+new URL(e.downloads[0]).pathname,unpath,String(e.fileSize)]) // }else{ // tmp.push([e.downloads[0],unpath,String(e.fileSize)]) // } }); - await mr_fastdownload(tmp as unknown as [string, string, string]) + await Wfastdownload(tmp as unknown as [string, string],ws) } } diff --git a/backend/src/utils/DeEarth.ts b/backend/src/utils/DeEarth.ts index b6ca50b..e86f300 100644 --- a/backend/src/utils/DeEarth.ts +++ b/backend/src/utils/DeEarth.ts @@ -3,6 +3,8 @@ import crypto from "node:crypto" import { yauzl_promise } from "./yauzl.promise.js" import got from "got" import { Utils } from "./utils.js" +import pa from "node:path" +import WebSocket from "ws" interface IMixins{ name: string data: string @@ -29,9 +31,9 @@ export class DeEarth{ modspath: string file: IFile[] utils: Utils - constructor(modspath:string) { + constructor(modspath:string,movepath:string) { this.utils = new Utils(); - this.movepath = "./.rubbish" + this.movepath = movepath this.modspath = modspath this.file = [] } @@ -44,8 +46,10 @@ export class DeEarth{ const hash = await this.Check_Hashes() const mixins = await this.Check_Mixins() const result = [...new Set(hash.concat(mixins))] + console.log(result) result.forEach(async e=>{ - await fs.promises.rename(`${this.modspath}/${e}`,`${this.movepath}/${e}`) + await fs.promises.rename(`${e}`,`${this.movepath}/${e}`.replace(this.modspath,"")) + //await fs.promises.rename(`${this.modspath}/${e}`,`${this.movepath}/${e}`) }) } diff --git a/backend/src/utils/utils.ts b/backend/src/utils/utils.ts index 111d35a..af7cf32 100644 --- a/backend/src/utils/utils.ts +++ b/backend/src/utils/utils.ts @@ -4,6 +4,8 @@ import got from "got"; import pRetry from "p-retry"; import fs from "node:fs"; import fse from "fs-extra"; +import { WebSocket } from "ws"; +import { ExecOptions, exec} from "node:child_process"; export class Utils { public modrinth_url: string; @@ -47,6 +49,19 @@ export function version_compare(v1: string, v2: string) { return 0; } +export function execPromise(cmd:string,options:ExecOptions){ + return new Promise((resolve,reject)=>{ + exec(cmd,options,(err,stdout,stderr)=>{ + if(err){ + reject(err) + return; + } + }).on('exit',(code)=>{ + resolve(code) + }) + }) +} + export async function fastdownload(data: [string, string]) { return await pMap( [data], @@ -95,6 +110,46 @@ export async function fastdownload(data: [string, string]) { ); } +export async function Wfastdownload(data: [string, string],ws:WebSocket) { + let index = 1; + return await pMap( + data, + async (e:any) => { + try { + await pRetry( + async () => { + if (!fs.existsSync(e[1])) { + await got + .get(e[0], { + responseType: "buffer", + headers: { + "user-agent": "DeEarthX", + }, + }) + .then((res) => { + fse.outputFileSync(e[1], res.rawBody); + }); + } + ws.send(JSON.stringify({ + status:"downloading", + result:{ + total:data.length, + index:index, + name:e[1] + } + })) + index++ + }, + { retries: 3 } + ); + } catch (e) { + //LOGGER.error({ err: e }); + } + }, + { concurrency: 16 } + ); +} + export async function xfastdownload(data: [string, string][]) { return await pMap( data, diff --git a/backend/src/utils/yauzl.promise.ts b/backend/src/utils/yauzl.promise.ts index d36cd30..1b876e7 100644 --- a/backend/src/utils/yauzl.promise.ts +++ b/backend/src/utils/yauzl.promise.ts @@ -4,6 +4,7 @@ import Stream from "node:stream" export interface IentryP extends yauzl.Entry { openReadStream: Promise; ReadEntry: Promise; + ReadEntrySync: Buffer; } export async function yauzl_promise(buffer: Buffer): Promise{ @@ -27,7 +28,8 @@ export async function yauzl_promise(buffer: Buffer): Promise{ isEncrypted: entry.isEncrypted, isCompressed: entry.isCompressed, openReadStream: _openReadStream(zip,entry), - ReadEntry: _ReadEntry(zip,entry) + ReadEntry: _ReadEntry(zip,entry), + ReadEntrySync: (await _ReadEntry(zip,entry)) } entries.push(_entry) if (zip.entryCount === entries.length){ diff --git a/front/package-lock.json b/front/package-lock.json index f58c709..97547b4 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -11,6 +11,7 @@ "@ant-design/icons-vue": "^7.0.1", "@tailwindcss/vite": "^4.1.13", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-notification": "^2.3.1", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-store": "^2.4.0", "ant-design-vue": "^4.2.6", @@ -1376,6 +1377,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-notification": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-notification/-/plugin-notification-2.3.1.tgz", + "integrity": "sha512-7gqgfANSREKhh35fY1L4j3TUjUdePmU735FYDqRGeIf8nMXWpcx6j4FhN9/4nYz+m0mv79DCTPLqIPTySggGgg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-opener": { "version": "2.5.0", "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-opener/-/plugin-opener-2.5.0.tgz", diff --git a/front/package.json b/front/package.json index 8c3497f..b4afd33 100644 --- a/front/package.json +++ b/front/package.json @@ -15,6 +15,7 @@ "@ant-design/icons-vue": "^7.0.1", "@tailwindcss/vite": "^4.1.13", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-notification": "^2.3.1", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-store": "^2.4.0", "ant-design-vue": "^4.2.6", diff --git a/front/src-tauri/2 b/front/src-tauri/2 index 8670cb9..7c81b23 100644 --- a/front/src-tauri/2 +++ b/front/src-tauri/2 @@ -1,5 +1,5 @@ -up to date in 1s +added 1 package in 2s 16 packages are looking for funding run `npm fund` for details diff --git a/front/src-tauri/Cargo.lock b/front/src-tauri/Cargo.lock index 80eb182..1107bdd 100644 --- a/front/src-tauri/Cargo.lock +++ b/front/src-tauri/Cargo.lock @@ -702,6 +702,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-notification", "tauri-plugin-opener", "tauri-plugin-shell", "tauri-plugin-store", @@ -1964,6 +1965,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-notification-sys" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" +dependencies = [ + "cc", + "objc2 0.6.2", + "objc2-foundation 0.3.1", + "time", +] + [[package]] name = "markup5ever" version = "0.14.1" @@ -2113,6 +2126,20 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "notify-rust" +version = "4.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2677,7 +2704,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", "indexmap 2.11.1", - "quick-xml", + "quick-xml 0.38.3", "serde", "time", ] @@ -2806,6 +2833,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.38.3" @@ -2855,6 +2891,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2875,6 +2921,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2893,6 +2949,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3757,6 +3822,25 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-notification" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fbc86b929b5376ab84b25c060f966d146b2fbd59b6af8264027b343c82c219" +dependencies = [ + "log", + "notify-rust", + "rand 0.9.2", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.16", + "time", + "url", +] + [[package]] name = "tauri-plugin-opener" version = "2.5.0" @@ -3916,6 +4000,18 @@ dependencies = [ "toml 0.9.5", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" +dependencies = [ + "quick-xml 0.37.5", + "thiserror 2.0.16", + "windows", + "windows-version", +] + [[package]] name = "tempfile" version = "3.22.0" diff --git a/front/src-tauri/Cargo.toml b/front/src-tauri/Cargo.toml index a930771..a79d188 100644 --- a/front/src-tauri/Cargo.toml +++ b/front/src-tauri/Cargo.toml @@ -25,4 +25,5 @@ serde_json = "1" open = "5.3.2" tauri-plugin-store = "2" tauri-plugin-shell = "2" +tauri-plugin-notification = "2" diff --git a/front/src-tauri/capabilities/default.json b/front/src-tauri/capabilities/default.json index 4b460c0..815d775 100644 --- a/front/src-tauri/capabilities/default.json +++ b/front/src-tauri/capabilities/default.json @@ -22,6 +22,7 @@ "args": true } ] - } + }, + "notification:default" ] } \ No newline at end of file diff --git a/front/src-tauri/src/lib.rs b/front/src-tauri/src/lib.rs index b182a21..84ee6f6 100644 --- a/front/src-tauri/src/lib.rs +++ b/front/src-tauri/src/lib.rs @@ -15,6 +15,7 @@ fn open_url(url: &str) { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_opener::init()) diff --git a/front/src/component/Main.vue b/front/src/component/Main.vue index 5bf0372..4e024a3 100644 --- a/front/src/component/Main.vue +++ b/front/src/component/Main.vue @@ -3,9 +3,14 @@ import { nextTick, ref, VNodeRef } from 'vue'; import { InboxOutlined } from '@ant-design/icons-vue'; import { message, StepsProps } from 'ant-design-vue'; import type { UploadFile, UploadChangeParam, Upload } from 'ant-design-vue'; +import { + isPermissionGranted, + requestPermission, + sendNotification, +} from '@tauri-apps/plugin-notification'; interface IWSM { - status: "unzip"|"pending"|"changed", - result: string + status: "unzip"|"finish"|"changed"|"downloading", + result: any } /* 进度显示区 */ const disp_steps = ref(true); @@ -64,30 +69,61 @@ function reactFL() { /* 获取文件区 */ //shell.Command.create('core',['start']).spawn() function runDeEarthX(data: Blob) { - console.log(data) + //console.log(data) const fd = new FormData(); fd.append('file', data); console.log(fd.getAll('file')) fetch('http://localhost:37019/start',{ method:'POST', body:fd - }).then(async res=>res.json()).then(res=>{ - prews(res) + }).then(async res=>res.json()).then(()=>{ + prews() }) // shell.Command.create('core',['start',new BigUint64Array(data).toString()]).stdout.on('data',(data)=>{ // console.log(data) // }) reactFL() } - -function prews(res: object){ +const prog = ref({status:"active",percent:0,display:true}) +const dprog = ref({status:"active",percent:0,display:true}) +function prews(){ const ws = new WebSocket('ws://localhost:37019/') + // ws.addEventListener('message',(wsm)=>{ + // const _data = JSON.parse(wsm.data) as IWSM + // if (_data.status === "changed") { + // setyps_current.value ++; + // } + // logs.value.push({message:_data.result}) + // }) ws.addEventListener('message',(wsm)=>{ - const _data = JSON.parse(wsm.data) as IWSM - if (_data.status === "changed") { + const _data = JSON.parse(wsm.data) as IWSM + //console.log(_data) + if (_data.status === "changed") { //状态更改 setyps_current.value ++; } - logs.value.push({message:_data.result}) + if (_data.status === "unzip"){ //解压ZIP + prog.value.percent = Math.round(((_data.result.current / _data.result.total) * 100)) + if (_data.result.current === _data.result.total){ + prog.value.status = "succees" + setTimeout(()=>{ + prog.value.display = false; + },2000) + } + } + if (_data.status === "downloading"){ //下载文件 + dprog.value.percent = Math.round((_data.result.index / _data.result.total) * 100) + if(dprog.value.percent === 100){ + dprog.value.status = "succees" + setTimeout(()=>{ + dprog.value.display = false; + },2000) + } + } + if (_data.status === "finish"){ + const time = Math.round(_data.result / 1000 / 1000) + setyps_current.value ++; + sendNotification({ title: 'DeEarthX V3', body: `服务端制作完成!共用时${time}秒!` }); + } }) } @@ -127,10 +163,20 @@ logContainer.value.scrollTop = logContainer.value.scrollHeight; class="tw:fixed tw:bottom-2 tw:ml-4 tw:w-272 tw:h-16 tw:flex tw:justify-center tw:items-center tw:text-sm"> -
-
- {{ log.message }} -
+ +
+ +
+

解压进度

+ +
+
+

下载进度

+ +
+