feat:改了一大堆
This commit is contained in:
69
backend/src/Dex.ts
Normal file
69
backend/src/Dex.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import websocket from "ws";
|
||||||
|
import { yauzl_promise } from "./utils/yauzl.promise.js";
|
||||||
|
import { pipeline } from "node:stream/promises";
|
||||||
|
import { platform, what_platform } from "./platform/index.js";
|
||||||
|
|
||||||
|
interface Iinfo{
|
||||||
|
name:string
|
||||||
|
buffer:Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Dex {
|
||||||
|
ws: websocket;
|
||||||
|
in: any;
|
||||||
|
constructor(ws: websocket) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.in = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Main(buffer: Buffer) {
|
||||||
|
const info = await this._getinfo(buffer)
|
||||||
|
const plat = what_platform(info)
|
||||||
|
const mpname = this.in.name
|
||||||
|
await Promise.all([
|
||||||
|
this._unzip(buffer,mpname),
|
||||||
|
platform(plat).downloadfile(this.in,`./instance/${mpname}`)
|
||||||
|
])
|
||||||
|
//await this._unzip(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getinfo(buffer: Buffer){
|
||||||
|
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())
|
||||||
|
contain = e.fileName
|
||||||
|
})
|
||||||
|
return contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _unzip(buffer: Buffer,instancename:string) {
|
||||||
|
/* 解压Zip */
|
||||||
|
const zip = await yauzl_promise(buffer);
|
||||||
|
let index = 0;
|
||||||
|
for await (const entry of zip) {
|
||||||
|
const ew = entry.fileName.endsWith("/");
|
||||||
|
if (ew) {
|
||||||
|
await fs.promises.mkdir(`./instance/${instancename}/${entry.fileName}`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!ew&&entry.fileName.startsWith("overrides/")) {
|
||||||
|
const dirPath = `./instance/${instancename}/${entry.fileName.substring(
|
||||||
|
0,
|
||||||
|
entry.fileName.lastIndexOf("/")
|
||||||
|
)}`;
|
||||||
|
await fs.promises.mkdir(dirPath, { recursive: true });
|
||||||
|
const stream = await entry.openReadStream;
|
||||||
|
const write = fs.createWriteStream(`./instance/${instancename}/${entry.fileName}`);
|
||||||
|
await pipeline(stream, write);
|
||||||
|
}
|
||||||
|
this.ws.send(JSON.stringify({ status: "unzip", result: { name: entry.fileName,total: zip.length, current:index } }));
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
/* 解压完成 */
|
||||||
|
this.ws.send(JSON.stringify({ status: "changed", result: undefined }));
|
||||||
|
}
|
||||||
|
}
|
||||||
60
backend/src/core.ts
Normal file
60
backend/src/core.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import express, { Application } from "express";
|
||||||
|
import multer from "multer";
|
||||||
|
import cors from "cors"
|
||||||
|
import websocket, { WebSocketServer } from "ws"
|
||||||
|
import { createServer, Server } from "node:http";
|
||||||
|
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";
|
||||||
|
export class Core {
|
||||||
|
private config: IConfig;
|
||||||
|
private readonly app: Application;
|
||||||
|
private readonly server: Server;
|
||||||
|
public ws!: websocket;
|
||||||
|
private readonly upload: multer.Multer;
|
||||||
|
private task: {} = {};
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async DeEarthX(buffer:Buffer){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
express() {
|
||||||
|
this.app.use(cors());
|
||||||
|
this.app.use(express.json());
|
||||||
|
this.app.post("/start", this.upload.single("file"), (req, res) => {
|
||||||
|
if (!req.file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.DeEarthX(req.file.buffer)
|
||||||
|
res.json({
|
||||||
|
status:200,
|
||||||
|
message:"task is peding"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.app.get('/config/get', (req, res) => {
|
||||||
|
res.json(this.config)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.post('/config/post', (req, res) => {
|
||||||
|
Config.write_config(req.body)
|
||||||
|
res.json({ status: 200 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.express()
|
||||||
|
this.server.listen(37019, () => {
|
||||||
|
console.log("Server is running on http://localhost:37019")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +1,24 @@
|
|||||||
import config, { Config } from "./utils/config.js";
|
import config from "./utils/config.js";
|
||||||
import fsp from "node:fs/promises";
|
import fsp from "node:fs/promises";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { pipeline } from "node:stream/promises";
|
import { pipeline } from "node:stream/promises";
|
||||||
import { yauzl_promise } from "./utils/yauzl.promise.js";
|
import { yauzl_promise } from "./utils/yauzl.promise.js";
|
||||||
import express from "express";
|
import { Core } from "./core.js";
|
||||||
import multer from "multer";
|
import { DeEarth } from "./utils/DeEarth.js";
|
||||||
import cors from "cors"
|
import { version_compare } from "./utils/utils.js";
|
||||||
import websocket, {WebSocketServer} from "ws"
|
import { Forge } from "./modloader/forge.js";
|
||||||
import { createServer } from "node:http";
|
import { Minecraft } from "./modloader/minecraft.js";
|
||||||
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);
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/getconfig',(req,res)=>{
|
const core = new Core(config);
|
||||||
res.json(config)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/writeconfig',(req,res)=>{
|
core.start();
|
||||||
Config.write_config(req.body)
|
|
||||||
res.json({status:200})
|
|
||||||
})
|
|
||||||
// WebSocket
|
|
||||||
wss.on("connection",(wsx)=>{
|
|
||||||
ws = wsx;
|
|
||||||
})
|
|
||||||
|
|
||||||
server.listen(37019,()=>{
|
//console.log(version_compare("1.18.1","1.16.5"))
|
||||||
console.log("Server is running on http://localhost:37019")
|
|
||||||
})
|
|
||||||
|
|
||||||
async function DeX(buffer: Buffer) {
|
// await new DeEarth("./mods").Main()
|
||||||
/* 解压Zip */
|
|
||||||
const zip = await yauzl_promise(buffer);
|
// async function Dex(buffer: Buffer) {
|
||||||
for await (const entry of zip) {
|
|
||||||
const ew = entry.fileName.endsWith('/')
|
// }
|
||||||
if (ew){
|
// new Forge("1.20.1","47.3.10").setup()
|
||||||
await fsp.mkdir(`./test/${entry.fileName}`,{recursive:true})
|
await new Minecraft("forge","1.20.1").setup()
|
||||||
}
|
|
||||||
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}))
|
|
||||||
}
|
|
||||||
0
backend/src/modloader/fabric.ts
Normal file
0
backend/src/modloader/fabric.ts
Normal file
94
backend/src/modloader/forge.ts
Normal file
94
backend/src/modloader/forge.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import got, { Got } from "got";
|
||||||
|
import fs from "node:fs"
|
||||||
|
import fse from "fs-extra"
|
||||||
|
import { xfastdownload } from "../utils/utils.js";
|
||||||
|
import { yauzl_promise } from "../utils/yauzl.promise.js";
|
||||||
|
|
||||||
|
interface Iforge{
|
||||||
|
data:{
|
||||||
|
MOJMAPS:{
|
||||||
|
server:string
|
||||||
|
},
|
||||||
|
MAPPINGS:{
|
||||||
|
server:string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Iversion{
|
||||||
|
downloads:{
|
||||||
|
server_mappings:{
|
||||||
|
url:string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Forge {
|
||||||
|
minecraft: string;
|
||||||
|
loaderVersion: string;
|
||||||
|
got: Got;
|
||||||
|
constructor(minecraft:string,loaderVersion:string){
|
||||||
|
this.minecraft = minecraft;
|
||||||
|
this.loaderVersion = loaderVersion;
|
||||||
|
this.got = got.extend({
|
||||||
|
prefixUrl: "https://bmclapi2.bangbang93.com",
|
||||||
|
headers: { "User-Agent": "DeEarthX" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async setup(){
|
||||||
|
await this.installer()
|
||||||
|
await this.library()
|
||||||
|
// if (this.minecraft.startsWith("1.18")){
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
async library(){
|
||||||
|
const _downlist: [string,string][]= []
|
||||||
|
const data = await fs.promises.readFile(`./forge/Forge-${this.minecraft}-${this.loaderVersion}.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}`])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(entry.fileName === "install_profile.json"){ //MOJMAPS
|
||||||
|
/* 获取MOJMAPS */
|
||||||
|
const json = JSON.parse((await entry.ReadEntry).toString()) as Iforge
|
||||||
|
const vjson = await this.got.get(`version/${this.minecraft}/json`).json<Iversion>()
|
||||||
|
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}`])
|
||||||
|
/* 获取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}`])
|
||||||
|
/* 获取MAPPING */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const downlist = [...new Set(_downlist)]
|
||||||
|
await xfastdownload(downlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
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}.jar`,res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MTP(string:string){
|
||||||
|
const mjp = string.replace(/^\[|\]$/g, '')
|
||||||
|
const OriginalName = mjp.split("@")[0]
|
||||||
|
const x = OriginalName.split(":")
|
||||||
|
const mappingType = mjp.split('@')[1];
|
||||||
|
if(x[3]){
|
||||||
|
return `${x[0].replace(/\./g, '/')}/${x[1]}/${x[2]}/${x[1]}-${x[2]}-${x[3]}.${mappingType}`
|
||||||
|
}else{
|
||||||
|
return `${x[0].replace(/\./g, '/')}/${x[1]}/${x[2]}/${x[1]}-${x[2]}.${mappingType}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
backend/src/modloader/index.ts
Normal file
15
backend/src/modloader/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
interface XModloader {
|
||||||
|
setup: Promise<void>
|
||||||
|
}
|
||||||
|
export function modloader(ml:string,mcv:string,mlv:string){
|
||||||
|
switch (ml) {
|
||||||
|
case "fabric":
|
||||||
|
break;
|
||||||
|
case "forge":
|
||||||
|
break;
|
||||||
|
case "neoforge":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
backend/src/modloader/minecraft.ts
Normal file
66
backend/src/modloader/minecraft.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
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";
|
||||||
|
|
||||||
|
interface ILInfo{
|
||||||
|
libraries:{
|
||||||
|
downloads:{
|
||||||
|
artifact:{
|
||||||
|
path:string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Minecraft{
|
||||||
|
loader: string;
|
||||||
|
minecraft: string;
|
||||||
|
constructor(loader:string,minecraft:string,){
|
||||||
|
this.loader = loader;
|
||||||
|
this.minecraft = minecraft;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setup(){
|
||||||
|
switch (this.loader){
|
||||||
|
case "forge":
|
||||||
|
await this.forge_setup();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async forge_setup(){
|
||||||
|
const mcpath = `./forge/libraries/net/minecraft/server/${this.minecraft}/server-${this.minecraft}.jar`
|
||||||
|
await fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`,mcpath])
|
||||||
|
if(version_compare(this.minecraft,"1.18") === 1){
|
||||||
|
// 1.18.x + 依赖解压
|
||||||
|
const zip = await yauzl_promise(await fs.promises.readFile(mcpath))
|
||||||
|
for await(const entry of zip){
|
||||||
|
//console.log(entry.fileName.replace("META-INF/libraries/",""))
|
||||||
|
if(entry.fileName.endsWith("/")){
|
||||||
|
const dirPath = entry.fileName.replace("META-INF/libraries/","./forge/libraries/")
|
||||||
|
if (!fs.existsSync(dirPath)){
|
||||||
|
await fs.promises.mkdir(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 - 依赖下载
|
||||||
|
const json = await got.get(`https://bmclapi2.bangbang93.com/version/${this.minecraft}/json`)
|
||||||
|
.json<ILInfo>()
|
||||||
|
json.libraries.forEach(async e=>{
|
||||||
|
const path = e.downloads.artifact.path
|
||||||
|
await fastdownload([`https://bmclapi2.bangbang93.com/maven/${path}`,`./forge/libraries/${path}`])
|
||||||
|
})
|
||||||
|
//1.18.x - 依赖下载
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
backend/src/modloader/neoforge.ts
Normal file
0
backend/src/modloader/neoforge.ts
Normal file
@@ -30,6 +30,9 @@ export class CurseForge implements XPlatform {
|
|||||||
|
|
||||||
async downloadfile(manifest: object, path: string): Promise<void> {
|
async downloadfile(manifest: object, path: string): Promise<void> {
|
||||||
const local_manifest = manifest as CurseForgeManifest;
|
const local_manifest = manifest as CurseForgeManifest;
|
||||||
|
if (local_manifest.files.length === 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
const FileID = JSON.stringify({
|
const FileID = JSON.stringify({
|
||||||
fileIds: local_manifest.files.map(
|
fileIds: local_manifest.files.map(
|
||||||
(file: { fileID: number }) => file.fileID
|
(file: { fileID: number }) => file.fileID
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { CurseForge } from "./curseforge.js";
|
import { CurseForge } from "./curseforge.js";
|
||||||
import { MCBBS } from "./mcbbs.js";
|
|
||||||
import { Modrinth } from "./modrinth.js";
|
import { Modrinth } from "./modrinth.js";
|
||||||
|
|
||||||
export interface XPlatform {
|
export interface XPlatform {
|
||||||
@@ -22,21 +21,20 @@ export function platform(plat: string | undefined): XPlatform {
|
|||||||
case "modrinth":
|
case "modrinth":
|
||||||
platform = new Modrinth();
|
platform = new Modrinth();
|
||||||
break;
|
break;
|
||||||
case "mcbbs":
|
// case "mcbbs":
|
||||||
platform = new MCBBS();
|
// platform = new MCBBS();
|
||||||
break;
|
// break;
|
||||||
}
|
}
|
||||||
return platform;
|
return platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function what_platform(dud_files: Array<string>) {
|
export function what_platform(dud_files: string | "manifest.json" | "modrinth.index.json") {
|
||||||
if (dud_files.includes("mcbbs.packmeta")) {
|
switch (dud_files) {
|
||||||
return "mcbbs";
|
case "manifest.json":
|
||||||
} else if (dud_files.includes("manifest.json")) {
|
return "curseforge";
|
||||||
return "curseforge";
|
case "modrinth.index.json":
|
||||||
} else if (dud_files.includes("modrinth.index.json")) {
|
return "modrinth";
|
||||||
return "modrinth";
|
default:
|
||||||
} else {
|
return undefined
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { CurseForgeManifest } from "./curseforge.js";
|
|
||||||
import { modpack_info, XPlatform } from "./index.js";
|
|
||||||
|
|
||||||
interface MCBBSManifest extends CurseForgeManifest {}
|
|
||||||
|
|
||||||
export class MCBBS implements XPlatform {
|
|
||||||
async getinfo(manifest: object): Promise<modpack_info> {
|
|
||||||
const result: modpack_info = Object.create({});
|
|
||||||
const local_manifest = manifest as MCBBSManifest;
|
|
||||||
if (result && local_manifest)
|
|
||||||
result.minecraft = local_manifest.minecraft.version;
|
|
||||||
const id = local_manifest.minecraft.modLoaders[0].id;
|
|
||||||
const loader_all = id.match(/(.*)-/) as RegExpMatchArray;
|
|
||||||
result.loader = loader_all[1];
|
|
||||||
result.loader_version = id.replace(loader_all[0], "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadfile(urls: [string, string]): Promise<void> {}
|
|
||||||
}
|
|
||||||
148
backend/src/utils/DeEarth.ts
Normal file
148
backend/src/utils/DeEarth.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
import crypto from "node:crypto"
|
||||||
|
import { yauzl_promise } from "./yauzl.promise.js"
|
||||||
|
import got from "got"
|
||||||
|
import { Utils } from "./utils.js"
|
||||||
|
interface IMixins{
|
||||||
|
name: string
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFile{
|
||||||
|
filename: string
|
||||||
|
hash: string
|
||||||
|
mixins: IMixins[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHashRes{
|
||||||
|
[key:string]:{
|
||||||
|
project_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface IPjs{
|
||||||
|
id:string,
|
||||||
|
client_side:string,
|
||||||
|
server_side:string
|
||||||
|
}
|
||||||
|
export class DeEarth{
|
||||||
|
movepath: string
|
||||||
|
modspath: string
|
||||||
|
file: IFile[]
|
||||||
|
utils: Utils
|
||||||
|
constructor(modspath:string) {
|
||||||
|
this.utils = new Utils();
|
||||||
|
this.movepath = "./.rubbish"
|
||||||
|
this.modspath = modspath
|
||||||
|
this.file = []
|
||||||
|
}
|
||||||
|
|
||||||
|
async Main(){
|
||||||
|
if(!fs.existsSync(this.movepath)){
|
||||||
|
fs.mkdirSync(this.movepath,{recursive:true})
|
||||||
|
}
|
||||||
|
await this.getFile()
|
||||||
|
const hash = await this.Check_Hashes()
|
||||||
|
const mixins = await this.Check_Mixins()
|
||||||
|
const result = [...new Set(hash.concat(mixins))]
|
||||||
|
result.forEach(async e=>{
|
||||||
|
await fs.promises.rename(`${this.modspath}/${e}`,`${this.movepath}/${e}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async Check_Hashes(){
|
||||||
|
const cmap = new Map<string,string>()
|
||||||
|
const fmap = new Map<string,string>()
|
||||||
|
const hashes:string[] = []
|
||||||
|
const files = this.file.forEach(e=>{
|
||||||
|
hashes.push(e.hash);
|
||||||
|
cmap.set(e.hash,e.filename)
|
||||||
|
})
|
||||||
|
const res = await got.post(this.utils.modrinth_url+"/v2/version_files",{
|
||||||
|
headers:{
|
||||||
|
"User-Agent": "DeEarth",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
json:{
|
||||||
|
hashes,
|
||||||
|
algorithm: "sha1"
|
||||||
|
}
|
||||||
|
}).json<IHashRes>()
|
||||||
|
const x = Object.keys(res)
|
||||||
|
const arr = []
|
||||||
|
const fhashes = []
|
||||||
|
for(let i=0;i<x.length;i++){
|
||||||
|
const e = x[i] //hash
|
||||||
|
const d = res[e] //hash object
|
||||||
|
const result = cmap.get(e)
|
||||||
|
if(result){
|
||||||
|
fmap.set(d.project_id,result)
|
||||||
|
}
|
||||||
|
fhashes.push(e)
|
||||||
|
arr.push(d.project_id)
|
||||||
|
|
||||||
|
}
|
||||||
|
const mpres = await got.get(`${this.utils.modrinth_url}/v2/projects?ids=${JSON.stringify(arr)}`,{
|
||||||
|
headers:{
|
||||||
|
"User-Agent": "DeEarth"
|
||||||
|
}
|
||||||
|
}).json<IPjs[]>()
|
||||||
|
const result = [] //要删除的文件
|
||||||
|
for(let i=0;i<mpres.length;i++){
|
||||||
|
const e = mpres[i]
|
||||||
|
if(e.client_side==="required" && e.server_side==="unsupported"){
|
||||||
|
const f = fmap.get(e.id)
|
||||||
|
result.push(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async Check_Mixins(){
|
||||||
|
//const files = await this.getFile()
|
||||||
|
const files = this.file
|
||||||
|
const result:string[] = []
|
||||||
|
for(let i=0;i<files.length;i++){
|
||||||
|
const file = files[i]
|
||||||
|
file.mixins.forEach(e=>{
|
||||||
|
try{
|
||||||
|
const json = JSON.parse(e.data);
|
||||||
|
if(this._isClientMx(file,json)){
|
||||||
|
result.push(file.filename)
|
||||||
|
}
|
||||||
|
}catch(e){}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const _result = [...new Set(result)]
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
async getFile():Promise<IFile[]>{
|
||||||
|
const files = this.getDir()
|
||||||
|
const arr = []
|
||||||
|
for(let i=0;i<files.length;i++){
|
||||||
|
const _file = files[i]
|
||||||
|
const file = `${this.modspath}/${_file}`
|
||||||
|
const data = fs.readFileSync(file)
|
||||||
|
const sha1 = crypto.createHash('sha1').update(data).digest('hex') //Get Hash
|
||||||
|
const mxarr:{name:string,data:string}[] = []
|
||||||
|
const mixins = (await yauzl_promise(data)).forEach(async e=>{ //Get Mixins Info to check
|
||||||
|
if(e.fileName.endsWith(".mixins.json")&&!e.fileName.includes("/")){
|
||||||
|
mxarr.push({name:e.fileName,data:(await e.ReadEntry).toString()})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arr.push({filename:file,hash:sha1,mixins:mxarr})
|
||||||
|
}
|
||||||
|
this.file = arr
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
private getDir():string[]{
|
||||||
|
if(!fs.existsSync(this.movepath)){
|
||||||
|
fs.mkdirSync(this.movepath)
|
||||||
|
}
|
||||||
|
const dirarr = fs.readdirSync(this.modspath).filter(e=>e.endsWith(".jar")).filter(e=>e.concat(this.modspath));
|
||||||
|
return dirarr
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isClientMx(file:IFile,mixins:any){
|
||||||
|
return (!("mixins" in mixins) || mixins.mixins.length === 0)&&(("client" in mixins) && (mixins.client.length !== 0))&&!file.filename.includes("lib")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import fs from "fs";
|
import fs from "node:fs";
|
||||||
interface IConfig {
|
export interface IConfig {
|
||||||
mirror: {
|
mirror: {
|
||||||
bmclapi: boolean;
|
bmclapi: boolean;
|
||||||
mcimirror: boolean;
|
mcimirror: boolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import pMap from "p-map";
|
|||||||
import config from "./config.js";
|
import config from "./config.js";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import pRetry from "p-retry";
|
import pRetry from "p-retry";
|
||||||
import fs from "fs";
|
import fs from "node:fs";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
@@ -24,18 +24,42 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mavenToUrl(
|
||||||
|
coordinate: { split: (arg0: string) => [any, any, any, any] },
|
||||||
|
base = "maven"
|
||||||
|
) {
|
||||||
|
const [g, a, v, ce] = coordinate.split(":");
|
||||||
|
const [c, e = "jar"] = (ce || "").split("@");
|
||||||
|
return `${base.replace(/\/$/, "")}/${g.replace(
|
||||||
|
/\./g,
|
||||||
|
"/"
|
||||||
|
)}/${a}/${v}/${a}-${v}${c ? "-" + c : ""}.${e}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function version_compare(v1: string, v2: string) {
|
||||||
|
const v1_arr = v1.split(".");
|
||||||
|
const v2_arr = v2.split(".");
|
||||||
|
for (let i = 0; i < v1_arr.length; i++) {
|
||||||
|
if (v1_arr[i] !== v2_arr[i]) {
|
||||||
|
return v1_arr[i] > v2_arr[i] ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fastdownload(data: [string, string]) {
|
export async function fastdownload(data: [string, string]) {
|
||||||
return await pMap(
|
return await pMap(
|
||||||
data,
|
[data],
|
||||||
async (e) => {
|
async (e:any) => {
|
||||||
try {
|
try {
|
||||||
await pRetry(
|
await pRetry(
|
||||||
async () => {
|
async () => {
|
||||||
if (!fs.existsSync(e[1])) {
|
if (!fs.existsSync(e[1])) {
|
||||||
|
/*
|
||||||
const size: number = await (async () => {
|
const size: number = await (async () => {
|
||||||
const head = (
|
const head = (
|
||||||
await got.head(
|
await got.head(
|
||||||
e[0] /*.replace("https://mod.mcimirror.top","https://edge.forgecdn.net")*/,
|
e[0],
|
||||||
{ headers: { "user-agent": "DeEarthX" } }
|
{ headers: { "user-agent": "DeEarthX" } }
|
||||||
)
|
)
|
||||||
).headers["content-length"];
|
).headers["content-length"];
|
||||||
@@ -44,7 +68,56 @@ export async function fastdownload(data: [string, string]) {
|
|||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
})();
|
})();*/
|
||||||
|
console.log(e)
|
||||||
|
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 xfastdownload(data: [string, string][]) {
|
||||||
|
return await pMap(
|
||||||
|
data,
|
||||||
|
async (e:any) => {
|
||||||
|
try {
|
||||||
|
await pRetry(
|
||||||
|
async () => {
|
||||||
|
if (!fs.existsSync(e[1])) {
|
||||||
|
/*
|
||||||
|
const size: number = await (async () => {
|
||||||
|
const head = (
|
||||||
|
await got.head(
|
||||||
|
e[0],
|
||||||
|
{ headers: { "user-agent": "DeEarthX" } }
|
||||||
|
)
|
||||||
|
).headers["content-length"];
|
||||||
|
if (head) {
|
||||||
|
return Number(head);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})();*/
|
||||||
|
console.log(e)
|
||||||
await got
|
await got
|
||||||
.get(e[0], {
|
.get(e[0], {
|
||||||
responseType: "buffer",
|
responseType: "buffer",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Stream from "node:stream"
|
|||||||
|
|
||||||
export interface IentryP extends yauzl.Entry {
|
export interface IentryP extends yauzl.Entry {
|
||||||
openReadStream: Promise<Stream.Readable>;
|
openReadStream: Promise<Stream.Readable>;
|
||||||
|
ReadEntry: Promise<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
|
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
|
||||||
@@ -25,7 +26,8 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
|
|||||||
getLastModDate: entry.getLastModDate,
|
getLastModDate: entry.getLastModDate,
|
||||||
isEncrypted: entry.isEncrypted,
|
isEncrypted: entry.isEncrypted,
|
||||||
isCompressed: entry.isCompressed,
|
isCompressed: entry.isCompressed,
|
||||||
openReadStream: _openReadStream(zip,entry)
|
openReadStream: _openReadStream(zip,entry),
|
||||||
|
ReadEntry: _ReadEntry(zip,entry)
|
||||||
}
|
}
|
||||||
entries.push(_entry)
|
entries.push(_entry)
|
||||||
if (zip.entryCount === entries.length){
|
if (zip.entryCount === entries.length){
|
||||||
@@ -39,6 +41,24 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _ReadEntry(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Buffer>{
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
zip.openReadStream(entry,(err,stream)=>{
|
||||||
|
if (err){
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
stream.on("data",(chunk)=>{
|
||||||
|
chunks.push(chunk);
|
||||||
|
})
|
||||||
|
stream.on("end",()=>{
|
||||||
|
resolve(Buffer.concat(chunks));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function _openReadStream(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Stream.Readable>{
|
async function _openReadStream(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Stream.Readable>{
|
||||||
return new Promise((resolve,reject)=>{
|
return new Promise((resolve,reject)=>{
|
||||||
zip.openReadStream(entry,(err,stream)=>{
|
zip.openReadStream(entry,(err,stream)=>{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { h, ref } from 'vue';
|
|||||||
import { MenuProps } from 'ant-design-vue';
|
import { MenuProps } from 'ant-design-vue';
|
||||||
import { SettingOutlined, UserOutlined, WindowsOutlined } from '@ant-design/icons-vue';
|
import { SettingOutlined, UserOutlined, WindowsOutlined } from '@ant-design/icons-vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import * as shell from '@tauri-apps/plugin-shell';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const selectedKeys = ref(['main']);
|
const selectedKeys = ref(['main']);
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { nextTick, ref, VNodeRef } from 'vue';
|
|||||||
import { InboxOutlined } from '@ant-design/icons-vue';
|
import { InboxOutlined } from '@ant-design/icons-vue';
|
||||||
import { message, StepsProps } from 'ant-design-vue';
|
import { message, StepsProps } from 'ant-design-vue';
|
||||||
import type { UploadFile, UploadChangeParam, Upload } from 'ant-design-vue';
|
import type { UploadFile, UploadChangeParam, Upload } from 'ant-design-vue';
|
||||||
import * as shell from '@tauri-apps/plugin-shell';
|
|
||||||
interface IWSM {
|
interface IWSM {
|
||||||
status: "unzip"|"pending"|"changed",
|
status: "unzip"|"pending"|"changed",
|
||||||
result: string
|
result: string
|
||||||
@@ -63,7 +62,7 @@ function reactFL() {
|
|||||||
isDisabled.value = false;
|
isDisabled.value = false;
|
||||||
}
|
}
|
||||||
/* 获取文件区 */
|
/* 获取文件区 */
|
||||||
shell.Command.create('core',['start']).spawn()
|
//shell.Command.create('core',['start']).spawn()
|
||||||
function runDeEarthX(data: Blob) {
|
function runDeEarthX(data: Blob) {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import * as shell from '@tauri-apps/plugin-shell';
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
/* Config */
|
/* Config */
|
||||||
interface IConfig {
|
interface IConfig {
|
||||||
@@ -15,7 +14,7 @@ interface IConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const config = ref<IConfig>({mirror: {bmclapi: false, mcimirror: false}, filter: {hashes: false, dexpub: false, mixins: false}})
|
const config = ref<IConfig>({mirror: {bmclapi: false, mcimirror: false}, filter: {hashes: false, dexpub: false, mixins: false}})
|
||||||
fetch('http://localhost:37019/getconfig',{
|
fetch('http://localhost:37019/config/get',{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -25,7 +24,7 @@ fetch('http://localhost:37019/getconfig',{
|
|||||||
let first = true;
|
let first = true;
|
||||||
watch(config,(newv)=>{ //写入Config
|
watch(config,(newv)=>{ //写入Config
|
||||||
if(!first){
|
if(!first){
|
||||||
fetch('http://localhost:37019/writeconfig',{
|
fetch('http://localhost:37019/config/post',{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
Reference in New Issue
Block a user