Files
DeEarthX-V2/src/utils/DeEarth.ts
2025-07-24 02:40:20 +08:00

267 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Power by.Tianpao
* 本工具可能会判断失误,但也能为您节省不少时间!
* DeEarth V2 From StarNet.X
* Writing in 07.10.2025(latest)
* ©2024-2025
*/
import AdmZip from "adm-zip";
import got from "got";
import fs from "fs";
import toml from 'smol-toml'
import path from 'path';
import pMap from "p-map";
import { LOGGER } from "./logger.js";
import { MultiBar } from "cli-progress";
export async function DeEarthMain(modspath: string, movepath: any) {
if (!fs.existsSync(movepath)) {
fs.mkdirSync(movepath)
}
LOGGER.info(`DeEarth V1.0.0`)
LOGGER.info(`如有无法筛选的mods请前往 https://dearth.0771010.xyz/ 提交未成功筛选的模组的modid`)
LOGGER.info(`Probejs 7.0.0以上版本为非客户端mod如rubbish中有请自行添加回去`)
const resaddr = fs.readdirSync(modspath)
LOGGER.info(`获取目录列表,一共${resaddr.length}个jar文件。`)
const totalBar = multibar.create(resaddr.length, 0, { filename: '总文件数' })
for (let i = 0; i < resaddr.length; i++) {
const e = `${modspath}/${resaddr[i]}`
if (e.endsWith(".jar") && fs.statSync(e).isFile()) { //判断是否以.jar结尾并且是文件
//console.log(e)
await DeEarth(e, movepath) //使用DeEarth进行审查mod并移动
totalBar.increment()
}
}
multibar.stop()
}
/*
export async function DeEarth(modpath: string, movepath: string) {
try {
const zip = new AdmZip(modpath).getEntries();
//for (let i = 0; i < zip.length; i++) {
//const e = zip[i]
try { //Modrinth
for (let i = 0; i < zip.length; i++) {
const e = zip[i]
if (isForge(e.entryName)) { //Forge,NeoForge
const modid = toml.parse(e.getData().toString('utf-8')).mods[0].modId
//const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json()
const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`))
if (body.client_side == "required" && body.server_side !== "required") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} else if (e.entryName == "fabric.mod.json") { //Fabric
const modid = JSON.parse(e.getData().toString('utf-8')).id
//const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json()
const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`))
if (body.client_side == "required" && body.server_side !== "required") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
console.log("Modrinth")
}
} catch (error) { //mods.toml或fabric.mod.json判断
for (let i = 0; i < zip.length; i++) {
try {
const e = zip[i]
if (isForge(e.entryName)) { //Forge,Neoforge
const modid = toml.parse(e.getData().toString('utf-8')).mods[0].modId
const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`))
if (body.isClient) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} else if (e.entryName == "fabric.mod.json") { //Fabric
const modid = JSON.parse(e.getData().toString('utf-8')).id
const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`))
if (body.isClient) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
console.log("DeEarth")
} catch (errorr) {
for (let i = 0; i < zip.length; i++) {
const e = zip[i]
try {
if (isForge(e.entryName)) { //Forge,Neoforge
const tr = toml.parse(e.getData().toString('utf-8'))
const mcside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "minecraft").side
if (mcside == "CLIENT") { //从Minecraft判断
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
const forgeside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "forge").side
if (forgeside == "CLIENT") { //从Forge判断
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
const neoside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "neoforge").side
if (neoside == "CLIENT") { //从NeoForge判断
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} else if (e.entryName == "fabric.mod.json") { //Fabric
const fmj = JSON.parse(e.getData().toString('utf-8')).environment
if (fmj == "client") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
} catch (erro) {//从Mixin判断 但是可能为不准确
for (let i = 0; i < zip.length; i++) {
const e = zip[i]
try {
if (isMixinFile(e.entryName)) {
LOGGER.info(e.entryName)
const resx = JSON.parse(e.getData().toString('utf-8'))
if (e.entryName.includes("common.mixins.json")) { //第一步从common mixins文件判断判断失败后再使用modid.mixins.json进行判断
if (isMixin(resx)) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} else {
if (isMixin(resx)) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
}
} catch (err: any) {//避免有傻逼JSON写注释虽然GSON可以这样 但是这样一点也不人道)
if (err.errno !== -4058) {
LOGGER.error(`大天才JSON写注释了估计模组路径:${modpath},过滤失败`)
}
}
}
}
}
}
}
}
} catch (error) {
LOGGER.error("DeEarth: " + error)
}
}
*/
export async function DeEarth(modpath: string, movepath: string) {
const zipinfo = ZipInfo(modpath)
let modid:string = ""
if(zipinfo.modinfo.type === "forge"){
modid = zipinfo.modinfo.data.mods[0].modId
}else if(zipinfo.modinfo.type === "fabric"){
modid = zipinfo.modinfo.data.id
}
try { //Modrinth
const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`))
if(body.client_side == "required" && body.server_side !== "required"){
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} catch (error) { //DeEarthPublic
try {
if (JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)).isClient) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
} catch (error) { //mods.toml或fabric.mod.json判断
try{
if(zipinfo.modinfo.type === "forge"){
const mcside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "minecraft").side //Minecraft
if (mcside == "CLIENT") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
const forgeside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "forge").side //Forge
if (forgeside == "CLIENT") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
const neoside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "neoforge").side //NeoForge
if (neoside == "CLIENT") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}else if(zipinfo.modinfo.type === "fabric"){ //Fabric
const fmj = zipinfo.modinfo.data.environment
if (fmj == "client") {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
}catch(error){
try{
for (let i = 0; i < zipinfo.mixins.length; i++) {
const e = zipinfo.mixins[i]
const info = JSON.parse(e.info)
if (isMixin(info)) {
fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`)
}
}
}catch(error){
LOGGER.error(`大天才JSON写注释了估计模组路径:${modpath},过滤失败`)
}
}
}
}
}
function ZipInfo(modpath: string) {
interface ZipInfo {
mixins: { filename: string, info: string }[]
modinfo: {type:string,data:any};
}
let zipinfo: ZipInfo = { mixins: [], modinfo: {type: "",data: {}} }
const zip = new AdmZip(modpath).getEntries();
for (let i = 0; i < zip.length; i++) {
const e = zip[i]
if (isMixinFile(e.entryName)) {
zipinfo.mixins.push({ filename: e.entryName, info: e.getData().toString('utf-8') })
} else if (isForge(e.entryName)) {
zipinfo.modinfo.type = "forge"
zipinfo.modinfo.data = toml.parse(e.getData().toString('utf-8'))
} else if (e.entryName.endsWith("fabric.mod.json")) {
zipinfo.modinfo.type = "fabric"
zipinfo.modinfo.data = JSON.parse(e.getData().toString('utf-8'))
}
}
return zipinfo;
}
async function FastGot(url: string) {
let e = []
e.push([url])
const fastgot = await pMap(e, async (e: any[]) => {
try {
if (e[0] !== null) { //防止URL为空
//if(isChinaIpAddress((await got.get("https://4.ipw.cn/")).body)){
return (await got.get(e[0], { headers: { "User-Agent": "DeEarth" } })).body
//}else{
//return (await got.get(`https://mod.mcimirror.top/modrinth/${new URL(e[0]).pathname}`, { headers: { "User-Agent": "DeEarth" } })).body //MCIM源
//}
}
} catch (error: any) {
if (error.message !== "Response code 404 (Not Found)") {
LOGGER.error({ err: error })
} else {
throw new Error(error)
}
}
}, {
concurrency: 48
})
if (fastgot[0] !== undefined) {
return fastgot[0]
} else {
return "null"
}
}
const multibar = new MultiBar({
format: ' {bar} | {filename} | {value}/{total}',
noTTYOutput: true,
notTTYSchedule: 10 * 1000,
})
function isMixin(resx: { mixins: {} | null; client: {}; }) {
return resx.mixins == null || Object.keys(resx.mixins).length == 0 && Object.keys(resx.client).length !== 0
}
function isForge(name: string): boolean {
return name.endsWith("mods.toml") || name.endsWith("neoforge.mod.toml")
}
function isMixinFile(name: string): boolean {
return !name.includes("/") && name.endsWith(".json") && !name.endsWith("refmap.json") && !name.endsWith("mod.json")
}
function isChinaIpAddress(ipAddress: string) {
const chinaRegex = /^((?:(?:1(?:0|1|2[0-7]|[3-9][0-9])|2(?:[0-4][0-9]|5[0-5])|[3-9][0-9]{2})\.){3}(?:(?:1(?:0|1|2[0-7]|[3-9][0-9])|2(?:[0-4][0-9]|5[0-5])|[3-9][0-9]{2})))$/;
return chinaRegex.test(ipAddress);
}