chore:AI大修改

This commit is contained in:
Tianpao
2025-12-27 14:54:04 +08:00
parent 03ed0a4cb7
commit 419c40c794
7 changed files with 500 additions and 260 deletions

View File

@@ -9,7 +9,7 @@ import { dinstall, mlsetup } from "./modloader/index.js";
import config from "./utils/config.js"; import config from "./utils/config.js";
import { execPromise } from "./utils/utils.js"; import { execPromise } from "./utils/utils.js";
import { MessageWS } from "./utils/ws.js"; import { MessageWS } from "./utils/ws.js";
import { debug } from "./utils/logger.js"; import { logger } from "./utils/logger.js";
export class Dex { export class Dex {
wsx!: WebSocketServer; wsx!: WebSocketServer;
@@ -24,136 +24,118 @@ export class Dex {
public async Main(buffer: Buffer, dser: boolean) { public async Main(buffer: Buffer, dser: boolean) {
try { try {
const first = Date.now(); const first = Date.now();
const { contain, info } = await this._getinfo(buffer).catch((e) => { const { contain, info } = await this._getinfo(buffer);
throw new Error(e);
});
const plat = what_platform(contain); const plat = what_platform(contain);
debug(plat); logger.debug("Platform detected", plat);
debug(info); logger.debug("Modpack info", info);
const mpname = info.name; const mpname = info.name;
const unpath = `./instance/${mpname}`; const unpath = `./instance/${mpname}`;
// 解压和下载(并行处理)
await Promise.all([ await Promise.all([
this._unzip(buffer, mpname), this._unzip(buffer, mpname),
await platform(plat).downloadfile(info, unpath, this.message), platform(plat).downloadfile(info, unpath, this.message)
]).catch((e) => { ]);
throw new Error(e);
}); // 解压和下载
this.message.statusChange(); //改变状态 this.message.statusChange(); //改变状态
await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`) await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`).Main();
.Main()
.catch((e) => {
throw new Error(e);
});
this.message.statusChange(); //改变状态(DeEarth筛选模组完毕) this.message.statusChange(); //改变状态(DeEarth筛选模组完毕)
const mlinfo = await platform(plat) const mlinfo = await platform(plat).getinfo(info);
.getinfo(info)
.catch((e) => {
throw new Error(e);
});
if (dser) { if (dser) {
await mlsetup( await mlsetup(
mlinfo.loader, mlinfo.loader,
mlinfo.minecraft, mlinfo.minecraft,
mlinfo.loader_version, mlinfo.loader_version,
unpath unpath
).catch((e) => { ); // 安装服务端
throw new Error(e); } else {
}); //安装服务端
}
if (!dser) {
dinstall( dinstall(
mlinfo.loader, mlinfo.loader,
mlinfo.minecraft, mlinfo.minecraft,
mlinfo.loader_version, mlinfo.loader_version,
unpath unpath
).catch((e) => { );
throw new Error(e);
});
} }
const latest = Date.now(); const latest = Date.now();
this.message.finish(first, latest); //完成 this.message.finish(first, latest); //完成
if (config.oaf) { if (config.oaf) {
await execPromise(`start ${p.join("./instance")}`).catch((e) => { await execPromise(`start ${p.join("./instance")}`);
throw new Error(e);
});
} }
//await this._unzip(buffer);
logger.info(`Task completed in ${latest - first}ms`);
} catch (e) { } catch (e) {
const err = e as Error; const err = e as Error;
logger.error("Main process failed", err);
this.message.handleError(err); this.message.handleError(err);
} }
} }
private async _getinfo(buffer: Buffer) { private async _getinfo(buffer: Buffer) {
const important_name = ["manifest.json", "modrinth.index.json"]; const importantFiles = ["manifest.json", "modrinth.index.json"];
let contain: string = "";
let info: any = {};
const zip = await yauzl_promise(buffer); const zip = await yauzl_promise(buffer);
for await (const entry of zip) { for await (const entry of zip) {
if (important_name.includes(entry.fileName)) { if (importantFiles.includes(entry.fileName)) {
contain = entry.fileName; const content = await entry.ReadEntry;
info = JSON.parse((await entry.ReadEntry).toString()); const info = JSON.parse(content.toString());
break; logger.debug("Found important file", { fileName: entry.fileName, info });
return { contain: entry.fileName, info };
} }
} }
return { contain, info };
throw new Error("No manifest file found in modpack");
} }
private async _unzip(buffer: Buffer, instancename: string) { private async _unzip(buffer: Buffer, instancename: string) {
/* 解压Zip */ logger.info("Starting unzip process", { instancename });
const zip = await yauzl_promise(buffer); const zip = await yauzl_promise(buffer);
const instancePath = `./instance/${instancename}`;
let index = 1; let index = 1;
for await (const entry of zip) { for await (const entry of zip) {
const ew = entry.fileName.endsWith("/"); const isDir = entry.fileName.endsWith("/");
if (ew) {
await fs.promises.mkdir( if (isDir) {
`./instance/${instancename}/${entry.fileName}`, await fs.promises.mkdir(`${instancePath}/${entry.fileName}`, {
{ recursive: true,
recursive: true, });
} } else if (entry.fileName.startsWith("overrides/")) {
); // 跳过黑名单文件
}
if (!ew && entry.fileName.startsWith("overrides/")) {
const dirPath = `./instance/${instancename}/${entry.fileName
.substring(0, entry.fileName.lastIndexOf("/"))
.replace("overrides/", "")}`;
if (this._ublack(entry.fileName)) { if (this._ublack(entry.fileName)) {
index++; index++;
continue; continue;
} }
// 创建目标目录
const targetPath = entry.fileName.replace("overrides/", "");
const dirPath = `${instancePath}/${targetPath.substring(0, targetPath.lastIndexOf("/"))}`;
await fs.promises.mkdir(dirPath, { recursive: true }); await fs.promises.mkdir(dirPath, { recursive: true });
// 解压文件
const stream = await entry.openReadStream; const stream = await entry.openReadStream;
const write = fs.createWriteStream( const write = fs.createWriteStream(`${instancePath}/${targetPath}`);
`./instance/${instancename}/${entry.fileName.replace(
"overrides/",
""
)}`
);
await pipeline(stream, write); await pipeline(stream, write);
} }
this.message.unzip(entry.fileName, zip.length, index); this.message.unzip(entry.fileName, zip.length, index);
// this.ws.send(
// JSON.stringify({
// status: "unzip",
// result: { name: entry.fileName, total: zip.length, current: index },
// })
// );
index++; index++;
} }
/* 解压完成 */
logger.info("Unzip process completed", { instancename, totalFiles: zip.length });
} }
private _ublack(filename: string) { /**
if (filename === "overrides/") { * 检查文件是否在解压黑名单中
return true; * @param filename 文件名
} * @returns 是否在黑名单中
if ( */
filename.includes("shaderpacks") || private _ublack(filename: string): boolean {
filename.includes("essential") || const blacklist = [
filename.includes("resourcepacks") || "overrides/",
filename === "overrides/options.txt" "overrides/options.txt",
) { "shaderpacks",
return true; "essential",
} "resourcepacks"
];
return blacklist.some(item => filename.includes(item));
} }
} }

