diff --git a/PyBuild/win_build.bat b/PyBuild/win_build.bat index 44d6b98..57b074e 100644 --- a/PyBuild/win_build.bat +++ b/PyBuild/win_build.bat @@ -1,5 +1,5 @@ -@echo off -cd.. -pip install -r requirements.txt -pip install pyinstaller +@echo off +cd.. +pip install -r requirements.txt +pip install pyinstaller pyinstaller main.spec \ No newline at end of file diff --git a/README.md b/README.md index 3f6ff74..dbeb485 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,56 @@ -
- logo -

Feed The Forge

-
- -## Introduce -This is a simple tool to download modpacks from FTB without the need of the FTB Launcher. - -You can then import or drag this zip file into any curseforge compatible launcher. - -For example: HMCL, PCL2, Prism Launcher etc. - -## Usage -WIP - -## Develop and Build -### Requirements - -- **Python Version**: 3.8+ -- **Supported Operating Systems**: Windows 10 or later, macOS, Linux - -### Running from Source - -1. **Install Dependencies**: - - Open a terminal and run the following command to install required packages: - ```bash - pip install -r requirements.txt - ``` - -2. **Run**: - - After installing the dependencies, run `__main__.py` - -### Building Executable for Windows - -1. **Package as Executable**: - - To package the application as an executable for Windows, run the following script: - ```bash - cd PyBuild - win_build.bat - ``` - -2. **Locate the Executable**: - - The resulting `.exe` file will be located in the `dist` folder. - -3. **Additional Steps**: - - Copy the `feedtheforge/lang` folder to the same directory as the `.exe` file to ensure the application runs correctly. - -## LICENSE +
+ logo +

Feed The Forge

