Skip to content

Commit 19ee495

Browse files
committed
feat(MaaEnd): 重构自动代理任务配置管理功能
- 添加re、json5、shutil模块依赖 - 移除ScriptConfig.py中的重复函数定义,优化代码结构 - 重构MaaEnd配置文件处理逻辑,使用copytree替代原有的单例处理 - 更新任务字典数据结构,支持多层级任务状态管理 - 优化日志监控中任务完成状态的判断逻辑 - 添加i18n国际化配置加载功能 - 改进任务实例ID统一为'automas' - 增强错误处理机制,提升代码健壮性 - 优化配置文件路径验证逻辑
1 parent 7c75b96 commit 19ee495

4 files changed

Lines changed: 133 additions & 125 deletions

File tree

app/task/MaaEnd/AutoProxy.py

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
# Contact: DLmaster_361@163.com
2020

2121

22+
import re
2223
import uuid
2324
import json
25+
import json5
26+
import shutil
2427
import asyncio
2528
from pathlib import Path
2629
from datetime import datetime, timedelta
@@ -34,7 +37,6 @@
3437
from app.utils import get_logger, LogMonitor, ProcessManager, skland_sign_in
3538
from app.utils.constants import UTC4, UTC8, MAAEND_KILLPROC_TASK
3639
from .tools import login, push_notification, wait_and_focus_window
37-
from .ScriptConfig import CONFIG_FILE_NAME, _keep_single_instance, _replace_config_dir
3840

3941
logger = get_logger("MaaEnd 自动代理")
4042

@@ -135,7 +137,7 @@ async def main_task(self):
135137
logger.info(f"开始代理用户 {self.cur_user_uid}")
136138
self.cur_user_item.status = "运行"
137139

138-
self.task_dict: dict[str, bool] | None = None
140+
self.task_dict: dict[str, dict[str, bool]] | None = None
139141
self.unique_task: dict[str, str] = {}
140142

