找llm做了个python的我的世界mod下载器

90 4

因为我的世界这个游戏很神奇,每次一更新就得亲自把mod一个一个的下完,而mojang的更新也太会水版本号了,所以就想做个帮我扫描mod目录下的mod,然后帮我下载相应版本的python小程序。

天影大侠他满心壮志地查modrinth怎么用api下mod,他们是怎么识别mod,然后不小心问了问manus...

算了manus直接把我想要的给做了出来

自己再改些没用的,就发出来了。

大家试试

llm太神奇了,给我玩mc节省了好多时间

python做的,用之前先pip install asyncio aiohttp tqdm,仅在linux上测试

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 感谢llm的帮忙


# 引入一些东西
import os
import asyncio
import aiohttp
from tqdm import tqdm
import platform
import zipfile
import json
import hashlib

# 颜色
C_RED = "\033[91m"; 
C_GREEN = "\033[92m"; 
C_YELLOW = "\033[93m";
C_BLUE = "\033[94m"; 
C_CYAN = "\033[96m"; 
C_MAGENTA = "\033[95m";
C_RESET = "\033[0m"; 
C_BOLD = "\033[1m"

# print
def print_color(message, color ): print(f"{color}{message}{C_RESET}")
def print_title(message): print_color(f"\n{'='*10} {message} {'='*10}", f"{C_CYAN}{C_BOLD}")
def print_success(message): print_color(f"✓ {message}", C_GREEN)
def print_warning(message): print_color(f"⚠️ {message}", C_YELLOW)
def print_error(message): print_color(f"✗ {message}", C_RED)
def print_info(message): print_color(f"ℹ️ {message}", C_BLUE)
def print_local(message): print_color(f"Found: {message}", C_MAGENTA)

class ModUpdater:
    MODRINTH_API_URL = "https://api.modrinth.com/v2"
    USER_AGENT = "一个mod下载工具"
    PATHS_FILE = "mod_paths.json"
    TQDM_BAR_FORMAT = "{l_bar}{bar:20}| {n_fmt}/{total_fmt}"

    def __init__(self, mc_version, mod_loader, mod_folder ):
        self.mc_version = mc_version
        self.mod_loader = mod_loader
        self.mod_folder = mod_folder
        self.download_dir = f"{self.mc_version}-{self.mod_loader}"
        os.makedirs(self.download_dir, exist_ok=True)

    @staticmethod
    def load_paths():
        if not os.path.exists(ModUpdater.PATHS_FILE): return []
        try:
            with open(ModUpdater.PATHS_FILE, 'r', encoding='utf-8') as f: return json.load(f)
        except (json.JSONDecodeError, IOError): return []

    @staticmethod
    def save_paths(paths):
        try:
            with open(ModUpdater.PATHS_FILE, 'w', encoding='utf-8') as f: json.dump(paths, f, indent=2)
        except IOError as e: print_error(f"无法保存路径文件: {e}")

    def _calculate_sha1(self, filepath):
        sha1 = hashlib.sha1()
        with open(filepath, 'rb') as f:
            while chunk := f.read(8192):
                sha1.update(chunk)
        return sha1.hexdigest()

    async def _api_request(self, session, method, url, **kwargs):
        try:
            async with session.request(method, url, **kwargs) as resp:
                if resp.status == 200: return await resp.json()
        except aiohttp.ClientError: pass
        return None

    async def _get_project_info(self, session, project_id ):
        url = f"{self.MODRINTH_API_URL}/project/{project_id}"
        return await self._api_request(session, "GET", url)

    async def _download_file(self, session, version_id, pbar):
        try:
            url = f"{self.MODRINTH_API_URL}/version/{version_id}"
            version_data = await self._api_request(session, "GET", url)
            if not version_data: raise ValueError("无法获取版本信息")

            file_info = version_data["files"][0]
            download_url, name = file_info["url"], file_info["filename"]
            path = os.path.join(self.download_dir, name)

            async with session.get(download_url) as r:
                r.raise_for_status()
                with open(path, "wb") as f:
                    while True:
                        chunk = await r.content.read(8192)
                        if not chunk: break
                        f.write(chunk)
            pbar.update(1)
            return name, True
        except Exception:
            pbar.update(1)
            return None, False

    async def run(self):
        print_info(f"模组将被下载到: ./{self.download_dir}/")
        print_title("1. 扫描并识别本地模组")
        jar_files = [os.path.join(self.mod_folder, f) for f in os.listdir(self.mod_folder) if f.endswith(".jar")]
        hashes = [self._calculate_sha1(f) for f in tqdm(jar_files, desc="计算哈希", bar_format=self.TQDM_BAR_FORMAT, ascii=' -')]

        local_projects = {}
        async with aiohttp.ClientSession(headers={"User-Agent": self.USER_AGENT} ) as session:
            url = f"{self.MODRINTH_API_URL}/version_files"
            version_data = await self._api_request(session, "POST", url, json={"hashes": hashes, "algorithm": "sha1"})
            
            if not version_data:
                print_error("无法从 Modrinth API 获取任何模组信息。")
                return

            print_info("从你选择的mod文件夹中找到以下模组:")
            project_ids = {info['project_id'] for info in version_data.values()}
            project_infos = await asyncio.gather(*[self._get_project_info(session, pid) for pid in project_ids])
            
            for info in project_infos:
                if info:
                    local_projects[info['id']] = info
                    print_local(info['title'])

            unidentified_hashes = [h for h in hashes if h not in version_data]

            print_title(f"2. 下载 {len(local_projects)} 个mod")
            version_tasks = []
            for pid in local_projects.keys():
                url = f"{self.MODRINTH_API_URL}/project/{pid}/version"
                params = {"loaders": f'["{self.mod_loader}"]', "game_versions": f'["{self.mc_version}"]'}
                version_tasks.append(self._api_request(session, "GET", url, params=params))
            
            versions_results = await asyncio.gather(*version_tasks)
            
            download_tasks, failed_to_find_version = [], []
            project_ids_list = list(local_projects.keys())
            for i, versions in enumerate(versions_results):
                pid = project_ids_list[i]
                if versions:
                    download_tasks.append(versions[0]['id'])
                else:
                    failed_to_find_version.append(local_projects[pid]['title'])

            download_results = []
            if download_tasks:
                with tqdm(total=len(download_tasks), desc="下载中", bar_format=self.TQDM_BAR_FORMAT, ascii=' -') as pbar:
                    tasks_to_run = [self._download_file(session, vid, pbar) for vid in download_tasks]
                    download_results = await asyncio.gather(*tasks_to_run)

        print_title("3. 下载完成")
        successful = [name for name, success in download_results if success]
        failed_titles = [info['title'] for (pid, info), (name, success) in zip(local_projects.items(), download_results) if not success]
        failed_titles.extend(failed_to_find_version)

        print_success(f"成功下载: {len(successful)} 个")
        if failed_titles:
            print_error(f"下载失败或未找到兼容版本: {len(set(failed_titles))} 个")
            for mod in sorted(list(set(failed_titles))): print(f"  - {mod}")
        if unidentified_hashes:
            print_warning(f"无法识别 {len(unidentified_hashes)} 个 .jar 文件")