View File

@@ -5,7 +5,8 @@ import websocket, { WebSocketServer } from "ws"
import { createServer, Server } from "node:http"; import { createServer, Server } from "node:http";
import { Config, IConfig } from "./utils/config.js"; import { Config, IConfig } from "./utils/config.js";
import { Dex } from "./Dex.js"; import { Dex } from "./Dex.js";
import { exec } from "node:child_process"; import { logger } from "./utils/logger.js";
import { checkJava, JavaCheckResult } from "./utils/utils.js";
export class Core { export class Core {
private config: IConfig; private config: IConfig;
private readonly app: Application; private readonly app: Application;
@@ -27,56 +28,119 @@ export class Core {
this.dex = new Dex(this.ws) this.dex = new Dex(this.ws)
} }
javachecker(){ private async javachecker() {
exec("java -version",(err,stdout,stderr)=>{ try {
if(err){ const result: JavaCheckResult = await checkJava();
this.wsx.send(JSON.stringify({
type:"error", if (result.exists && result.version) {
message:"jini" logger.info(`Java detected: ${result.version.fullVersion} (${result.version.vendor})`);
}))
if (this.wsx) {
this.wsx.send(JSON.stringify({
type: "info",
message: `Java detected: ${result.version.fullVersion} (${result.version.vendor})`,
data: result.version
}));
}
} else {
logger.error("Java check failed", result.error);
if (this.wsx) {
this.wsx.send(JSON.stringify({
type: "error",
message: result.error || "Java not found or version check failed",
data: result
}));
}
} }
}) } catch (error) {
logger.error("Java check exception", error as Error);
if (this.wsx) {
this.wsx.send(JSON.stringify({
type: "error",
message: "Java check encountered an exception"
}));
}
}
} }
express() { private express() {
this.app.use(cors()); this.app.use(cors());
this.app.use(express.json()); this.app.use(express.json());
this.app.get('/',(req,res)=>{
res.json({
status:200,
by:"DeEarthX.Core",
qqg:"559349662",
bilibili:"https://space.bilibili.com/1728953419"
})
})
this.app.post("/start", this.upload.single("file"), (req, res) => {
if (!req.file) {
return;
}
if (!req.query.mode){
return;
}
this.dex.Main(req.file.buffer,req.query.mode == "server") //Dex
//this.dex.Main(req.file.buffer)
res.json({
status:200,
message:"task is peding"
})
})
this.app.get('/config/get', (req, res) => {
res.json(this.config)
})
// 健康检查路由
this.app.get('/', (req, res) => {
res.json({
status: 200,
by: "DeEarthX.Core",
qqg: "559349662",
bilibili: "https://space.bilibili.com/1728953419"
});
});
// 启动任务路由
this.app.post("/start", this.upload.single("file"), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ status: 400, message: "No file uploaded" });
}
if (!req.query.mode) {
return res.status(400).json({ status: 400, message: "Mode parameter missing" });
}
const isServerMode = req.query.mode === "server";
logger.info("Starting task", { isServerMode });
// 非阻塞执行主要任务
this.dex.Main(req.file.buffer, isServerMode).catch(err => {
logger.error("Task execution failed", err);
});
res.json({ status: 200, message: "Task is pending" });
} catch (err) {
const error = err as Error;
logger.error("/start route error", error);
res.status(500).json({ status: 500, message: "Internal server error" });
}
});
// 获取配置路由
this.app.get('/config/get', (req, res) => {
try {
res.json(this.config);
} catch (err) {
const error = err as Error;
logger.error("/config/get route error", error);
res.status(500).json({ status: 500, message: "Failed to get config" });
}
});
// 更新配置路由
this.app.post('/config/post', (req, res) => { this.app.post('/config/post', (req, res) => {
Config.write_config(req.body) try {
res.json({ status: 200 }) Config.writeConfig(req.body);
}) this.config = req.body; // 更新内存中的配置
logger.info("Config updated");
res.json({ status: 200 });
} catch (err) {
const error = err as Error;
logger.error("/config/post route error", error);
res.status(500).json({ status: 500, message: "Failed to update config" });
}
});
} }
start() { public async start() {
this.express() this.express();
this.server.listen(37019, () => { this.server.listen(37019, async () => {
console.log("Server is running on http://localhost:37019") logger.info("Server is running on http://localhost:37019");
}) await this.javachecker(); // 启动时检查Java
});
// 处理服务器错误
this.server.on('error', (err) => {
logger.error("Server error", err);
});
} }
} }

