feat:UI及切换后端为http
This commit is contained in:
1364
backend/package-lock.json
generated
1364
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
||||
"scripts": {
|
||||
"test": "tsc&&node dist/main.js",
|
||||
"rollup": "rollup -c rollup.config.js",
|
||||
"nexe": "nexe -i ./dist/bundle.js --ico Dex.ico --build -t x86-22.13.0 --output ./dist/Dex-v3-core.exe",
|
||||
"nexe": "nexe -i ./dist/bundle.js --build -t x86-22.13.0 --output ./dist/core-x86_64-pc-windows-msvc.exe",
|
||||
"build": "npm run rollup && npm run nexe"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -17,16 +17,27 @@
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.4",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"nexe": "^5.0.0-beta.4",
|
||||
"rollup": "^4.50.1",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"got": "^14.4.8",
|
||||
"inquirer": "^12.9.4",
|
||||
"multer": "^2.0.2",
|
||||
"p-map": "^7.0.3",
|
||||
"p-retry": "^7.0.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"yauzl-promise": "^4.0.0"
|
||||
"ws": "^8.18.3",
|
||||
"yauzl": "^3.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import typescript from '@rollup/plugin-typescript'
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import json from '@rollup/plugin-json';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
/** @type {import('rollup').RollupOptions} */
|
||||
// ---cut---
|
||||
export default {
|
||||
@@ -10,8 +11,10 @@ export default {
|
||||
file: 'dist/bundle.js',
|
||||
format: 'esm'
|
||||
},
|
||||
//external: (id) => /\\.node$/.test(id) || id.includes('crc32'),
|
||||
plugins:[
|
||||
typescript(),
|
||||
nodeResolve(),
|
||||
resolve({preferBuiltins: true}),
|
||||
commonjs(),
|
||||
json()
|
||||
|
||||
@@ -1,16 +1,72 @@
|
||||
import config,{ Config } from "./utils/config.js";
|
||||
import config, { Config } from "./utils/config.js";
|
||||
import fsp from "node:fs/promises";
|
||||
import fs from "node:fs";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { yauzl_promise } from "./utils/yauzl.promise.js";
|
||||
import express from "express";
|
||||
import multer from "multer";
|
||||
import cors from "cors"
|
||||
import websocket, {WebSocketServer} from "ws"
|
||||
import { createServer } from "node:http";
|
||||
const app = express();
|
||||
const upload = multer()
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
const server = createServer(app);
|
||||
const wss = new WebSocketServer({server})
|
||||
const tasks = new Map<number, {status: "peding"|"success",result: any}>()
|
||||
let timespm = 0;
|
||||
let ws:websocket|undefined = undefined;
|
||||
/* 对外API */
|
||||
// Express
|
||||
app.post("/start",upload.single("file"),(req,res)=>{
|
||||
if (!req.file){
|
||||
return;
|
||||
}
|
||||
DeX(req.file.buffer)
|
||||
timespm = Date.now();
|
||||
tasks.set(timespm,{status:"peding",result:undefined});
|
||||
res.json({taskId:timespm})
|
||||
//const buffer = Buffer.from(req.body);
|
||||
//console.log(buffer);
|
||||
})
|
||||
|
||||
const input = process.argv[2];
|
||||
app.get('/getconfig',(req,res)=>{
|
||||
res.json(config)
|
||||
})
|
||||
|
||||
switch (input) {
|
||||
case 'getconfig': //读取配置
|
||||
process.stdout.write(JSON.stringify(config));
|
||||
break;
|
||||
case 'writeconfig': //写入配置
|
||||
if(process.argv.length < 4){
|
||||
process.exit(1);
|
||||
}
|
||||
Config.write_config(JSON.parse(process.argv[3]));
|
||||
break;
|
||||
case 'start':
|
||||
app.post('/writeconfig',(req,res)=>{
|
||||
Config.write_config(req.body)
|
||||
res.json({status:200})
|
||||
})
|
||||
// WebSocket
|
||||
wss.on("connection",(wsx)=>{
|
||||
ws = wsx;
|
||||
})
|
||||
|
||||
server.listen(37019,()=>{
|
||||
console.log("Server is running on http://localhost:37019")
|
||||
})
|
||||
|
||||
async function DeX(buffer: Buffer) {
|
||||
/* 解压Zip */
|
||||
const zip = await yauzl_promise(buffer);
|
||||
for await (const entry of zip) {
|
||||
const ew = entry.fileName.endsWith('/')
|
||||
if (ew){
|
||||
await fsp.mkdir(`./test/${entry.fileName}`,{recursive:true})
|
||||
}
|
||||
if (!ew) {
|
||||
const dirPath = `./test/${entry.fileName.substring(0, entry.fileName.lastIndexOf('/'))}`;
|
||||
await fsp.mkdir(dirPath, { recursive: true });
|
||||
const stream = await entry.openReadStream;
|
||||
const write = fs.createWriteStream(`./test/${entry.fileName}`);
|
||||
await pipeline(stream, write);
|
||||
}
|
||||
if(ws){
|
||||
ws.send(JSON.stringify({status:"unzip",result:entry.fileName}))
|
||||
}
|
||||
}
|
||||
/* 解压完成 */
|
||||
ws?.send(JSON.stringify({status:"changed",result:undefined}))
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
import got from "got";
|
||||
import { join } from "node:path";
|
||||
import { fastdownload, usemirror } from "../utils/utils.js";
|
||||
import { fastdownload, Utils } from "../utils/utils.js";
|
||||
import { modpack_info, XPlatform } from "./index.js";
|
||||
|
||||
const cf_url = (() => {
|
||||
if (usemirror) {
|
||||
return "https://mod.mcimirror.top/curseforge";
|
||||
} else {
|
||||
return "https://api.curseforge.com";
|
||||
}
|
||||
})();
|
||||
export interface CurseForgeManifest {
|
||||
minecraft: {
|
||||
version: string;
|
||||
@@ -19,6 +12,10 @@ export interface CurseForgeManifest {
|
||||
}
|
||||
|
||||
export class CurseForge implements XPlatform {
|
||||
private utils: Utils;
|
||||
constructor() {
|
||||
this.utils = new Utils();
|
||||
}
|
||||
async getinfo(manifest: object): Promise<modpack_info> {
|
||||
let result: modpack_info = Object.create({});
|
||||
const local_manifest = manifest as CurseForgeManifest;
|
||||
@@ -40,7 +37,7 @@ export class CurseForge implements XPlatform {
|
||||
});
|
||||
let tmp: [string, string] | string[][] = [];
|
||||
await got
|
||||
.post(cf_url + "/v1/mods/files", {
|
||||
.post(this.utils.curseforge_url + "/v1/mods/files", {
|
||||
body: FileID,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -57,14 +54,16 @@ export class CurseForge implements XPlatform {
|
||||
return;
|
||||
}
|
||||
const unpath = join(path + "/mods/", e.fileName);
|
||||
if (usemirror) {
|
||||
tmp.push([
|
||||
"https://mod.mcimirror.top" + new URL(e.downloadUrl).pathname,
|
||||
unpath,
|
||||
]);
|
||||
} else {
|
||||
tmp.push([e.downloadUrl, unpath]);
|
||||
}
|
||||
const url = e.downloadUrl.replace("https://edge.forgecdn.net", this.utils.curseforge_Durl)
|
||||
tmp.push([url, unpath])
|
||||
// if (usemirror) {
|
||||
// tmp.push([
|
||||
// "https://mod.mcimirror.top" + new URL(e.downloadUrl).pathname,
|
||||
// unpath,
|
||||
// ]);
|
||||
// } else {
|
||||
// tmp.push([e.downloadUrl, unpath]);
|
||||
// }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import { mr_fastdownload, usemirror } from "../utils/utils.js";
|
||||
import { mr_fastdownload, Utils } from "../utils/utils.js";
|
||||
import { modpack_info, XPlatform } from "./index.js";
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -15,6 +15,10 @@ interface ModrinthManifest {
|
||||
}
|
||||
|
||||
export class Modrinth implements XPlatform {
|
||||
private utils: Utils;
|
||||
constructor() {
|
||||
this.utils = new Utils();
|
||||
}
|
||||
async getinfo(manifest: object): Promise<modpack_info> {
|
||||
let result: modpack_info = Object.create({});
|
||||
const local_manifest = manifest as ModrinthManifest;
|
||||
@@ -37,12 +41,14 @@ export class Modrinth implements XPlatform {
|
||||
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)
|
||||
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)])
|
||||
}
|
||||
tmp.push([e.downloads[0],unpath,String(e.fileSize)])
|
||||
// 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])
|
||||
}
|
||||
|
||||
@@ -1,14 +1,106 @@
|
||||
import pMap from "p-map";
|
||||
import config from "./config.js";
|
||||
import got from "got";
|
||||
import pRetry from "p-retry";
|
||||
import fs from "fs";
|
||||
import fse from "fs-extra";
|
||||
|
||||
export class Utils{
|
||||
public modrinth_url: string;
|
||||
public curseforge_url: string;
|
||||
constructor(){
|
||||
this.modrinth_url = "https://api.modrinth.com"
|
||||
this.curseforge_url = "https://api.curseforge.com"
|
||||
if(config.mirror.mcimirror){
|
||||
this.modrinth_url = "https://mod.mcimirror.top/modrinth"
|
||||
this.curseforge_url = "https://mod.mcimirror.top/curseforge"
|
||||
export class Utils {
|
||||
public modrinth_url: string;
|
||||
public curseforge_url: string;
|
||||
public curseforge_Durl: string;
|
||||
public modrinth_Durl: string;
|
||||
constructor() {
|
||||
this.modrinth_url = "https://api.modrinth.com";
|
||||
this.curseforge_url = "https://api.curseforge.com";
|
||||
this.modrinth_Durl = "https://cdn.modrinth.com";
|
||||
this.curseforge_Durl = "https://media.forgecdn.net";
|
||||
if (config.mirror.mcimirror) {
|
||||
this.modrinth_url = "https://mod.mcimirror.top/modrinth";
|
||||
this.curseforge_url = "https://mod.mcimirror.top/curseforge";
|
||||
this.modrinth_Durl = "https://mod.mcimirror.top";
|
||||
this.curseforge_Durl = "https://mod.mcimirror.top";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function fastdownload(data: [string, string]) {
|
||||
return await pMap(
|
||||
data,
|
||||
async (e) => {
|
||||
try {
|
||||
await pRetry(
|
||||
async () => {
|
||||
if (!fs.existsSync(e[1])) {
|
||||
const size: number = await (async () => {
|
||||
const head = (
|
||||
await got.head(
|
||||
e[0] /*.replace("https://mod.mcimirror.top","https://edge.forgecdn.net")*/,
|
||||
{ headers: { "user-agent": "DeEarthX" } }
|
||||
)
|
||||
).headers["content-length"];
|
||||
if (head) {
|
||||
return Number(head);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
await got
|
||||
.get(e[0], {
|
||||
responseType: "buffer",
|
||||
headers: {
|
||||
"user-agent": "DeEarthX",
|
||||
},
|
||||
})
|
||||
.on("downloadProgress", (progress) => {
|
||||
//bar.update(progress.transferred);
|
||||
})
|
||||
.then((res) => {
|
||||
fse.outputFileSync(e[1], res.rawBody);
|
||||
});
|
||||
}
|
||||
},
|
||||
{ retries: 3 }
|
||||
);
|
||||
} catch (e) {
|
||||
//LOGGER.error({ err: e });
|
||||
}
|
||||
},
|
||||
{ concurrency: 16 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function mr_fastdownload(data: [string, string, string]) {
|
||||
return await pMap(
|
||||
data,
|
||||
async (e) => {
|
||||
//const bar = multibar.create(Number(e[2]), 0, { filename: e[1] });
|
||||
try {
|
||||
await pRetry(
|
||||
async () => {
|
||||
if (!fse.existsSync(e[1])) {
|
||||
await got
|
||||
.get(e[0], {
|
||||
responseType: "buffer",
|
||||
headers: {
|
||||
"user-agent": "DeEarthX",
|
||||
},
|
||||
})
|
||||
.on("downloadProgress", (progress) => {
|
||||
//bar.update(progress.transferred);
|
||||
})
|
||||
.then((res) => {
|
||||
fse.outputFileSync(e[1], res.rawBody);
|
||||
});
|
||||
}
|
||||
},
|
||||
{ retries: 3 }
|
||||
);
|
||||
} catch (e) {
|
||||
//LOGGER.error({ err: e });
|
||||
}
|
||||
},
|
||||
{ concurrency: 16 }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
52
backend/src/utils/yauzl.promise.ts
Normal file
52
backend/src/utils/yauzl.promise.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import yauzl from "yauzl";
|
||||
import Stream from "node:stream"
|
||||
|
||||
export interface IentryP extends yauzl.Entry {
|
||||
openReadStream: Promise<Stream.Readable>;
|
||||
}
|
||||
|
||||
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
|
||||
const zip = await (new Promise((resolve,reject)=>{
|
||||
yauzl.fromBuffer(buffer,/*{lazyEntries:true},*/ (err, zipfile) => {
|
||||
if (err){
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(zipfile);
|
||||
});
|
||||
return;
|
||||
}) as Promise<yauzl.ZipFile>);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const entries: IentryP[]= []
|
||||
zip.on("entry", async (entry: yauzl.Entry) => {
|
||||
const _entry = {
|
||||
...entry,
|
||||
getLastModDate: entry.getLastModDate,
|
||||
isEncrypted: entry.isEncrypted,
|
||||
isCompressed: entry.isCompressed,
|
||||
openReadStream: _openReadStream(zip,entry)
|
||||
}
|
||||
entries.push(_entry)
|
||||
if (zip.entryCount === entries.length){
|
||||
zip.close();
|
||||
resolve(entries);
|
||||
}
|
||||
});
|
||||
zip.on("error",err=>{
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async function _openReadStream(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Stream.Readable>{
|
||||
return new Promise((resolve,reject)=>{
|
||||
zip.openReadStream(entry,(err,stream)=>{
|
||||
if (err){
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(stream);
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user