def get_user_mod_folder():
    paths = ModUpdater.load_paths()
    print(f"{C_YELLOW}请选择一个模组文件夹路径:{C_RESET}")
    for i, path in enumerate(paths): print(f"  {i}. {path}")
    
    new_path_index = len(paths)
    print(f"  {new_path_index}. 手动输入新路径")

    choice = -1
    while not (0 <= choice <= new_path_index):
        try: choice = int(input(f"{C_YELLOW}请输入您的选择 (0-{new_path_index}): {C_RESET}"))
        except ValueError: choice = -1

    if choice == new_path_index:
        new_path = input(f"{C_YELLOW}请输入新的模组文件夹路径: {C_RESET}")
        if os.path.isdir(new_path):
            if new_path not in paths:
                paths.append(new_path)
                ModUpdater.save_paths(paths)
            return new_path
        else:
            print_error(f"错误: 路径 '{new_path}' 不存在或不是一个文件夹。")
            return None
    else:
        return paths[choice]

async def main():
    print_title("一个mod下载工具")
    
    mod_folder = get_user_mod_folder()
    if not mod_folder: return

    mc_version = input(f"{C_YELLOW}请输入 Minecraft 版本 (例如 1.20.1): {C_RESET}")
    
    print(f"{C_YELLOW}请选择 Mod 加载器:{C_RESET}")
    loaders = ["fabric", "forge", "neoforge", "quilt"]
    for i, loader in enumerate(loaders, 1): print(f"  {i}. {loader.capitalize()}")
    
    choice = 0
    while not 1 <= choice <= len(loaders):
        try: choice = int(input(f"{C_YELLOW}请输入您的选择 (1-{len(loaders)}): {C_RESET}"))
        except ValueError: choice = 0
    mod_loader = loaders[choice - 1]

    updater = ModUpdater(mc_version, mod_loader, mod_folder)
    await updater.run()

if __name__ == "__main__":
    if platform.system() == "Windows":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print_warning("\n程序被中断。")
    finally:
        print_title("程序结束")

一个中二到骨子里的大侠。 博客 主页
最新回复 ( 4 )
  • 2
    0
    滚来滚去……~(~o ̄▽ ̄)~o 。。。滚来滚去……o~(_△_o~) ~。。。
  • 3
    0
    建议PCL2
  • 4
    0
    猫猫摸大鱼 建议PCL2
    我忘了pcl2能不能实现这个了,只是linux上没法用pcl2⚆_⚆
  • 5
    0
    Linux可以用HMCL
  • 游客
    6

    您需要登录后才可以回帖

    登录 注册

发新帖