View File

@@ -3,39 +3,55 @@ import { Forge } from "./forge.js";
import { Minecraft } from "./minecraft.js"; import { Minecraft } from "./minecraft.js";
import { NeoForge } from "./neoforge.js"; import { NeoForge } from "./neoforge.js";
/**
* 模组加载器接口
*/
interface XModloader { interface XModloader {
setup(): Promise<void>; setup(): Promise<void>;
installer(): Promise<void>; installer(): Promise<void>;
} }
export function modloader(ml:string,mcv:string,mlv:string,path:string){ /**
let modloader:XModloader * 创建模组加载器实例
switch (ml) { * @param ml 加载器类型
* @param mcv Minecraft版本
* @param mlv 加载器版本
* @param path 安装路径
* @returns 加载器实例
*/
export function modloader(ml: string, mcv: string, mlv: string, path: string): XModloader {
switch (ml) {
case "fabric": case "fabric":
modloader = new Fabric(mcv,mlv,path)
break;
case "fabric-loader": case "fabric-loader":
modloader = new Fabric(mcv,mlv,path) return new Fabric(mcv, mlv, path);
break;
case "forge": case "forge":
modloader = new Forge(mcv,mlv,path) return new Forge(mcv, mlv, path);
break;
case "neoforge": case "neoforge":
modloader = new NeoForge(mcv,mlv,path) return new NeoForge(mcv, mlv, path);
break;
default: default:
modloader = new Minecraft(ml,mcv,mlv,path) return 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) * @param ml 加载器类型
await modloader(ml,mcv,mlv,path).setup() * @param mcv Minecraft版本
await minecraft.setup() * @param mlv 加载器版本
* @param path 安装路径
*/
export async function mlsetup(ml: string, mcv: string, mlv: string, path: string): Promise<void> {
const minecraft = new Minecraft(ml, mcv, mlv, path);
await modloader(ml, mcv, mlv, path).setup();
await minecraft.setup();
} }
export async function dinstall(ml:string,mcv:string,mlv:string,path:string){ /**
await modloader(ml,mcv,mlv,path).installer(); * 安装模组加载器
* @param ml 加载器类型
* @param mcv Minecraft版本
* @param mlv 加载器版本
* @param path 安装路径
*/
export async function dinstall(ml: string, mcv: string, mlv: string, path: string): Promise<void> {
await modloader(ml, mcv, mlv, path).installer();
} }

View File

@@ -1,4 +1,8 @@
import fs from "node:fs"; import fs from "node:fs";
/**
* 应用配置接口
*/
export interface IConfig { export interface IConfig {
mirror: { mirror: {
bmclapi: boolean; bmclapi: boolean;
@@ -9,32 +13,65 @@ export interface IConfig {
dexpub: boolean; dexpub: boolean;
mixins: boolean; mixins: boolean;
}; };
oaf: boolean oaf: boolean;
} }
/**
* 默认配置
*/
const DEFAULT_CONFIG: IConfig = {
mirror: {
bmclapi: true,
mcimirror: true,
},
filter: {
hashes: true,
dexpub: false,
mixins: true,
},
oaf: true
};
/**
* 配置文件路径
*/
const CONFIG_PATH = "./config.json";
/**
* 配置管理器
*/
export class Config { export class Config {
private readonly default_config: IConfig = { /**
mirror: { * 获取配置
bmclapi: true, * @returns 配置对象
mcimirror: true, */
}, public static getConfig(): IConfig {
filter: { if (!fs.existsSync(CONFIG_PATH)) {
hashes: true, fs.writeFileSync(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));
dexpub: false, return DEFAULT_CONFIG;
mixins: true, }
},
oaf:true try {
}; const content = fs.readFileSync(CONFIG_PATH, "utf-8");
config(): IConfig { return JSON.parse(content);
if (!fs.existsSync("./config.json")) { } catch (err) {
fs.writeFileSync("./config.json", JSON.stringify(this.default_config)); console.error("Failed to read config file, using defaults", err);
return this.default_config; return DEFAULT_CONFIG;
} }
return JSON.parse(fs.readFileSync("./config.json", "utf-8"));
} }
static write_config(config: IConfig) {
fs.writeFileSync("./config.json", JSON.stringify(config)); /**
* 写入配置
* @param config 配置对象
*/
public static writeConfig(config: IConfig): void {
try {
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
} catch (err) {
console.error("Failed to write config file", err);
}
} }
} }
export default new Config().config(); // 默认导出配置实例
export default Config.getConfig();