+
+ +## Introduce +This is a simple tool to download modpacks from FTB without the need of the FTB Launcher. + +You can then import or drag this zip file into any curseforge compatible launcher. + +For example: HMCL, PCL2, Prism Launcher etc. + +## Usage +WIP + +## Develop and Build +### Requirements +- **Git**: 3.8+ +- **Python Version**: 3.8+ +- **Supported Operating Systems**: Windows 10 or later, macOS, Linux + +### Running from Source + +1. **Install Dependencies**: + - Open a terminal and run the following command to install required packages: + ```bash + git clone https://github.com/Wulian233/FeedTheForge.git + cd FeedTheForge + pip install -r requirements.txt + ``` + +2. **Run**: + - After installing the dependencies, run `__main__.py` + +### Building Executable for Windows + +1. **Package as Executable**: + - To package the application as an executable for Windows, run the following script: + - Windows + ```bash + cd PyBuild + win_build.bat + ``` + - Unix + ```bash + cd PyBuild + unix_build.sh + ``` +2. **Locate the Executable**: + - The resulting `.exe` file will be located in the `dist` folder. + +3. **Additional Steps**: + - Copy the `feedtheforge/lang` folder to the `dist` directory + +## LICENSE [GNU General Public License v3.0](.LICENSE) \ No newline at end of file diff --git a/__main__.py b/__main__.py index f9357b0..beb431d 100644 --- a/__main__.py +++ b/__main__.py @@ -1,14 +1,14 @@ -if __name__ == "__main__": - import asyncio - import sys - from main import * - from feedtheforge.utils import pause - - py_version = sys.version_info - - if not (3, 8) < py_version < (3, 15): - print(lang.t("feedtheforge.main.unsupported_version", - cur=f"{py_version.major}.{py_version.minor}.{py_version.micro}")) - pause() - asyncio.run(main()) +if __name__ == "__main__": + import asyncio + import sys + from main import * + from feedtheforge.utils import pause + + py_version = sys.version_info + + if not (3, 8) < py_version < (3, 15): + print(lang.t("feedtheforge.main.unsupported_version", + cur=f"{py_version.major}.{py_version.minor}.{py_version.micro}")) + pause() + asyncio.run(main()) \ No newline at end of file diff --git a/feedtheforge/const.py b/feedtheforge/const.py index 916f0d7..aef4b47 100644 --- a/feedtheforge/const.py +++ b/feedtheforge/const.py @@ -1,42 +1,41 @@ -import locale -import os -import tempfile - -from feedtheforge.i18n import Locale - -# 自动切换语言 -if locale._getdefaultlocale()[0] == "zh_CN": - lang = Locale("zh_CN") -else: - lang = Locale("en_US") - -current_language = lang.get_language() - -cache_dir = os.path.join(tempfile.gettempdir(), "FeedTheForge") -packlist_path = os.path.join(cache_dir, "packlist.json") -modpack_path = os.path.join(cache_dir, "pack_files") - -patch = os.path.join(cache_dir, "patch.zip") -patch_folder = os.path.join(cache_dir, "patch") -i18nupdate_link = "https://mediafilez.forgecdn.net/files/5335/196/I18nUpdateMod-3.5.5-all.jar" -mod_path = os.path.join(modpack_path, "overrides", "mods") - -api_list = "https://api.modpacks.ch/public/modpack/all" -api_featured = "https://api.modpacks.ch/public/modpack/featured/20" -api_search = "https://api.modpacks.ch/public/modpack/search/20/detailed?platform=modpacksch&term=" - -# 全部汉化 key: FTB唯一包版本 vaule: 蓝奏云汉化下载链接 -# TODO 未来应单独做出一个json并联网更新,不宜写死。支持匹配id全部版本支持 -all_patch = { - # 100 StoneBlock 3 - "6498": "https://wulian233.lanzouj.com/iwAZ61xg3yib", - "6647": "https://wulian233.lanzouj.com/iwAZ61xg3yib", - "6967": "https://wulian233.lanzouj.com/iwAZ61xg3yib", - "11655": "https://wulian233.lanzouj.com/iwAZ61xg3yib", - # 115 Arcanum Institute - "11512": "https://vmhanhuazu.lanzouj.com/i8W7Y1nr83le", - # 122 Builders Paradise 2 - "11840": "https://wulian233.lanzouj.com/ib5G81wnrpwb", - "11937": "https://wulian233.lanzouj.com/ib5G81wnrpwb", - "12266": "https://wulian233.lanzouj.com/ib5G81wnrpwb" -} +import locale +import os +import tempfile + +from feedtheforge.i18n import Locale + +# 自动切换语言 +default_locale = locale.getdefaultlocale()[0] +lang = Locale("zh_CN" if default_locale == "zh_CN" else "en_US") +current_language = lang.get_language() + +# 缓存目录设置 +cache_dir = os.path.join(tempfile.gettempdir(), "FeedTheForge") +packlist_path = os.path.join(cache_dir, "packlist.json") +modpack_path = os.path.join(cache_dir, "pack_files") +patch = os.path.join(cache_dir, "patch.zip") +patch_folder = os.path.join(cache_dir, "patch") +mod_path = os.path.join(modpack_path, "overrides", "mods") + +I18NUPDATE_LINK = "https://mediafilez.forgecdn.net/files/5335/196/I18nUpdateMod-3.5.5-all.jar" + +# API链接 +API_LIST = "https://api.modpacks.ch/public/modpack/all" +API_FEATURED = "https://api.modpacks.ch/public/modpack/featured/20" +API_SEARCH = "https://api.modpacks.ch/public/modpack/search/20/detailed?platform=modpacksch&term=" + +# 全部汉化 key: FTB唯一包版本 vaule: 蓝奏云汉化下载链接 +# TODO 未来应单独做出一个json并联网更新,不宜写死。支持匹配id全部版本支持 +all_patch = { + # 100 StoneBlock 3 + "6498": "https://wulian233.lanzouj.com/iwAZ61xg3yib", + "6647": "https://wulian233.lanzouj.com/iwAZ61xg3yib", + "6967": "https://wulian233.lanzouj.com/iwAZ61xg3yib", + "11655": "https://wulian233.lanzouj.com/iwAZ61xg3yib", + # 115 Arcanum Institute + "11512": "https://vmhanhuazu.lanzouj.com/i8W7Y1nr83le", + # 122 Builders Paradise 2 + "11840": "https://wulian233.lanzouj.com/ib5G81wnrpwb", + "11937": "https://wulian233.lanzouj.com/ib5G81wnrpwb", + "12266": "https://wulian233.lanzouj.com/ib5G81wnrpwb" +} diff --git a/feedtheforge/i18n.py b/feedtheforge/i18n.py index 079e4e7..329ad01 100644 --- a/feedtheforge/i18n.py +++ b/feedtheforge/i18n.py @@ -1,40 +1,40 @@ -import json -from pathlib import Path -from string import Template - - -class Locale: - def __init__(self, lang: str): - self.lang = lang - self.path = Path(f"./feedtheforge/lang/{lang}.json") - self.data = {} - self.load() - - def __getitem__(self, key: str): - return self.data[key] - - def __contains__(self, key: str): - return key in self.data - - def load(self): - with open(self.path, "r", encoding="utf-8") as f: - d = f.read() - self.data = json.loads(d) - f.close() - - def get_string(self, key: str, failed_prompt): - n = self.data.get(key, None) - if n != None: - return n - if failed_prompt: - return str(key) + self.t("feedtheforge.i18n.failed") - return key - - def t(self, key: str, failed_prompt=True, *args, **kwargs): - localized = self.get_string(key, failed_prompt) - return Template(localized).safe_substitute(*args, **kwargs) - - def get_language(self): - return self.lang - +import json +from pathlib import Path +from string import Template + + +class Locale: + def __init__(self, lang: str): + self.lang = lang + self.path = Path(f"./feedtheforge/lang/{lang}.json") + self.data = {} + self.load() + + def __getitem__(self, key: str): + return self.data[key] + + def __contains__(self, key: str): + return key in self.data + + def load(self): + with open(self.path, "r", encoding="utf-8") as f: + d = f.read() + self.data = json.loads(d) + f.close() + + def get_string(self, key: str, failed_prompt): + n = self.data.get(key, None) + if n != None: + return n + if failed_prompt: + return str(key) + self.t("feedtheforge.i18n.failed") + return key + + def t(self, key: str, failed_prompt=True, *args, **kwargs): + localized = self.get_string(key, failed_prompt) + return Template(localized).safe_substitute(*args, **kwargs) + + def get_language(self): + return self.lang + lang = Locale("zh_CN") \ No newline at end of file diff --git a/feedtheforge/lang/en_US.json b/feedtheforge/lang/en_US.json index 632a947..9551956 100644 --- a/feedtheforge/lang/en_US.json +++ b/feedtheforge/lang/en_US.json @@ -1,32 +1,33 @@ -{ - "feedtheforge.start.title": "Press Up or Down to select, Enter to confirm:", - "feedtheforge.start.featured_modpack": "View Featured Modpacks", - "feedtheforge.start.featured_modpack_desc": "View and download the top 5 modpacks of the last 20 days", - "feedtheforge.start.search_modpack": "Search Modpacks", - "feedtheforge.start.search_modpack_desc": "Enter English keywords to search for modpacks, then select to download", - "feedtheforge.start.enter_id": "Enter Modpack ID", - "feedtheforge.start.enter_id_desc": "Directly enter the numeric ID of the modpack to download", - "feedtheforge.start.clean_temp": "Clean Cache", - "feedtheforge.start.clean_temp_desc": "Clear cached modpack information. The next startup will be slower", - "feedtheforge.start.exit": "Exit", - "feedtheforge.start.exit_desc": "Exit the tool and close the window", - - "feedtheforge.main.clean_temp": "Cache cleaned successfully, a total of $size KB was cleaned", - "feedtheforge.main.default_version": "Automatically selected the latest version $selected_version", - "feedtheforge.main.enter_id": "Please enter the ID of the modpack to download:", - "feedtheforge.main.enter_version": "Please enter the modpack version (leave blank for latest):", - "feedtheforge.main.getting_error": "Network error, failed to retrieve modpack list.", - "feedtheforge.main.getting_list": "Getting modpacks list...", - "feedtheforge.main.has_chinese_patch": "Don't show in English", - "feedtheforge.main.search_modpack": "Please enter keywords for the modpack:", - "feedtheforge.main.invalid_pack_id": "No corresponding modpack found. Please enter a valid modpack ID", - "feedtheforge.main.invalid_modpack_version": "No corresponding modpack version found. Please enter a valid version.", - "feedtheforge.main.unsupported_version": "This program requires Python 3.8+ to run. The current version is Python $cur .", - "feedtheforge.main.version_list": "Available versions for the current modpack: $version_list", - "feedtheforge.main.modpack_created": "Compression successful. A modpack named $modpack_name has been created. Please drag it into the launcher to install", - "feedtheforge.main.modpack_name": "Successfully selected $modpack_name", - "feedtheforge.main.zipping_modpack": "Download completed, creating modpack installation package.", - - "feedtheforge.main.pause": "Press Enter to exit", - "feedtheforge.i18n.failed": "Error: No corresponding localization string" +{ + "feedtheforge.start.title": "Press Up or Down to select, Enter to confirm:", + "feedtheforge.start.featured_modpack": "View Featured Modpacks", + "feedtheforge.start.featured_modpack_desc": "View and download the top 5 modpacks of the last 20 days", + "feedtheforge.start.search_modpack": "Search Modpacks", + "feedtheforge.start.search_modpack_desc": "Enter English keywords to search for modpacks, then select to download", + "feedtheforge.start.enter_id": "Enter Modpack ID", + "feedtheforge.start.enter_id_desc": "Directly enter the numeric ID of the modpack to download", + "feedtheforge.start.clean_temp": "Clean Cache", + "feedtheforge.start.clean_temp_desc": "Clear cached modpack information. The next startup will be slower", + "feedtheforge.start.exit": "Exit", + "feedtheforge.start.exit_desc": "Exit the tool and close the window", + + "feedtheforge.main.clean_temp": "Cache cleaned successfully, a total of $size KB was cleaned", + "feedtheforge.main.default_version": "Automatically selected the latest version $selected_version", + "feedtheforge.main.empty_search": "No keywords entered or search results are empty, no corresponding modpack available", + "feedtheforge.main.enter_id": "Please enter the ID of the modpack to download:", + "feedtheforge.main.enter_version": "Please enter the modpack version (leave blank for latest):", + "feedtheforge.main.getting_error": "Network error, failed to retrieve modpack list.", + "feedtheforge.main.getting_list": "Getting modpacks list...", + "feedtheforge.main.has_chinese_patch": "Don't show in English", + "feedtheforge.main.search_modpack": "Please enter keywords for the modpack:", + "feedtheforge.main.invalid_pack_id": "No corresponding modpack found. Please enter a valid modpack ID", + "feedtheforge.main.invalid_modpack_version": "No corresponding modpack version found. Please enter a valid version.", + "feedtheforge.main.unsupported_version": "This program requires Python 3.8+ to run. The current version is Python $cur .", + "feedtheforge.main.version_list": "Available versions for the current modpack: $version_list", + "feedtheforge.main.modpack_created": "Compression successful. A modpack named $modpack_name has been created. Please drag it into the launcher to install", + "feedtheforge.main.modpack_name": "Successfully selected $modpack_name", + "feedtheforge.main.zipping_modpack": "Download completed, creating modpack installation package.", + + "feedtheforge.main.pause": "Press Enter to exit", + "feedtheforge.i18n.failed": "Error: No corresponding localization string" } \ No newline at end of file diff --git a/feedtheforge/lang/zh_CN.json b/feedtheforge/lang/zh_CN.json index 9106f1c..213a4d8 100644 --- a/feedtheforge/lang/zh_CN.json +++ b/feedtheforge/lang/zh_CN.json @@ -1,32 +1,33 @@ -{ - "feedtheforge.start.title": "按 上 下 键 选 择 , 回 车 确 认 :", - "feedtheforge.start.featured_modpack": "查 看 热 门 整 合 包", - "feedtheforge.start.featured_modpack_desc": "查 看 当 前 近 20天 最 热 门 的 5个 整 合 包 并 选 择 下 载", - "feedtheforge.start.search_modpack": "搜 索 整 合 包", - "feedtheforge.start.search_modpack_desc": "输 入 英 文 关 键 词 搜 索 整 合 包 , 选 择 后 下 载", - "feedtheforge.start.enter_id": "输 入 整 合 包 id", - "feedtheforge.start.enter_id_desc":"直 接 输 入 整 合 包 对 应 的 数 字 id下 载", - "feedtheforge.start.clean_temp": "清 除 工 具 缓 存", - "feedtheforge.start.clean_temp_desc": "清 除 缓 存 的 整 合 包 信 息 , 下 次 启 动 会 变 慢", - "feedtheforge.start.exit": "退 出", - "feedtheforge.start.exit_desc": "退 出 工 具 , 关 闭 窗 口", - - "feedtheforge.main.clean_temp": "清理缓存成功,共清理了 $size kb", - "feedtheforge.main.default_version": "已自动选择最新的 $selected_version 版本", - "feedtheforge.main.enter_id": "请输入要下载的整合包id:", - "feedtheforge.main.enter_version": "请输入整合包版本(留空默认最新):", - "feedtheforge.main.getting_error": "网络错误,获取整合包列表失败。", - "feedtheforge.main.getting_list": "正在获取整合包列表", - "feedtheforge.main.has_chinese_patch": "本整合包有人工汉化补丁可用,是否自动下载并安装?输入Y安装:", - "feedtheforge.main.search_modpack": "请输入整合包英文关键词:", - "feedtheforge.main.invalid_pack_id": "没有对应的整合包。请输入正确的整合包id", - "feedtheforge.main.invalid_modpack_version": "没有对应的整合包版本。请输入一个正确的版本。", - "feedtheforge.main.unsupported_version": "该程序需要Python 3.8+运行,当前版本为Python $cur 。", - "feedtheforge.main.version_list": "当前整合包可下载版本:$version_list", - "feedtheforge.main.modpack_created": "压缩成功。已创建名为 $modpack_name 的整合包,请拖入启动器安装", - "feedtheforge.main.modpack_name": "成功选择了 $modpack_name", - "feedtheforge.main.zipping_modpack": "下载完成,正在压缩制作整合包安装包。", - - "feedtheforge.main.pause": "请按Enter键退出", - "feedtheforge.i18n.failed": "错误:没有对应的本地化字符串" +{ + "feedtheforge.start.title": "按 上 下 键 选 择 , 回 车 确 认 :", + "feedtheforge.start.featured_modpack": "查 看 热 门 整 合 包", + "feedtheforge.start.featured_modpack_desc": "查 看 当 前 近 20天 最 热 门 的 5个 整 合 包 并 选 择 下 载", + "feedtheforge.start.search_modpack": "搜 索 整 合 包", + "feedtheforge.start.search_modpack_desc": "输 入 英 文 关 键 词 搜 索 整 合 包 , 选 择 后 下 载", + "feedtheforge.start.enter_id": "输 入 整 合 包 id", + "feedtheforge.start.enter_id_desc":"直 接 输 入 整 合 包 对 应 的 数 字 id下 载", + "feedtheforge.start.clean_temp": "清 除 工 具 缓 存", + "feedtheforge.start.clean_temp_desc": "清 除 缓 存 的 整 合 包 信 息 , 下 次 启 动 会 变 慢", + "feedtheforge.start.exit": "退 出", + "feedtheforge.start.exit_desc": "退 出 工 具 , 关 闭 窗 口", + + "feedtheforge.main.clean_temp": "清理缓存成功,共清理了 $size kb", + "feedtheforge.main.default_version": "已自动选择最新的 $selected_version 版本", + "feedtheforge.main.empty_search": "", + "feedtheforge.main.enter_id": "请输入要下载的整合包id:", + "feedtheforge.main.enter_version": "请输入整合包版本(留未输入关键词或搜索结果为空,无对应整合包空默认最新):", + "feedtheforge.main.getting_error": "网络错误,获取整合包列表失败。", + "feedtheforge.main.getting_list": "正在获取整合包列表", + "feedtheforge.main.has_chinese_patch": "本整合包有人工汉化补丁可用,是否自动下载并安装?输入Y安装:", + "feedtheforge.main.search_modpack": "请输入整合包英文关键词:", + "feedtheforge.main.invalid_pack_id": "没有对应的整合包。请输入正确的整合包id", + "feedtheforge.main.invalid_modpack_version": "没有对应的整合包版本。请输入一个正确的版本。", + "feedtheforge.main.unsupported_version": "该程序需要Python 3.8+运行,当前版本为Python $cur 。", + "feedtheforge.main.version_list": "当前整合包可下载版本:$version_list", + "feedtheforge.main.modpack_created": "压缩成功。已创建名为 $modpack_name 的整合包,请拖入启动器安装", + "feedtheforge.main.modpack_name": "成功选择了 $modpack_name", + "feedtheforge.main.zipping_modpack": "下载完成,正在压缩制作整合包安装包。", + + "feedtheforge.main.pause": "请按Enter键退出", + "feedtheforge.i18n.failed": "错误:没有对应的本地化字符串" } \ No newline at end of file diff --git a/feedtheforge/utils.py b/feedtheforge/utils.py index 7d05826..f77cc78 100644 --- a/feedtheforge/utils.py +++ b/feedtheforge/utils.py @@ -5,6 +5,12 @@ from feedtheforge.const import * +async def download_file(session, url, output_path): + async with session.get(url) as response: + with open(output_path, "wb") as f: + while chunk := await response.content.read(1024): + f.write(chunk) + async def create_directory(path): """ 创建目录,如果目录不存在则创建 diff --git a/main.py b/main.py index bfb5502..f362693 100644 --- a/main.py +++ b/main.py @@ -1,255 +1,269 @@ -import aiohttp -import asyncio -import json -import os -import shutil -from pick import pick, Option - -from feedtheforge import utils -from feedtheforge.const import * - - -async def download_file(session, url, output_path): - async with session.get(url) as response: - with open(output_path, "wb") as f: - while chunk := await response.content.read(1024): - f.write(chunk) - - -async def download_files(session, files): - tasks = [] - for file_info in files: - file_path = file_info["path"][2:] - file_name = file_info["name"] - full_path = os.path.join(modpack_path, "overrides", file_path) - output_path = os.path.join(full_path, file_name) - - if not os.path.exists(output_path): - await utils.create_directory(full_path) - tasks.append(download_file(session, file_info["url"], output_path)) - - await asyncio.gather(*tasks) - - -async def load_modpack_data(modpack_id): - """异步加载整合包数据""" - modpack_id_path = os.path.join(cache_dir, f"pack-{modpack_id}.json") - url = f"https://api.modpacks.ch/public/modpack/{modpack_id}" - - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - data = await response.json() - - with open(modpack_id_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) - - return data - - -async def display_modpack_list(load_json): - """读取json并制作对应的选择菜单""" - options = [] - with open(load_json, "r", encoding="utf-8") as f: - data = json.load(f) - for modpack_id in data["packs"]: - modpack_data = await load_modpack_data(modpack_id) - modpack_name = modpack_data['name'] - options.append(Option(f"{modpack_name} (id: {modpack_id})", modpack_id)) - - title = lang.t("feedtheforge.start.title") - selected_modpack = pick(options, title, indicator="=>") - return selected_modpack[0].value - - -async def download_featured_modpack(): - featured_json = os.path.join(cache_dir, "featured_modpacks.json") - # 检查7天内有没有缓存,加快速度 - if not await utils.is_recent_file(featured_json): - async with aiohttp.ClientSession() as session: - await download_file(session, api_featured, featured_json) - - # 下载选择的整合包 - modpack_id = await display_modpack_list(featured_json) - await download_modpack(modpack_id) - - -async def search_modpack(): - search_json = os.path.join(cache_dir, "search_modpacks.json") - keyword = input(lang.t("feedtheforge.main.search_modpack")) - print("未完成,敬请期待") - - -async def apply_chinese_patch(lanzou_url): - """蓝奏云直链解析下载汉化并自动覆盖应用汉化""" - from feedtheforge.lanzou import LanzouDownloader - from zipfile import ZipFile - # 获取返回的json中downUrl的值为下载链接 - data = json.loads(LanzouDownloader().get_direct_link(lanzou_url)) - down_url = data.get("downUrl") - async with aiohttp.ClientSession() as session: - await download_file(session, down_url, patch) - - with ZipFile(patch, 'r') as zip_ref: - zip_ref.extractall(patch_folder) - os.remove(patch) - # 把汉化移动剪切到整合包临时目录完成汉化 - for root, _, files in os.walk(patch_folder): - for file in files: - patch_file = os.path.join(root, file) - relative_path = os.path.relpath(patch_file, patch_folder) - target_path = os.path.join(modpack_path, "overrides", relative_path) - os.makedirs(os.path.dirname(target_path), exist_ok=True) - shutil.move(patch_file, target_path) - shutil.rmtree(patch_folder) - - -async def download_modpack(modpack_id): - modpack_data = await load_modpack_data(modpack_id) - modpack_name = modpack_data["name"] - modpack_author = modpack_data["authors"][0]["name"] - versions = modpack_data["versions"] - version_list = [version["id"] for version in versions] - - print(lang.t("feedtheforge.main.modpack_name", modpack_name=modpack_name)) - print(lang.t("feedtheforge.main.version_list", version_list=version_list)) - selected_version = input(lang.t("feedtheforge.main.enter_version")) - # 输入为空且有版本可下载(更保险),取最新版本 - if not selected_version and version_list: - selected_version = str(max(version_list)) - print(lang.t("feedtheforge.main.default_version", selected_version=selected_version)) - # id无对应整合包或不是数字 - else: - print(lang.t("feedtheforge.main.invalid_modpack_version")) - utils.pause() - - - async with aiohttp.ClientSession() as session: - download_url = f"https://api.modpacks.ch/public/modpack/{modpack_id}/{selected_version}" - await download_file(session, download_url, os.path.join(cache_dir, "download.json")) - await prepare_modpack_files(modpack_name, modpack_author, selected_version, session) - - if current_language == "zh_CN": - async with aiohttp.ClientSession() as session: - # 切片[-27:]恰为模组文件名 - await download_file(session, i18nupdate_link, os.path.join(mod_path, i18nupdate_link[-27:])) - # 检查有无对应汉化 - if str(selected_version) in all_patch: - install = input(lang.t("feedtheforge.main.has_chinese_patch")) - if install.lower() == "y": - await apply_chinese_patch(all_patch[selected_version]) - - utils.zip_modpack(modpack_name) - - -async def prepare_modpack_files(modpack_name, modpack_author, modpack_version, session): - os.makedirs(modpack_path, exist_ok=True) - with open(os.path.join(cache_dir, "download.json"), "r", encoding="utf-8") as f: - data = json.load(f) - # 下面均为CurseForge整合包识别的固定格式 - mc_version = data["targets"][1]["version"] - modloader_name = data["targets"][0]["name"] - modloader_version = data["targets"][0]["version"] - - curse_files, non_curse_files = [], [] - for file_info in data["files"]: - if "curseforge" in file_info: - curse_files.append({ - "fileID": file_info["curseforge"]["file"], - "projectID": file_info["curseforge"]["project"], - "required": True - }) - else: - non_curse_files.append(file_info) - - modloader_id = f"{modloader_name}-{modloader_version}" - if modloader_name == "neoforge" and mc_version == "1.20.1": - modloader_id = f"{modloader_name}-{mc_version}-{modloader_version}" - - manifest_data = { - "author": modpack_author, - "files": curse_files, - "manifestType": "minecraftModpack", - "manifestVersion": 1, - "minecraft": { - "version": mc_version, - "modLoaders": [{"id": modloader_id, "primary": True}] - }, - "name": modpack_name, - "overrides": "overrides", - "version": modpack_version - } - - with open(os.path.join(modpack_path, "manifest.json"), "w", encoding="utf-8") as f: - json.dump(manifest_data, f, indent=4) - - # FIXME 生成 modlist.html 文件 - modlist_file = os.path.join(modpack_path, "modlist.html") - with open(modlist_file, "w", encoding="utf-8") as f: - f.write("\n") - - os.makedirs(os.path.join(modpack_path, "overrides"), exist_ok=True) - await download_files(session, non_curse_files) - - -async def fetch_modpack_list(): - print(lang.t("feedtheforge.main.getting_list")) - try: - async with aiohttp.ClientSession() as session: - async with session.get(api_list) as response: - if response.status == 200: - modpacks_data = await response.json() - with open(packlist_path, "w", encoding="utf-8") as f: - json.dump(modpacks_data, f, indent=4) - except OSError: - print(lang.t("feedtheforge.main.getting_error")) - utils.pause() - - # 从本地文件读取数据 - with open(packlist_path, "r", encoding="utf-8") as f: - modpacks_data = json.load(f) - - global all_pack_ids - all_pack_ids = [str(pack_id) for pack_id in modpacks_data.get("packs", [])] - print(all_pack_ids) - -async def main(): - if not os.path.exists(cache_dir): - os.makedirs(cache_dir) - - # 本地化中这里的字中间要有空格,不加空格VSCode终端正常,在cmd中字会重叠 - title = lang.t("feedtheforge.start.title") - options = [ - Option(lang.t("feedtheforge.start.featured_modpack"), - description=lang.t("feedtheforge.start.featured_modpack_desc")), - Option(lang.t("feedtheforge.start.search_modpack"), - description=lang.t("feedtheforge.start.search_modpack_desc")), - Option(lang.t("feedtheforge.start.enter_id"), - description=lang.t("feedtheforge.start.enter_id_desc")), - Option(lang.t("feedtheforge.start.clean_temp"), - description=lang.t("feedtheforge.start.clean_temp_desc")), - Option(lang.t("feedtheforge.start.exit"), - description=lang.t("feedtheforge.start.exit_desc")) - ] - - option, index = pick(options, title, indicator="=>") - - # 根据选择执行相应的操作 - if index == 0: - await download_featured_modpack() - elif index == 1: - await search_modpack() - utils.pause() - elif index == 2: - await fetch_modpack_list() - modpack_id = input(lang.t("feedtheforge.main.enter_id")) - if modpack_id not in all_pack_ids: - print(lang.t("feedtheforge.main.invalid_pack_id")) - return - await download_modpack(modpack_id) - elif index == 3: - utils.clean_temp() - utils.pause() +import aiohttp +import asyncio +import json +import os +import shutil +from pick import pick, Option + +from feedtheforge import utils +from feedtheforge.const import * + + +async def download_files(session, files): + tasks = [] + for file_info in files: + file_path = file_info["path"][2:] + file_name = file_info["name"] + full_path = os.path.join(modpack_path, "overrides", file_path) + output_path = os.path.join(full_path, file_name) + + if not os.path.exists(output_path): + await utils.create_directory(full_path) + tasks.append(utils.download_file(session, file_info["url"], output_path)) + + await asyncio.gather(*tasks) + + +async def load_modpack_data(modpack_id): + """异步加载整合包数据""" + modpack_id_path = os.path.join(cache_dir, f"pack-{modpack_id}.json") + url = f"https://api.modpacks.ch/public/modpack/{modpack_id}" + + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + data = await response.json() + + with open(modpack_id_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4) + + return data + + +async def display_modpack_list(load_json): + """读取json并制作对应的选择菜单""" + options = [] + with open(load_json, "r", encoding="utf-8") as f: + data = json.load(f) + for modpack in data["packs"]: + # 处理字典形式的数据,即搜索整合包 + if isinstance(modpack, dict): modpack_id = modpack['id'] + # 直接是ID的情况,即查看流行的整合包 + else: modpack_id = modpack + # 屏蔽模组加载器 + if modpack_id in [116, 104, 81, 105]: + continue + modpack_data = await load_modpack_data(modpack_id) + modpack_name = modpack_data['name'] + + options.append(Option(f"{modpack_name} (id: {modpack_id})", modpack_id)) + # 防止搜索结果为空 + if not options: + print(lang.t("feedtheforge.main.empty_search")) + utils.pause() + + title = lang.t("feedtheforge.start.title") + selected_modpack = pick(options, title, indicator="=>") + return selected_modpack[0].value + + +async def download_featured_modpack(): + featured_json = os.path.join(cache_dir, "featured_modpacks.json") + # 检查7天内有没有缓存,加快速度 + if not await utils.is_recent_file(featured_json): + async with aiohttp.ClientSession() as session: + await utils.download_file(session, API_FEATURED, featured_json) + + # 下载选择的整合包 + modpack_id = await display_modpack_list(featured_json) + await download_modpack(modpack_id) + + +async def search_modpack(): + import string + + search_json = os.path.join(cache_dir, "search_modpacks.json") + keyword = input(lang.t("feedtheforge.main.search_modpack")) + # 防止搜索为空或不是英文字母数字 + if not keyword or keyword not in string.printable: + print(lang.t("feedtheforge.main.empty_search")) + utils.pause() + + async with aiohttp.ClientSession() as session: + await utils.download_file(session, API_SEARCH + keyword, search_json) + # 下载选择的整合包 + modpack_id = await display_modpack_list(search_json) + await download_modpack(modpack_id) + +async def apply_chinese_patch(lanzou_url): + """蓝奏云直链解析下载汉化并自动覆盖应用汉化""" + from feedtheforge.lanzou import LanzouDownloader + from zipfile import ZipFile + # 获取返回的json中downUrl的值为下载链接 + data = json.loads(LanzouDownloader().get_direct_link(lanzou_url)) + down_url = data.get("downUrl") + async with aiohttp.ClientSession() as session: + await utils.download_file(session, down_url, patch) + + with ZipFile(patch, 'r') as zip_ref: + zip_ref.extractall(patch_folder) + os.remove(patch) + # 把汉化移动剪切到整合包临时目录完成汉化 + for root, _, files in os.walk(patch_folder): + for file in files: + patch_file = os.path.join(root, file) + relative_path = os.path.relpath(patch_file, patch_folder) + target_path = os.path.join(modpack_path, "overrides", relative_path) + os.makedirs(os.path.dirname(target_path), exist_ok=True) + shutil.move(patch_file, target_path) + shutil.rmtree(patch_folder) + + +async def download_modpack(modpack_id): + modpack_data = await load_modpack_data(modpack_id) + modpack_name = modpack_data["name"] + modpack_author = modpack_data["authors"][0]["name"] + versions = modpack_data["versions"] + version_list = [version["id"] for version in versions] + + print(lang.t("feedtheforge.main.modpack_name", modpack_name=modpack_name)) + print(lang.t("feedtheforge.main.version_list", version_list=version_list)) + selected_version = input(lang.t("feedtheforge.main.enter_version")) + # 输入为空且有版本可下载(更保险),取最新版本 + if not selected_version and version_list: + selected_version = str(max(int(version) for version in version_list)) + print(lang.t("feedtheforge.main.default_version", selected_version=selected_version)) + # id无对应整合包或不是数字 + else: + print(lang.t("feedtheforge.main.invalid_modpack_version")) + utils.pause() + + + async with aiohttp.ClientSession() as session: + download_url = f"https://api.modpacks.ch/public/modpack/{modpack_id}/{selected_version}" + await utils.download_file(session, download_url, os.path.join(cache_dir, "download.json")) + await prepare_modpack_files(modpack_name, modpack_author, selected_version, session) + + if current_language == "zh_CN": + async with aiohttp.ClientSession() as session: + # 切片[-27:]恰为模组文件名 + await utils.download_file(session, I18NUPDATE_LINK, os.path.join(mod_path, I18NUPDATE_LINK[-27:])) + # 检查有无对应汉化 + if str(selected_version) in all_patch: + install = input(lang.t("feedtheforge.main.has_chinese_patch")) + if install.lower() == "y": + await apply_chinese_patch(all_patch[selected_version]) + + utils.zip_modpack(modpack_name) + + +async def prepare_modpack_files(modpack_name, modpack_author, modpack_version, session): + os.makedirs(modpack_path, exist_ok=True) + with open(os.path.join(cache_dir, "download.json"), "r", encoding="utf-8") as f: + data = json.load(f) + # 下面均为CurseForge整合包识别的固定格式 + mc_version = data["targets"][1]["version"] + modloader_name = data["targets"][0]["name"] + modloader_version = data["targets"][0]["version"] + + curse_files, non_curse_files = [], [] + for file_info in data["files"]: + if "curseforge" in file_info: + curse_files.append({ + "fileID": file_info["curseforge"]["file"], + "projectID": file_info["curseforge"]["project"], + "required": True + }) + else: + non_curse_files.append(file_info) + + modloader_id = f"{modloader_name}-{modloader_version}" + if modloader_name == "neoforge" and mc_version == "1.20.1": + modloader_id = f"{modloader_name}-{mc_version}-{modloader_version}" + + manifest_data = { + "author": modpack_author, + "files": curse_files, + "manifestType": "minecraftModpack", + "manifestVersion": 1, + "minecraft": { + "version": mc_version, + "modLoaders": [{"id": modloader_id, "primary": True}] + }, + "name": modpack_name, + "overrides": "overrides", + "version": modpack_version + } + + with open(os.path.join(modpack_path, "manifest.json"), "w", encoding="utf-8") as f: + json.dump(manifest_data, f, indent=4) + + # FIXME 生成 modlist.html 文件 + modlist_file = os.path.join(modpack_path, "modlist.html") + with open(modlist_file, "w", encoding="utf-8") as f: + f.write("\n") + + os.makedirs(os.path.join(modpack_path, "overrides"), exist_ok=True) + await download_files(session, non_curse_files) + + +async def fetch_modpack_list(): + print(lang.t("feedtheforge.main.getting_list")) + try: + async with aiohttp.ClientSession() as session: + async with session.get(API_LIST) as response: + if response.status == 200: + modpacks_data = await response.json() + with open(packlist_path, "w", encoding="utf-8") as f: + json.dump(modpacks_data, f, indent=4) + except OSError: + print(lang.t("feedtheforge.main.getting_error")) + utils.pause() + + # 从本地文件读取数据 + with open(packlist_path, "r", encoding="utf-8") as f: + modpacks_data = json.load(f) + + global all_pack_ids + all_pack_ids = [str(pack_id) for pack_id in modpacks_data.get("packs", [])] + print(all_pack_ids) + +async def main(): + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + # 本地化中这里的字中间要有空格,不加空格VSCode终端正常,在cmd中字会重叠 + title = lang.t("feedtheforge.start.title") + options = [ + Option(lang.t("feedtheforge.start.featured_modpack"), + description=lang.t("feedtheforge.start.featured_modpack_desc")), + Option(lang.t("feedtheforge.start.search_modpack"), + description=lang.t("feedtheforge.start.search_modpack_desc")), + Option(lang.t("feedtheforge.start.enter_id"), + description=lang.t("feedtheforge.start.enter_id_desc")), + Option(lang.t("feedtheforge.start.clean_temp"), + description=lang.t("feedtheforge.start.clean_temp_desc")), + Option(lang.t("feedtheforge.start.exit"), + description=lang.t("feedtheforge.start.exit_desc")) + ] + + option, index = pick(options, title, indicator="=>") + + # 根据选择执行相应的操作 + if index == 0: + await download_featured_modpack() + elif index == 1: + await search_modpack() + elif index == 2: + await fetch_modpack_list() + modpack_id = input(lang.t("feedtheforge.main.enter_id")) + if modpack_id not in all_pack_ids: + print(lang.t("feedtheforge.main.invalid_pack_id")) + return + await download_modpack(modpack_id) + elif index == 3: + utils.clean_temp() + utils.pause() diff --git a/main.spec b/main.spec index 3df87a9..646dfaf 100644 --- a/main.spec +++ b/main.spec @@ -1,39 +1,39 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['__main__.py'], - pathex=[], - binaries=[], - datas=[], - hiddenimports=['aiohttp','pick'], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='FeedTheForge', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - icon=['PyBuild/icon.ico'], +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['__main__.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=['aiohttp','pick'], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='FeedTheForge', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['PyBuild/icon.ico'], ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 92a116b..fecb45d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -aiohttp +aiohttp pick \ No newline at end of file