From 35c2b35d2116728e63ca565be5d9baffc4146a39 Mon Sep 17 00:00:00 2001 From: OptiJava Date: Sun, 10 Dec 2023 12:34:36 +0800 Subject: [PATCH 1/9] feat(examples): add player_connect_err_printer --- README.md | 2 ++ examples/README.md | 5 +++++ .../player_connect_err_printer.yaml | 6 ++++++ examples/player_connect_err_printer/script.py | 11 +++++++++++ 4 files changed, 24 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/player_connect_err_printer/player_connect_err_printer.yaml create mode 100644 examples/player_connect_err_printer/script.py diff --git a/README.md b/README.md index e78eff9..ae55db4 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,8 @@ def on_mcdr_start(server: PluginServerInterface): ## 实例 +> 所有示例代码都在`examples`目录中 + ### 1. 玩家非正常退出报告 首先编写python代码: diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..9c80165 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +# Examples + +这里有一些示例脚本,每一个文件夹是一整个脚本。如需使用,须将整个文件夹复制到`config/hooks/scripts`中,并执行`!!hooks reload` + +There are some example scripts. If you want to try them, you must copy the full folder to `config/hooks/scripts` and execute `!!hooks reload` \ No newline at end of file diff --git a/examples/player_connect_err_printer/player_connect_err_printer.yaml b/examples/player_connect_err_printer/player_connect_err_printer.yaml new file mode 100644 index 0000000..a3c83eb --- /dev/null +++ b/examples/player_connect_err_printer/player_connect_err_printer.yaml @@ -0,0 +1,6 @@ +tasks: + - name: player_connect_err_printer + task_type: python_code + command_file: '{hooks_config_path}/scripts/player_connect_err_printer/script.py' + hooks: + - on_info \ No newline at end of file diff --git a/examples/player_connect_err_printer/script.py b/examples/player_connect_err_printer/script.py new file mode 100644 index 0000000..0d82cb7 --- /dev/null +++ b/examples/player_connect_err_printer/script.py @@ -0,0 +1,11 @@ +from mcdreforged.api.all import * + +# hooks在执行本脚本时会自动提前声明info、server这几个实例,所以你可以忽略IDE提示的"未解析的引用",本脚本在实际执行时是没有问题的 +# 使用/开发之前一定要仔细阅读仓库根路径下的README.md !!!!!! +if info.content.__contains__('lost connection: ') \ + and not info.content.endswith('Disconnected') \ + and not info.content.endswith('Killed'): + server.tell('@a', RTextList( + RText('检测到玩家非正常退出:', color=RColor.red), + RText(info.content, color=RColor.yellow).c(RAction.copy_to_clipboard, info.content).h('点击复制到剪贴板'), + )) \ No newline at end of file From e89af2f8fdd9b3ea7a131b71c87654d0a0c013c5 Mon Sep 17 00:00:00 2001 From: OptiJava Date: Wed, 7 Feb 2024 09:39:07 +0800 Subject: [PATCH 2/9] fix(script-load): will not use deprecated method `yaml.load(...)` --- hooks/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/__init__.py b/hooks/__init__.py index c8d5d71..e4f9ba6 100755 --- a/hooks/__init__.py +++ b/hooks/__init__.py @@ -173,8 +173,9 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): try: # 读取 + _yml = yaml.YAML() with open(cfg.temp_config.scripts_list.get(script), 'r') as f: - content: dict[str, Union[str, Union[list, dict]]] = yaml.load(f.read(), Loader=yaml.Loader) + content: dict[str, Union[str, Union[list, dict]]] = _yml.load(f) # yaml.load(f.read(), Loader=yaml.Loader) if content is not None: if content.get('tasks') is not None: From 54b18025e3912048084746ded3286f0effcc3d41 Mon Sep 17 00:00:00 2001 From: OptiJava Date: Wed, 7 Feb 2024 09:40:18 +0800 Subject: [PATCH 3/9] chore: .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 05156b6..b273ebd 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .idea/ -debug.sh hooks/__pycache__/ test/.pytest_cache/ From 739222e59a71de98b032718f73deec068563953b Mon Sep 17 00:00:00 2001 From: OptiJava Date: Wed, 7 Feb 2024 09:46:52 +0800 Subject: [PATCH 4/9] chore: v2.1.2 --- mcdreforged.plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcdreforged.plugin.json b/mcdreforged.plugin.json index 50b8289..8fcb335 100755 --- a/mcdreforged.plugin.json +++ b/mcdreforged.plugin.json @@ -1,6 +1,6 @@ { "id": "hooks", - "version": "2.1.1", + "version": "2.1.2", "name": "hooks", "description": { "en_us": "Allow MCDR to trigger custom scripts under specific conditions.", From c5c9859420cb4fd33aaf7590616415231495b8be Mon Sep 17 00:00:00 2001 From: OptiJava Date: Thu, 27 Jun 2024 10:15:57 +0800 Subject: [PATCH 5/9] fix: python 3.8 --- hooks/__init__.py | 14 +++++++------- hooks/config.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hooks/__init__.py b/hooks/__init__.py index e4f9ba6..e3dce1c 100755 --- a/hooks/__init__.py +++ b/hooks/__init__.py @@ -1,6 +1,6 @@ import json import os -from typing import Union, Any +from typing import Union, Any, Dict, List from ruamel import yaml @@ -17,7 +17,7 @@ scripts_folder: str = '' -def trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: dict[str, Any] = None): +def trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: Dict[str, Any] = None): if not cfg.config.automatically: return @@ -29,7 +29,7 @@ def trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict @new_thread('hooks - trigger') -def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: dict[str, Any] = None): +def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: Dict[str, Any] = None): logger.debug(f'Triggering hooks {hook.value}', server) # 初始化最终变量字典 @@ -129,7 +129,7 @@ def man_run_task(task: str, env_str: str, src: CommandSource, server: PluginServ return try: - env_dict: dict[str, str] = dict(json.loads(env_str)) + env_dict: Dict[str, str] = dict(json.loads(env_str)) except Exception as e: src.reply(RTextMCDRTranslation('hooks.man_run.illegal_env_json', e)) return @@ -175,7 +175,7 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): # 读取 _yml = yaml.YAML() with open(cfg.temp_config.scripts_list.get(script), 'r') as f: - content: dict[str, Union[str, Union[list, dict]]] = _yml.load(f) # yaml.load(f.read(), Loader=yaml.Loader) + content: Dict[str, Union[str, Union[list, dict]]] = _yml.load(f) # yaml.load(f.read(), Loader=yaml.Loader) if content is not None: if content.get('tasks') is not None: @@ -268,9 +268,9 @@ def load_scripts(server: PluginServerInterface): os.makedirs(scripts_folder) return - def list_all_files(root_dir) -> list[str]: + def list_all_files(root_dir) -> List[str]: # 显示一个文件夹及子文件夹中的所有yaml文件 - _files_in_a_folder: list[str] = [] + _files_in_a_folder: List[str] = [] for file in os.listdir(root_dir): file_path = os.path.join(root_dir, file) diff --git a/hooks/config.py b/hooks/config.py index 6914b14..ba496c7 100644 --- a/hooks/config.py +++ b/hooks/config.py @@ -1,4 +1,4 @@ -from typing import List, Any +from typing import List, Any, Dict from mcdreforged.api.all import Serializable @@ -37,11 +37,11 @@ def __init__(self): self.task = {} self.scripts_list = {} - hooks: dict[str, List[str]] + hooks: Dict[str, List[str]] - task: dict[str, Any] + task: Dict[str, Any] - scripts_list: dict[str, str] + scripts_list: Dict[str, str] schedule_daemon_threads: list = list() From ed9865c65e02d5bb98220aaeea9bd2ed4a7f9a00 Mon Sep 17 00:00:00 2001 From: OptiJava Date: Fri, 28 Jun 2024 07:59:38 +0800 Subject: [PATCH 6/9] doc: README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae55db4..8b4ffa6 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ tasks: # 普通任务 hooks: # 要挂载到的hook,必须是数组 - on_server_started - on_mcdr_started - schedule_tasks: # 定时任务声明 +schedule_tasks: # 定时任务声明 - name: ababababa # 名字 task_type: server_command # 任务类型 command: say 6 # 指令 From 08008613f2fcea0b38b6e1ff07bc7e38276599b7 Mon Sep 17 00:00:00 2001 From: OptiJava Date: Sat, 1 Feb 2025 11:04:51 +0800 Subject: [PATCH 7/9] fix: no vars on `run_command` (part) --- hooks/__init__.py | 101 +++++++++++++++++++++++++--------------------- hooks/tasks.py | 97 +++++++++++++++++++++++--------------------- 2 files changed, 105 insertions(+), 93 deletions(-) diff --git a/hooks/__init__.py b/hooks/__init__.py index e3dce1c..09c8f13 100755 --- a/hooks/__init__.py +++ b/hooks/__init__.py @@ -20,7 +20,7 @@ def trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: Dict[str, Any] = None): if not cfg.config.automatically: return - + try: if len(cfg.temp_config.hooks.get(hook.value)) != 0: _trigger_hooks(hook, server, objects_dict) @@ -28,13 +28,10 @@ def trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict server.logger.exception(f'Unexpected exception when triggering hook {hook.value}', e) -@new_thread('hooks - trigger') -def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: Dict[str, Any] = None): - logger.debug(f'Triggering hooks {hook.value}', server) - +def process_objects(objects_dict: Dict[str, Any] = None) -> Dict: # 初始化最终变量字典 finally_var_dict = dict() - + if (objects_dict is not None) and (len(objects_dict.keys()) != 0): # 遍历所有已知对象 for an_object_key in objects_dict.keys(): @@ -46,14 +43,22 @@ def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dic if (not hasattr(var_inner_attr_dict, 'keys')) or (var_inner_attr_dict is None): finally_var_dict[an_object_key] = an_object_value continue - + # 正在遍历的对象的属性字典中的正在遍历的key for var_inner_attr_key in var_inner_attr_dict.keys(): # 正在遍历的对象的属性字典中的正在遍历的key的value var_inner_attr_value: Any = var_inner_attr_dict.get(var_inner_attr_key) # 整合进入finally_var_dict finally_var_dict[an_object_key + '_' + var_inner_attr_key] = var_inner_attr_value - + return finally_var_dict + + +@new_thread('hooks - trigger') +def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dict: Dict[str, Any] = None): + logger.debug(f'Triggering hooks {hook.value}', server) + + finally_var_dict = process_objects(objects_dict) + # 遍历被挂载到此hook的task的key for task in cfg.temp_config.hooks.get(hook.value): if cfg.temp_config.task.get(task) is None: @@ -74,12 +79,12 @@ def _trigger_hooks(hook: mount.Hooks, server: PluginServerInterface, objects_dic @new_thread('hooks - list') def list_task(src: CommandSource): rtext_list = RTextList() - + if len(cfg.temp_config.task.values()) == 0: rtext_list.append(RText('Nothing', color=RColor.dark_gray, styles=RStyle.italic)) src.reply(RTextMCDRTranslation('hooks.list.task', rtext_list)) return - + for t in cfg.temp_config.task.values(): rtext_list.append(RTextList( RText('\n '), @@ -92,32 +97,32 @@ def list_task(src: CommandSource): @new_thread('hooks - list') def list_mount(src: CommandSource): list_hooks: list = list() - + for hk in dict(mount.Hooks.__members__).keys(): list_hooks.append(str(cfg.temp_config.hooks.get(str(hk)))) - + src.reply(RTextMCDRTranslation('hooks.list.mount', *list_hooks)) @new_thread('hooks - list') def list_scripts(src: CommandSource): rtext_list = RTextList() - + for scr in cfg.temp_config.scripts_list.keys(): rtext_list.append(RText(scr + ' ', color=RColor.red).h(cfg.temp_config.scripts_list.get(scr))) - + if rtext_list.is_empty(): rtext_list.append(RText('Nothing', color=RColor.dark_gray, styles=RStyle.italic)) - + src.reply(RTextMCDRTranslation('hooks.list.script', rtext_list)) def reload_config(src: CommandSource, server: PluginServerInterface): schedule_tasks.stop_all_schedule_daemon_threads(server) - + cfg.temp_config = cfg.TempConfig() cfg.config = server.load_config_simple(target_class=cfg.Configuration) - + load_scripts(server) server.logger.info('Config reloaded.') src.reply(RTextMCDRTranslation('hooks.reload.success')) @@ -127,13 +132,13 @@ def man_run_task(task: str, env_str: str, src: CommandSource, server: PluginServ if task not in cfg.temp_config.task.keys(): src.reply(RTextMCDRTranslation('hooks.man_run.task_not_exist')) return - + try: env_dict: Dict[str, str] = dict(json.loads(env_str)) except Exception as e: src.reply(RTextMCDRTranslation('hooks.man_run.illegal_env_json', e)) return - + try: cfg.temp_config.task.get(task).execute_task(server, mount.Hooks.undefined.value, var_dict=env_dict, obj_dict=env_dict) @@ -148,7 +153,7 @@ def man_run_task(task: str, env_str: str, src: CommandSource, server: PluginServ def clear_tasks(server: PluginServerInterface, src: CommandSource): for tsk in cfg.temp_config.task.copy().keys(): tasks.delete_task(tsk, src, server) - + @new_thread('hooks - run_command') def run_command(command: str, task_type: str, server: PluginServerInterface, src: CommandSource): @@ -157,7 +162,9 @@ def run_command(command: str, task_type: str, server: PluginServerInterface, src except ValueError: src.reply(RTextMCDRTranslation('hooks.create.task_type_wrong', task_type)) return - + + ## TODO + if task_type_var1 == tasks.TaskType.shell_command: os.system(command) elif task_type_var1 == tasks.TaskType.server_command: @@ -170,22 +177,22 @@ def run_command(command: str, task_type: str, server: PluginServerInterface, src def _parse_and_apply_scripts(script: str, server: PluginServerInterface): logger.debug(f'Prepare for apply script: {script}', server) - + try: # 读取 _yml = yaml.YAML() with open(cfg.temp_config.scripts_list.get(script), 'r') as f: content: Dict[str, Union[str, Union[list, dict]]] = _yml.load(f) # yaml.load(f.read(), Loader=yaml.Loader) - + if content is not None: if content.get('tasks') is not None: for task in content.get('tasks'): use_cmd_file: bool = False cmd_file_path: str = '' - + if task.get('command_file') is not None: var1 = str(task.get('command_file')).replace('{hooks_config_path}', server.get_data_folder()) - + if os.path.isfile(var1): cmd_file_path = var1 use_cmd_file = True @@ -193,7 +200,7 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): server.logger.warning( f'Script path for task {task.get("name")} is invalid, use command instead! ' f'{task.get("command_file")}') - + if use_cmd_file: # 读取 with open(cmd_file_path, 'r') as command_file: @@ -207,25 +214,25 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): tasks.create_task(task.get('task_type'), task.get('command'), task.get('name'), server.get_plugin_command_source(), server, created_by=script) - + if task.get('hooks') is None: continue for hook in task.get('hooks'): # 挂载 mount.mount_task(hook, task.get('name'), server.get_plugin_command_source(), server) - + if content.get('schedule_tasks') is not None: for schedule in content.get('schedule_tasks'): use_cmd_file: bool = False cmd_file_path: str = '' - + if int(schedule.get('exec_interval')) <= 0: server.logger.warning(f'Invalid exec_interval in schedule task {schedule.get("name")}!') - + if schedule.get('command_file') is not None: var1 = str(schedule.get('command_file')).replace('{hooks_config_path}', server.get_data_folder()) - + if os.path.isfile(var1): cmd_file_path = var1 use_cmd_file = True @@ -233,7 +240,7 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): server.logger.warning( f'Script path for task {schedule.get("name")} is invalid, use command instead! ' f'{schedule.get("command_file")}') - + if use_cmd_file: with open(cmd_file_path, 'r') as command_file: command_file_content = command_file.read() @@ -248,7 +255,7 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): server.get_plugin_command_source(), server, created_by=script, is_schedule=True, exec_interval=schedule.get('exec_interval')) - + if schedule.get('hooks') is None: continue for hook in schedule.get('hooks'): @@ -262,19 +269,19 @@ def _parse_and_apply_scripts(script: str, server: PluginServerInterface): def load_scripts(server: PluginServerInterface): logger.debug('Loading scripts...', server) - + if not os.path.isdir(scripts_folder): # 创建脚本目录 os.makedirs(scripts_folder) return - + def list_all_files(root_dir) -> List[str]: # 显示一个文件夹及子文件夹中的所有yaml文件 _files_in_a_folder: List[str] = [] - + for file in os.listdir(root_dir): file_path = os.path.join(root_dir, file) - + if os.path.isdir(file_path): if file_path.endswith('_'): logger.debug('Ignored folder ' + str(file_path), server) @@ -284,14 +291,14 @@ def list_all_files(root_dir) -> List[str]: if os.path.isfile(file_path) and (file_path.endswith('.yaml') or file_path.endswith('.yml')): # 添加文件路径 _files_in_a_folder.append(file_path) - + return _files_in_a_folder - + # 遍历所有yaml文件 for script_path in list_all_files(scripts_folder): # key:文件名 value:文件路径 cfg.temp_config.scripts_list[os.path.basename(script_path)] = script_path - + # 遍历所有已成功注册的脚本 for script in cfg.temp_config.scripts_list.keys(): _parse_and_apply_scripts(script, server) @@ -299,18 +306,18 @@ def list_all_files(root_dir) -> List[str]: def on_load(server: PluginServerInterface, old_module): global scripts_folder - + cfg.temp_config = cfg.TempConfig() cfg.config = server.load_config_simple(target_class=cfg.Configuration) - + scripts_folder = os.path.join(server.get_data_folder(), 'scripts') load_scripts(server) - + if utils.is_windows(): server.logger.warning('!###################################################################################!') server.logger.warning('Some features of hooks plugin cannot be run on Windows, you have already been warned.') server.logger.warning('!###################################################################################!') - + server.register_command( Literal('!!hooks') .then( @@ -437,16 +444,16 @@ def on_load(server: PluginServerInterface, old_module): ) ) ) - + trigger_hooks(mount.Hooks.on_plugin_loaded, server, {'server': process_arg_server(server), 'old_module': old_module}) def on_unload(server: PluginServerInterface): schedule_tasks.stop_all_schedule_daemon_threads(server) - + trigger_hooks(mount.Hooks.on_plugin_unloaded, server, {'server': process_arg_server(server)}) - + server.save_config_simple(cfg.config) diff --git a/hooks/tasks.py b/hooks/tasks.py index 07c8f71..d33233c 100644 --- a/hooks/tasks.py +++ b/hooks/tasks.py @@ -2,6 +2,7 @@ import time from enum import Enum from io import StringIO +from typing import Dict from mcdreforged.api.all import CommandSource, PluginServerInterface, RTextMCDRTranslation, new_thread @@ -51,57 +52,61 @@ def execute_task(self, server: PluginServerInterface, hook: str, var_dict: dict f'Task state is not correct! Task: {self.task_name} Hooks: {hook} TaskType: {self.task_type} ' f'command: {self.command}') return - - # shell - if self.task_type == TaskType.shell_command: - # 生成参数 - command = StringIO() - - if var_dict is not None: - for key in var_dict.keys(): - command.write('export "') - command.write(str(key)) - command.write('"') - command.write('=') - command.write('"') - command.write(str(var_dict.get(key))) - command.write('" && ') - command.write(self.command) - - os.system(command.getvalue()) - # mc command - elif self.task_type == TaskType.server_command: - # 替换参数 - command = self.command - if var_dict is not None: - for key in var_dict.keys(): - command = command.replace('{$' + key + '}', str(var_dict.get(key))) - - server.execute(command) - # mcdr command - elif self.task_type == TaskType.mcdr_command: - # 替换参数 - command = self.command - if var_dict is not None: - for key in var_dict.keys(): - command = command.replace('{$' + key + '}', str(var_dict.get(key))) - - server.execute_command(command) - - # python code - elif self.task_type == TaskType.python_code: - if obj_dict is not None: - exec(self.command, {}, obj_dict) - else: - if var_dict is not None: - exec(self.command, {}, var_dict) - else: - exec(self.command, {}, locals()) + + execute_known_command(self.task_type, var_dict, obj_dict, self.command, server) logger.debug(f'Task finished, name: {self.task_name}, task_type: {self.task_type}, ' f'costs {time.time() - start_time} seconds.', server) +def execute_known_command(task_type: TaskType, var_dict: Dict, obj_dict: Dict, p_command: str, server: PluginServerInterface): + # shell + if task_type == TaskType.shell_command: + # 生成参数 + command = StringIO() + + if var_dict is not None: + for key in var_dict.keys(): + command.write('export "') + command.write(str(key)) + command.write('"') + command.write('=') + command.write('"') + command.write(str(var_dict.get(key))) + command.write('" && ') + command.write(p_command) + + os.system(command.getvalue()) + # mc command + elif task_type == TaskType.server_command: + # 替换参数 + command = p_command + if var_dict is not None: + for key in var_dict.keys(): + command = command.replace('{$' + key + '}', str(var_dict.get(key))) + + server.execute(command) + # mcdr command + elif task_type == TaskType.mcdr_command: + # 替换参数 + command = p_command + if var_dict is not None: + for key in var_dict.keys(): + command = command.replace('{$' + key + '}', str(var_dict.get(key))) + + server.execute_command(command) + + # python code + elif task_type == TaskType.python_code: + if obj_dict is not None: + exec(p_command, {}, obj_dict) + else: + if var_dict is not None: + exec(p_command, {}, var_dict) + else: + exec(p_command, {}, locals()) + + def create_task(task_type: str, command: str, name: str, src: CommandSource, server: PluginServerInterface, is_schedule=False, exec_interval=0, created_by=None): if name in cfg.temp_config.task: From 951afcafc8aeec4930c66595b772f8636c32fc4b Mon Sep 17 00:00:00 2001 From: Opti Java <106148777+OptiJava@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:07:55 +0800 Subject: [PATCH 8/9] docs: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b4ffa6..d2869f7 100644 --- a/README.md +++ b/README.md @@ -269,5 +269,5 @@ tasks: - on_info ``` -上传好脚本后,命令行输入`!!hooks reload`即可awa +上传好脚本后,命令行输入`!!hooks reload`即可 From 079767fa4af0e22fdbd734dd9e10c8626540bf9a Mon Sep 17 00:00:00 2001 From: Opti Java <106148777+OptiJava@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:09:06 +0800 Subject: [PATCH 9/9] ci: update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7691c1..bc0a775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,6 @@ jobs: run: | python -m mcdreforged pack -o ./build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: build/