diff --git a/tooldelta/extend_functions/__init__.py b/tooldelta/extend_functions/__init__.py index b27bc509..90f71ade 100644 --- a/tooldelta/extend_functions/__init__.py +++ b/tooldelta/extend_functions/__init__.py @@ -32,5 +32,11 @@ def activate_functions(): func.when_activate() +def restore_console_cmds(): + """恢复内置扩展提供的控制台命令。""" + for func in registered_functions: + func.when_console_cmd_reset() + + def import_functions(): - from . import gamerule_warnings, fast_plugin_download + from . import gamerule_warnings, fast_plugin_download, plugin_config diff --git a/tooldelta/extend_functions/basic.py b/tooldelta/extend_functions/basic.py index 1ec7eced..275f79f0 100644 --- a/tooldelta/extend_functions/basic.py +++ b/tooldelta/extend_functions/basic.py @@ -9,3 +9,7 @@ def __init__(self, frame: "ToolDelta"): self.frame = frame def when_activate(self): ... + + def when_console_cmd_reset(self): + """控制台命令重置后的扩展钩子。""" + return None diff --git a/tooldelta/extend_functions/fast_plugin_download.py b/tooldelta/extend_functions/fast_plugin_download.py index c953b74c..2e4c83ff 100644 --- a/tooldelta/extend_functions/fast_plugin_download.py +++ b/tooldelta/extend_functions/fast_plugin_download.py @@ -12,6 +12,10 @@ def __init__(self, frame): def when_activate(self): super().when_activate() + self.when_console_cmd_reset() + + def when_console_cmd_reset(self): + """注册快速下载插件的控制台命令。""" self.frame.add_console_cmd_trigger( ["plg add"], "插件 ID", diff --git a/tooldelta/extend_functions/plugin_config.py b/tooldelta/extend_functions/plugin_config.py new file mode 100644 index 00000000..b113e1d9 --- /dev/null +++ b/tooldelta/extend_functions/plugin_config.py @@ -0,0 +1,158 @@ +"""插件市场源和 GitHub 镜像的控制台配置命令。""" + +from urllib.parse import urlparse + +from ..constants import PLUGIN_MARKET_SOURCE_OFFICIAL +from ..constants.tooldelta_cfg import LAUNCH_CFG_STD +from ..plugin_market import market +from ..utils import cfg, fmts, urlmethod +from . import regist_extend_function +from .basic import ExtendFunction + + +CONFIG_PATH = "ToolDelta基本配置.json" +CONFIG_KEYS = { + "plugin_market": "插件市场源", + "github_mirror": "全局GitHub镜像", +} +CONFIG_KEYS_HINT = "plugin_market|github_mirror" + + +def _normalize_url(value: str) -> str: + """校验并规范化配置 URL。""" + if any(char.isspace() for char in value): + raise ValueError + parsed = urlparse(value) + if parsed.scheme not in ("http", "https") or not parsed.netloc: + raise ValueError + return value.rstrip("/") + + +@regist_extend_function +class PluginConfig(ExtendFunction): + """管理插件市场源和全局 GitHub 镜像。""" + + def when_activate(self): + """激活扩展并注册控制台命令。""" + super().when_activate() + self.when_console_cmd_reset() + + def when_console_cmd_reset(self): + """注册插件源配置命令。""" + self.frame.add_console_cmd_trigger( + ["plg config set"], + f"<{CONFIG_KEYS_HINT}> ", + "设置插件市场源或全局 GitHub 镜像", + self.on_set_config, + ) + self.frame.add_console_cmd_trigger( + ["plg config unset"], + f"<{CONFIG_KEYS_HINT}>", + "清除插件市场源或全局 GitHub 镜像的自定义值", + self.on_unset_config, + ) + self.frame.add_console_cmd_trigger( + ["plg config list"], + None, + "查看插件市场源和全局 GitHub 镜像", + self.on_list_config, + ) + + def on_set_config(self, args: list[str]) -> None: + """设置并立即应用指定配置。""" + if len(args) != 2: + self._print_usage("set") + return + key, value = args + if not self._check_key(key): + return + try: + value = _normalize_url(value) + except ValueError: + fmts.print_err("URL 必须是包含主机名的 http:// 或 https:// 地址") + return + if self._save_config(key, value): + self._apply_config(key, value) + fmts.print_suc(f"已设置 {key}: {value},当前进程已生效") + + def on_unset_config(self, args: list[str]) -> None: + """清除指定配置并恢复运行时默认值。""" + if len(args) != 1: + self._print_usage("unset") + return + key = args[0] + if not self._check_key(key): + return + if self._save_config(key, ""): + self._apply_config(key, "") + effective_value = self._get_effective_value(key) + fmts.print_suc(f"已清除 {key} 的自定义值,当前使用: {effective_value}") + + def on_list_config(self, args: list[str]) -> None: + """显示配置值及当前生效值。""" + if args: + self._print_usage("list") + return + configs = self._load_config() + if configs is None: + return + fmts.print_inf("插件源配置:") + for key, config_key in CONFIG_KEYS.items(): + configured_value = configs[config_key] or "自动" + effective_value = self._get_effective_value(key) + fmts.print_inf(f" {key}: 配置={configured_value},当前={effective_value}") + + @staticmethod + def _check_key(key: str) -> bool: + """检查命令使用的配置键。""" + if key in CONFIG_KEYS: + return True + fmts.print_err(f"未知配置项 {key!r},可用项: {', '.join(CONFIG_KEYS)}") + return False + + @staticmethod + def _load_config() -> dict | None: + """读取 ToolDelta 基本配置。""" + try: + return cfg.get_cfg(CONFIG_PATH, LAUNCH_CFG_STD) + except (OSError, cfg.ConfigError) as err: + fmts.print_err(f"读取 ToolDelta 基本配置失败: {err}") + return None + + def _save_config(self, key: str, value: str) -> bool: + """保留其他配置项并持久化指定值。""" + configs = self._load_config() + if configs is None: + return False + configs[CONFIG_KEYS[key]] = value + try: + cfg.write_default_cfg_file(CONFIG_PATH, configs, force=True) + except OSError as err: + fmts.print_err(f"保存 ToolDelta 基本配置失败: {err}") + return False + return True + + @staticmethod + def _apply_config(key: str, value: str) -> None: + """将配置应用到当前运行中的客户端。""" + if key == "plugin_market": + market.set_source_url(value or PLUGIN_MARKET_SOURCE_OFFICIAL) + return + urlmethod.set_global_github_src_url(value) + + @staticmethod + def _get_effective_value(key: str) -> str: + """获取当前进程实际使用的配置值。""" + if key == "plugin_market": + return market.plugin_market_content_url + return urlmethod.get_global_github_src_url() + + @staticmethod + def _print_usage(command: str) -> None: + """输出子命令的正确用法。""" + usages = { + "set": f"plg config set <{CONFIG_KEYS_HINT}> ", + "unset": f"plg config unset <{CONFIG_KEYS_HINT}>", + "list": "plg config list", + } + fmts.print_err(f"用法: {usages[command]}") diff --git a/tooldelta/frame.py b/tooldelta/frame.py index a0fcf6c5..1ff17018 100755 --- a/tooldelta/frame.py +++ b/tooldelta/frame.py @@ -169,6 +169,7 @@ def reload(self, deep_reload: bool = False): fmts.print_inf("重载: 正在保存数据缓存文件..") utils.safe_close(SysStatus.RELOAD) self.cmd_manager.reset_cmds() + extend_functions.restore_console_cmds() fmts.print_inf("重载: 正在重新载入插件..") self.plugin_group.reload(deep_reload) fmts.print_suc("重载插件: 全部插件重载成功!") diff --git a/tooldelta/plugin_market.py b/tooldelta/plugin_market.py index d12e0f62..3f8909d5 100755 --- a/tooldelta/plugin_market.py +++ b/tooldelta/plugin_market.py @@ -62,11 +62,21 @@ def __init__(self): self._cached_plugins_id_map = {} self._cached_market_filetree: FILETREE = {} try: - self.plugin_market_content_url = cfg.get_cfg( + source_url = cfg.get_cfg( "ToolDelta基本配置.json", {"插件市场源": str} )["插件市场源"] except Exception: - self.plugin_market_content_url = PLUGIN_MARKET_SOURCE_OFFICIAL + source_url = PLUGIN_MARKET_SOURCE_OFFICIAL + self.set_source_url(source_url) + + def set_source_url(self, source_url: str) -> None: + """切换插件市场源并清空已有缓存。""" + self.plugin_market_content_url = ( + source_url or PLUGIN_MARKET_SOURCE_OFFICIAL + ) + self._cached_market_tree.clear() + self._cached_plugins_id_map.clear() + self._cached_market_filetree.clear() def enter_plugin_market(self, source_url: str | None = None, in_game=False) -> None: """ @@ -77,7 +87,7 @@ def enter_plugin_market(self, source_url: str | None = None, in_game=False) -> N in_game (bool, optional): 是否在游戏内调用的插件市场命令 """ if source_url: - self.plugin_market_content_url = source_url + self.set_source_url(source_url) fmts.ansi_save_screen() fmts.clean_print("§6正在连接到插件市场..")