141143
if (
@@ -253,9 +255,7 @@ async def main_task(self):
253255
self.wait_event.clear()
254256
t = datetime.now()
255257
await self.maaend_process_manager.open_process(
256-
self.maaend_exe_path,
257-
"--log-mode=verbose",
258-
stdout=asyncio.subprocess.PIPE,
258+
self.maaend_exe_path, stdout=asyncio.subprocess.PIPE
259259
)
260260

261261
# 静默模式隐藏 MaaEnd 窗口
@@ -363,28 +363,26 @@ async def set_maaend(self, device_info: DeviceInfo | None) -> None:
363363
await self.maaend_process_manager.kill()
364364
await System.kill_process(self.maaend_exe_path)
365365

366-
source_config_path = None
367-
if self.cur_user_config.get("Info", "Mode") == "简洁":
368-
source_config_path = (
369-
Path.cwd() / f"data/{self.script_info.script_id}/Default/ConfigFile"
370-
)
371-
elif self.cur_user_config.get("Info", "Mode") == "详细":
372-
source_config_path = (
373-
Path.cwd()
374-
/ f"data/{self.script_info.script_id}/{self.cur_user_uid}/ConfigFile"
375-
)
376-
377-
if source_config_path is None:
378-
raise RuntimeError("未找到 MaaEnd 配置目录")
379-
380-
source_config_file = source_config_path / CONFIG_FILE_NAME
381-
if source_config_file.exists():
382-
_keep_single_instance(source_config_file)
383-
_replace_config_dir(source_config_path, self.maaend_set_path)
366+
# 基础配置内容
367+
maaend_config_path = (
368+
Path.cwd()
369+
/ f"data/{self.script_info.script_id}/{'Default' if self.cur_user_config.get('Info', 'Mode') == '简洁' else self.cur_user_uid}/ConfigFile"
370+
)
371+
shutil.rmtree(self.maaend_set_path, ignore_errors=True)
372+
shutil.copytree(maaend_config_path, self.maaend_set_path)
384373

385-
maaend_set, maaend_instance = _keep_single_instance(
386-
self.maaend_set_path / CONFIG_FILE_NAME
374+
# 初始化任务实例
375+
maaend_set = json.loads(
376+
(self.maaend_set_path / "mxu-MaaEnd.json").read_text(encoding="utf-8")
387377
)
378+
379+
# 获取任务项单例
380+
for instance in maaend_set["instances"]:
381+
if instance.get("id") == "automas":
382+
maaend_instance = instance
383+
break
384+
else:
385+
maaend_instance = {"id": "automas", "name": "AUTO-MAS", "tasks": []}
388386
maaend_tasks = maaend_instance["tasks"]
389387

390388
settings = maaend_set.get("settings")
@@ -393,7 +391,7 @@ async def set_maaend(self, device_info: DeviceInfo | None) -> None:
393391
maaend_set["settings"] = settings
394392

395393
# 直接运行任务
396-
settings["autoStartInstanceId"] = maaend_instance["id"]
394+
settings["autoStartInstanceId"] = "automas"
397395
settings["autoRunOnLaunch"] = True
398396

399397
# 模拟器相关配置
@@ -408,32 +406,47 @@ async def set_maaend(self, device_info: DeviceInfo | None) -> None:
408406
"adbDeviceName": (await MaaFWManager.convert_adb(device_info)).name
409407
}
410408

409+
# 加载 i18n 配置
410+
if settings["language"] == "system":
411+
settings["language"] = "zh-CN"
412+
maaend_i18n_raw = json.loads(
413+
(
414+
self.maaend_root_path
415+
/ f"locales/interface/{settings['language'].lower().replace('-', '_')}.json"
416+
).read_text(encoding="utf-8")
417+
)
418+
maaend_i18n = {}
419+
for task_definition_file in self.maaend_root_path.glob("tasks/*.json"):
420+
task_definition = json5.loads( # type: ignore
421+
task_definition_file.read_text(encoding="utf-8")
422+
)["task"][0]
423+
if task_definition["label"].startswith("$"):
424+
maaend_i18n[task_definition["name"]] = maaend_i18n_raw[
425+
task_definition["label"].lstrip("$")
426+
]
427+
else:
428+
maaend_i18n[task_definition["name"]] = task_definition["label"]
429+
411430
# 配置任务启用状态
412431
if self.task_dict is None:
413432
# 任务列表为空则记录任务
414433
self.task_dict = {}
415434
task = {}
416435
for task in maaend_tasks:
417-
self.task_dict[task["id"]] = task["enabled"]
418-
if task.get("taskName") == "__MXU_KILLPROC__" and task.get(
419-
"optionValues", {}
420-
).get("__MXU_KILLPROC_SELF_OPTION__", {}).get("value", False):
421-
self.task_dict.popitem()
436+
if task.get("taskName") == "__MXU_KILLPROC__" and task.get(
437+
"optionValues", {}
438+
).get("__MXU_KILLPROC_SELF_OPTION__", {}).get("value", False):
439+
continue
440+
task_name = maaend_i18n.get(task["taskName"], task["taskName"])
441+
if task_name not in self.task_dict:
442+
self.task_dict[task_name] = {}
443+
self.task_dict[task_name][task["id"]] = task["enabled"]
422444
else:
423445
# 任务列表不为空则配置任务
424446
for task in maaend_tasks:
425-
task["enabled"] = self.task_dict[task["id"]]
426-
427-
# 记录启用的无重复任务项以便简化判定
428-
self.unique_task = {}
429-
duplicate_task = set()
430-
for task in maaend_tasks:
431-
if task["enabled"] and task["id"] in self.task_dict:
432-
if task["taskName"] in self.unique_task:
433-
self.unique_task.pop(task["taskName"])
434-
duplicate_task.add(task["taskName"])
435-
elif task["taskName"] not in duplicate_task:
436-
self.unique_task[task["taskName"]] = task["id"]
447+
task_name = maaend_i18n.get(task["taskName"], task["taskName"])
448+
if task_name in self.task_dict:
449+
task["enabled"] = self.task_dict[task_name][task["id"]]
437450

438451
# 配置协议空间
439452
for task in maaend_tasks:
@@ -480,16 +493,34 @@ async def check_log(self, log_content: list[str], latest_time: datetime) -> None
480493
if self.task_dict is None:
481494
self.cur_user_log.status = "MaaEnd 未加载任何任务"
482495
else:
483-
for id in self.task_dict.keys():
484-
if f"[{id}] 任务完成" in log:
485-
self.task_dict[id] = False
486-
for task_name, task_id in self.unique_task.items():
487-
if f"任务完成: {task_name}" in log:
488-
self.task_dict[task_id] = False
489-
if any(self.task_dict.values()):
490-
self.cur_user_log.status = "MaaEnd 部分任务执行失败"
491-
else:
492-
self.cur_user_log.status = "Success!"
496+
try:
497+
task_name = ""
498+
task_index = {
499+
k: {"index": 0, "list": list(v.keys())}
500+
for k, v in self.task_dict.items()
501+
}
502+
for log_line in self.cur_user_log.content:
503+
match = re.search(r"任务开始:\s*(.+)", log_line)
504+
task_name = match.group(1) if match else task_name
505+
if (
506+
task_name in self.task_dict
507+
and f"任务完成: {task_name}" in log_line
508+
):
509+
self.task_dict[task_name][
510+
task_index[task_name]["list"][
511+
task_index[task_name]["index"]
512+
]
513+
] = False
514+
task_index[task_name]["index"] += 1
515+
elif f"任务失败: {task_name}" in log_line:
516+
task_index[task_name]["index"] += 1
517+
518+
if any(any(_.values()) for _ in self.task_dict.values()):
519+
self.cur_user_log.status = "MaaEnd 部分任务执行失败"
520+
else:
521+
self.cur_user_log.status = "Success!"
522+
except:
523+
self.cur_user_log.status = "MaaEnd 任务执行情况解析失败"
493524

494525
elif datetime.now() - latest_time > timedelta(
495526
minutes=self.script_config.get("Run", "RunTimeLimit")

app/task/MaaEnd/ScriptConfig.py

Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import shutil
2424
import asyncio
2525
from pathlib import Path
26-
from typing import Any
26+
from copy import deepcopy
2727

2828
from app.core import Config
2929
from app.models.task import TaskExecuteBase, ScriptItem
@@ -34,65 +34,6 @@
3434
from app.utils import get_logger, ProcessManager
3535

3636
logger = get_logger("MaaEnd 脚本设置")
37-
CONFIG_FILE_NAME = "mxu-MaaEnd.json"
38-
39-
40-
def _load_config(config_path: Path) -> dict[str, Any]:
41-
return json.loads(config_path.read_text(encoding="utf-8"))
42-
43-
44-
def _dump_config(config_path: Path, data: dict[str, Any]) -> None:
45-
config_path.write_text(
46-
json.dumps(data, ensure_ascii=False, indent=4), encoding="utf-8"
47-
)
48-
49-
50-
def _keep_single_instance(config_path: Path) -> tuple[dict[str, Any], dict[str, Any]]:
51-
data = _load_config(config_path)
52-
53-
instances = data.get("instances", [])
54-
55-
selected_instance: dict[str, Any] | None = None
56-
57-
for instance in instances:
58-
if instance.get("name") == "AUTO-MAS":
59-
selected_instance = dict(instance)
60-
break
61-
62-
if selected_instance is None:
63-
active_id = str(data.get("lastActiveInstanceId", "")).strip()
64-
if active_id:
65-
for instance in instances:
66-
if str(instance.get("id", "")).strip() == active_id:
67-
selected_instance = dict(instance)
68-
break
69-
70-
if selected_instance is None:
71-
for instance in instances:
72-
selected_instance = dict(instance)
73-
break
74-
75-
if selected_instance is None:
76-
selected_instance = {"id": "", "name": "AUTO-MAS", "tasks": []}
77-
78-
if "tasks" not in selected_instance:
79-
selected_instance["tasks"] = []
80-
if not str(selected_instance.get("id", "")).strip():
81-
selected_instance["id"] = "AUTO-MAS"
82-
83-
selected_instance["name"] = "AUTO-MAS"
84-
data["instances"] = [selected_instance]
85-
data["lastActiveInstanceId"] = selected_instance["id"]
86-
87-
data.setdefault("settings", {})
88-
89-
_dump_config(config_path, data)
90-
return data, selected_instance
91-
92-
93-
def _replace_config_dir(source_dir: Path, target_dir: Path) -> None:
94-
shutil.rmtree(target_dir, ignore_errors=True)
95-
shutil.copytree(source_dir, target_dir)
9637

9738

9839
class ScriptConfigTask(TaskExecuteBase):
@@ -147,18 +88,44 @@ async def set_maaend(self):
14788
await self.maaend_process_manager.kill()
14889
await System.kill_process(self.maaend_exe_path)
14990

150-
if self.config_file_path.exists():
151-
config_path = self.config_file_path / CONFIG_FILE_NAME
152-
if config_path.exists():
153-
_keep_single_instance(config_path)
154-
_replace_config_dir(self.config_file_path, self.maaend_set_path)
155-
156-
maaend_set, maaend_instance = _keep_single_instance(
157-
self.maaend_set_path / CONFIG_FILE_NAME
91+
# 初始化任务实例
92+
maaend_set = json.loads(
93+
(self.maaend_set_path / "mxu-MaaEnd.json").read_text(encoding="utf-8")
94+
)
95+
maaend_instances = maaend_set["instances"]
96+
97+
selected_instance = None
98+
99+
# 创建任务项单例
100+
for instance in maaend_instances:
101+
if instance.get("id") == "automas":
102+
selected_instance = deepcopy(instance)
103+
break
104+
else:
105+
if maaend_set.get("lastActiveInstanceId"):
106+
for instance in maaend_instances:
107+
if instance.get("id") == maaend_set.get("lastActiveInstanceId"):
108+
selected_instance = deepcopy(instance)
109+
break
110+
else:
111+
selected_instance = (
112+
deepcopy(maaend_instances[0])
113+
if len(maaend_instances) > 0
114+
else {"id": "automas", "name": "AUTO-MAS", "tasks": []}
115+
)
116+
117+
selected_instance = dict(
118+
selected_instance or {"id": "automas", "name": "AUTO-MAS", "tasks": []}
158119
)
120+
if "tasks" not in selected_instance:
121+
selected_instance["tasks"] = []
122+
selected_instance["id"] = "automas"
123+
selected_instance["name"] = "AUTO-MAS"
124+
maaend_instances = [selected_instance]
125+
maaend_set["lastActiveInstanceId"] = "automas"
159126

160127
# 不直接运行任务
161-
maaend_set["settings"]["autoStartInstanceId"] = maaend_instance["id"]
128+
maaend_set["settings"]["autoStartInstanceId"] = "automas"
162129
maaend_set["settings"]["autoRunOnLaunch"] = False
163130

164131
(self.maaend_set_path / "mxu-MaaEnd.json").write_text(
@@ -173,7 +140,6 @@ async def final_task(self):
173140
await self.maaend_process_manager.kill()
174141
await System.kill_process(self.maaend_exe_path)
175142

176-
_keep_single_instance(self.maaend_set_path / CONFIG_FILE_NAME)
177143
shutil.rmtree(self.config_file_path, ignore_errors=True)
178144
shutil.copytree(self.maaend_set_path, self.config_file_path)
179145

app/task/MaaEnd/manager.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,23 @@ async def check(self) -> str:
8181
):
8282
return "未完成游戏配置, 请检查脚本配置中的游戏设置!"
8383

84+
if not (
85+
Path(
86+
Config.ScriptConfig[uuid.UUID(self.script_info.script_id)].get(
87+
"Info", "Path"
88+
)
89+
)
90+
/ "config/mxu-MaaEnd.json"
91+
).exists():
92+
return "MaaEnd 配置文件不存在, 请检查 MaaEnd 路径设置或先启动 MaaEnd 完成配置文件生成!"
93+
8494
if (
8595
self.task_info.mode != "ScriptConfig"
8696
and not (
8797
Path.cwd() / f"data/{self.script_info.script_id}/Default/ConfigFile"
8898
).exists()
8999
):
90-
return "MaaEnd default config is missing, please configure MaaEnd first."
100+
return "未完成 MaaEnd 全局设置, 请先设置 MaaEnd"
91101
return "Pass"
92102

93103
async def prepare(self):

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ numpy==2.3.4
2424
opencv-python==4.10.0.84
2525
maafw==5.8.1
2626
mss==9.0.2
27-
fastapi-mcp==0.4.0
27+
fastapi-mcp==0.4.0
28+
json5==0.14.0

0 commit comments

Comments
 (0)