View File

@@ -1,17 +1,42 @@
const env = process.env.DEBUG; const env = process.env.DEBUG;
const isDebug = env === "true";
export function debug(msg: any){ // 日志级别枚举
if (env === "true"){ type LogLevel = "debug" | "info" | "warn" | "error";
if(msg instanceof Error){
console.log(`[ERROR] [${new Date().toLocaleString()}] `); /**
console.log(msg); * 日志记录器
} * @param level 日志级别
if (typeof msg === "string"){ * @param msg 日志内容
console.log(`[DEBUG] [${new Date().toLocaleString()}] ` + msg); * @param data 附加数据(可选)
} */
if (typeof msg === "object"){ function log(level: LogLevel, msg: string | Error, data?: any) {
console.log(`[OBJ] [${new Date().toLocaleString()}] `); const timestamp = new Date().toLocaleString();
console.log(msg); const prefix = `[${level.toUpperCase()}] [${timestamp}]`;
}
} // 确保只在调试模式下输出debug日志
if (level === "debug" && !isDebug) return;
if (msg instanceof Error) {
console.error(`${prefix} ${msg.message}`);
console.error(msg.stack);
if (data) console.error(`${prefix} Data:`, data);
} else {
const logFunc = level === "error" ? console.error :
level === "warn" ? console.warn :
console.log;
logFunc(`${prefix} ${msg}`);
if (data) logFunc(`${prefix} Data:`, data);
}
} }
export const logger = {
debug: (msg: string | Error, data?: any) => log("debug", msg, data),
info: (msg: string | Error, data?: any) => log("info", msg, data),
warn: (msg: string | Error, data?: any) => log("warn", msg, data),
error: (msg: string | Error, data?: any) => log("error", msg, data)
};
// 保持向后兼容
export const debug = logger.debug;

View File

@@ -6,7 +6,29 @@ import fs from "node:fs";
import fse from "fs-extra"; import fse from "fs-extra";
import { WebSocket } from "ws"; import { WebSocket } from "ws";
import { ExecOptions, exec} from "node:child_process"; import { ExecOptions, exec} from "node:child_process";
/**
* Java版本信息接口
*/
export interface JavaVersion {
major: number;
minor: number;
patch: number;
fullVersion: string;
vendor: string;
runtimeVersion?: string;
}
/**
* Java检测结果接口
*/
export interface JavaCheckResult {
exists: boolean;
version?: JavaVersion;
error?: string;
}
import { MessageWS } from "./ws.js"; import { MessageWS } from "./ws.js";
import { logger } from "./logger.js";
export class Utils { export class Utils {
public modrinth_url: string; public modrinth_url: string;
@@ -50,90 +72,157 @@ export function version_compare(v1: string, v2: string) {
return 0; return 0;
} }
/**
* 检测Java是否安装并获取版本信息
* @returns Java检测结果
*/
export async function checkJava(): Promise<JavaCheckResult> {
try {
const output = await new Promise<string>((resolve, reject) => {
exec("java -version", (err, stdout, stderr) => {
if (err) {
logger.error("Java check failed", err);
reject(new Error("Java not found"));
return;
}
// Java版本信息输出在stderr中
resolve(stderr);
});
});
logger.debug(`Java version output: ${output}`);
// 解析Java版本信息
const versionRegex = /version "(\d+)(\.(\d+))?(\.(\d+))?/;
const vendorRegex = /(Java\(TM\)|OpenJDK).*Runtime Environment.*by (.*)/;
const versionMatch = output.match(versionRegex);
const vendorMatch = output.match(vendorRegex);
if (!versionMatch) {
return {
exists: true,
error: "Failed to parse Java version"
};
}
const major = parseInt(versionMatch[1], 10);
const minor = versionMatch[3] ? parseInt(versionMatch[3], 10) : 0;
const patch = versionMatch[5] ? parseInt(versionMatch[5], 10) : 0;
const versionInfo: JavaVersion = {
major,
minor,
patch,
fullVersion: versionMatch[0].replace("version ", ""),
vendor: vendorMatch ? vendorMatch[2] : "Unknown"
};
logger.info(`Java detected: ${JSON.stringify(versionInfo)}`);
return {
exists: true,
version: versionInfo
};
} catch (error) {
logger.error("Java check error", error as Error);
return {
exists: false,
error: (error as Error).message
};
}
}
export function execPromise(cmd:string,options?:ExecOptions){ export function execPromise(cmd:string,options?:ExecOptions){
logger.debug(`Executing command: ${cmd}`);
return new Promise((resolve,reject)=>{ return new Promise((resolve,reject)=>{
exec(cmd,options,(err,stdout,stderr)=>{ exec(cmd,options,(err,stdout,stderr)=>{
if(err){ if(err){
logger.error(`Command execution failed: ${cmd}`, err);
logger.debug(`Stderr: ${stderr}`);
reject(err) reject(err)
return; return;
} }
if (stdout) logger.debug(`Command stdout: ${stdout}`);
if (stderr) logger.debug(`Command stderr: ${stderr}`);
}).on('exit',(code)=>{ }).on('exit',(code)=>{
logger.debug(`Command completed with exit code: ${code}`);
resolve(code) resolve(code)
}) })
}) })
} }
export async function fastdownload(data: [string, string]|string[][]) { export async function fastdownload(data: [string, string]|string[][]) {
let _data = undefined; // 确保downloadList始终是[string, string][]类型
if(Array.isArray(data[0])){ const downloadList: [string, string][] = Array.isArray(data[0])
_data = data ? (data as string[][]).map(item => item as [string, string])
}else{ : [data as [string, string]];
_data = [data] logger.info(`Starting fast download of ${downloadList.length} files`);
}
return await pMap( return await pMap(
_data, downloadList,
async (e:any) => { async (item: [string, string]) => {
const [url, filePath] = item;
try { try {
await pRetry( await pRetry(
async () => { async () => {
if (!fs.existsSync(e[1])) { if (!fs.existsSync(filePath)) {
await got logger.debug(`Downloading ${url} to ${filePath}`);
.get(e[0], { const res = await got.get(url, {
responseType: "buffer", responseType: "buffer",
headers: { headers: { "user-agent": "DeEarthX" },
"user-agent": "DeEarthX", });
}, fse.outputFileSync(filePath, res.rawBody);
}) logger.debug(`Downloaded ${url} successfully`);
.then((res) => { } else {
fse.outputFileSync(e[1], res.rawBody); logger.debug(`File already exists, skipping: ${filePath}`);
});
} }
}, },
{ retries: 3 } { retries: 3, onFailedAttempt: (error) => {
logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`);
}}
); );
} catch (e) { } catch (error) {
//LOGGER.error({ err: e }); logger.error(`Failed to download ${url} after 3 attempts`, error);
throw error;
} }
}, },
{ concurrency: 16 } { concurrency: 16 }
); );
} }
export async function Wfastdownload(data: string[][],ws:MessageWS) { export async function Wfastdownload(data: string[][], ws: MessageWS) {
let index = 1; logger.info(`Starting web download of ${data.length} files`);
return await pMap( return await pMap(
data, data,
async (e:any) => { async (item: string[], idx: number) => {
const [url, filePath] = item;
try { try {
await pRetry( await pRetry(
async () => { async () => {
if (!fs.existsSync(e[1])) { if (!fs.existsSync(filePath)) {
await got logger.debug(`Downloading ${url} to ${filePath}`);
.get(e[0], { const res = await got.get(url, {
responseType: "buffer", responseType: "buffer",
headers: { headers: { "user-agent": "DeEarthX" },
"user-agent": "DeEarthX", });
}, fse.outputFileSync(filePath, res.rawBody);
}) logger.debug(`Downloaded ${url} successfully`);
.then((res) => { } else {
fse.outputFileSync(e[1], res.rawBody); logger.debug(`File already exists, skipping: ${filePath}`);
});
} }
ws.download(data.length,index,e[1])
// ws.send(JSON.stringify({ // 更新下载进度
// status:"downloading", ws.download(data.length, idx + 1, filePath);
// result:{
// total:data.length,
// index:index,
// name:e[1]
// }
// }))
index++
}, },
{ retries: 3 } { retries: 3, onFailedAttempt: (error) => {
logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`);
}}
); );
} catch (e) { } catch (error) {
//LOGGER.error({ err: e }); logger.error(`Failed to download ${url} after 3 attempts`, error);
throw error;
} }
}, },
{ concurrency: 16 } { concurrency: 16 }

View File

@@ -1,56 +1,83 @@
import websocket, { WebSocketServer } from "ws"; import websocket, { WebSocketServer } from "ws";
import { logger } from "./logger.js";
export class MessageWS { export class MessageWS {
ws!: websocket; private ws!: websocket;
constructor(ws: websocket) { constructor(ws: websocket) {
this.ws = ws; this.ws = ws;
// 监听WebSocket错误
this.ws.on('error', (err) => {
logger.error("WebSocket error", err);
});
// 监听连接关闭
this.ws.on('close', (code, reason) => {
logger.info("WebSocket connection closed", { code, reason: reason.toString() });
});
} }
finish(first: number, latest: number) { /**
this.ws.send( * 发送完成消息
JSON.stringify({ * @param startTime 开始时间
status: "finish", * @param endTime 结束时间
result: latest - first, */
}) finish(startTime: number, endTime: number) {
); this.send("finish", endTime - startTime);
} }
/**
* 发送解压进度消息
* @param entryName 文件名
* @param total 总文件数
* @param current 当前文件索引
*/
unzip(entryName: string, total: number, current: number) { unzip(entryName: string, total: number, current: number) {
this.ws.send( this.send("unzip", { name: entryName, total, current });
JSON.stringify({
status: "unzip",
result: { name: entryName, total, current },
})
);
} }
/**
* 发送下载进度消息
* @param total 总文件数
* @param index 当前文件索引
* @param name 文件名
*/
download(total: number, index: number, name: string) { download(total: number, index: number, name: string) {
this.ws.send( this.send("downloading", { total, index, name });
JSON.stringify({
status: "downloading",
result: {
total,
index,
name,
},
})
);
} }
/**
* 发送状态变更消息
*/
statusChange() { statusChange() {
this.ws.send( this.send("changed", undefined);
JSON.stringify({
status: "changed",
result: undefined,
})
);
} }
/**
* 发送错误消息
* @param error 错误对象
*/
handleError(error: Error) { handleError(error: Error) {
this.ws.send( this.send("error", error.message);
JSON.stringify({ }
status: "error",
result: error.message, /**
}) * 通用消息发送方法
); * @param status 消息状态
* @param result 消息内容
*/
private send(status: string, result: any) {
try {
if (this.ws.readyState === websocket.OPEN) {
const message = JSON.stringify({ status, result });
logger.debug("Sending WebSocket message", { status, result });
this.ws.send(message);
} else {
logger.warn(`WebSocket not open, cannot send message: ${status}`);
}
} catch (err) {
logger.error("Failed to send WebSocket message", err);
}
} }
} }