/* 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); }