Skip to content

Commit 2d819dd

Browse files
committed
chore: skeleton
1 parent beb7cd1 commit 2d819dd

21 files changed

Lines changed: 1021 additions & 115 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# dev
2+
MFAAvalonia/*
3+
14
# docs
25
docs/node_modules
36
docs/.vuepress/.temp
@@ -448,4 +451,4 @@ install
448451
tools/ImageCropper/**/*.png
449452

450453
config
451-
debug
454+
debug

.qoder/commands/comments.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
type: project_command
3+
description:
4+
---
5+
6+
对注释进行生成或规范化,使用中文,要点如下:
7+
8+
- 遵循 PEP 8 — Style Guide for Python Code(Python 官方代码风格指南)以及 PEP 257 — Docstring Conventions(文档字符串约定)
9+
- 如果文件太长,可以按逻辑分组,使用 `# ==================== 模块名 ====================` 格式的对称分隔符
10+
- 注释符号也使用中文

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
**请您不要在任何官方社区/评论区提及 MDDL!**
1010

11-
基于全新架构的 [**嘟嘟脸恶作剧**](https://xytx.firewick.net/home) 小助手<br/>图像技术 + 模拟控制,解放双手,由 [MaaFramework](https://github.com/MaaXYZ/MaaFramework)[MFAAvalonia](https://github.com/SweetSmellFox/MFAAvalonia) 强力驱动!
11+
基于全新架构的 [**嘟嘟脸恶作剧**](https://game.bilibili.com/trickcal/) 小助手<br/>图像技术 + 模拟控制,解放双手,由 [MaaFramework](https://github.com/MaaXYZ/MaaFramework)[MFAAvalonia](https://github.com/SweetSmellFox/MFAAvalonia) 强力驱动!
1212

1313
<p align="center">
1414
<a href="https://github.com/MaaXYZ/MaaFramework/blob/main/docs/zh_cn/3.1-%E4%BB%BB%E5%8A%A1%E6%B5%81%E6%B0%B4%E7%BA%BF%E5%8D%8F%E8%AE%AE.md" target="_blank"><img alt="pipeline" src="https://img.shields.io/badge/Pipeline-%23876f69?logo=paddypower&logoColor=%23FFFFFF"></a>

agent/customs/__init__.py

Whitespace-only changes.

agent/customs/utils/__init__.py

Whitespace-only changes.

agent/main.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""MaaDuDuL Agent 主入口。
2+
3+
负责初始化环境、检查依赖、启动 Agent 服务器。
4+
"""
5+
6+
import os
7+
import sys
8+
from pathlib import Path
9+
10+
# 将项目根目录添加到 Python 路径
11+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
12+
sys.path.insert(0, str(PROJECT_ROOT))
13+
14+
from agent.preprocess import check_and_install_dependencies
15+
16+
# 设置控制台编码
17+
sys.stdout.reconfigure(encoding="gbk")
18+
sys.stderr.reconfigure(encoding="gbk")
19+
20+
21+
def main():
22+
"""启动 MaaDuDuL Agent 服务"""
23+
from maa.agent.agent_server import AgentServer
24+
from maa.toolkit import Toolkit
25+
from agent.preprocess import clear
26+
import customs
27+
28+
try:
29+
# 清理调试文件
30+
clear()
31+
# 初始化 MaaFW 工具包
32+
Toolkit.init_option("./")
33+
# 获取 socket ID 并启动服务
34+
socket_id = sys.argv[-1]
35+
AgentServer.start_up(socket_id)
36+
AgentServer.join()
37+
AgentServer.shut_down()
38+
39+
except Exception as e:
40+
print(f"Agent 启动失败:{e}")
41+
sys.exit(1)
42+
43+
44+
if __name__ == "__main__":
45+
if not os.getenv("MDDL_DEV_MODE"):
46+
check_and_install_dependencies()
47+
main()

agent/preprocess/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .clear import clear
2+
from .report import punch_in
3+
from .setup import check_and_install_dependencies

agent/preprocess/clear.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""预处理清理模块。
2+
3+
本模块负责在任务执行前清理调试产生的临时文件。
4+
"""
5+
6+
import shutil
7+
from pathlib import Path
8+
9+
10+
def _clear_on_error_images():
11+
"""清理错误时产生的调试图片。
12+
13+
删除 debug/on_error 目录及其所有内容。
14+
该目录用于存储任务执行出错时的截图,清理可避免历史图片占用空间。
15+
"""
16+
# 获取项目根目录(当前文件的上两级目录)
17+
root = Path(__file__).resolve().parent.parent
18+
19+
# 构建调试图片文件夹路径
20+
on_error_folder = root / "debug" / "on_error"
21+
22+
# 如果文件夹存在则删除整个目录树
23+
if on_error_folder.exists():
24+
try:
25+
shutil.rmtree(on_error_folder)
26+
except Exception:
27+
# 删除失败时静默处理,避免中断预处理流程
28+
pass
29+
30+
31+
def clear():
32+
"""执行预处理清理操作。
33+
34+
该函数是清理模块的对外接口,负责调用所有内部清理函数。
35+
即使清理过程出错也不会中断程序,仅输出提示信息。
36+
"""
37+
try:
38+
_clear_on_error_images()
39+
except Exception as e:
40+
print(f"预处理时出现问题:\n{e}\n这可能不影响使用,但建议在交流群内反馈!")

agent/preprocess/report.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""数据上报模块。
2+
3+
该模块负责向远程服务器发送使用统计信息。
4+
"""
5+
6+
import requests
7+
8+
9+
def punch_in():
10+
"""发送打卡数据到远程服务器。
11+
12+
向指定的统计服务器发送项目使用信息,包括来源标识和版本号。
13+
14+
Returns:
15+
dict: 服务器返回的 JSON 响应数据。
16+
Exception: 当请求失败时返回异常对象。
17+
18+
Note:
19+
请求超时时间设置为 3 秒,失败时不会中断程序执行。
20+
"""
21+
try:
22+
# 发送 POST 请求到统计服务器
23+
response = requests.post(
24+
"http://ts.codax.site/repo",
25+
json={"from": "mddl", "version": "v0.0.1"},
26+
headers={"Content-Type": "application/json"},
27+
timeout=3,
28+
)
29+
response.raise_for_status()
30+
return response.json()
31+
32+
except requests.exceptions.RequestException as e:
33+
return e

agent/preprocess/setup.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""依赖环境自动安装模块。
2+
3+
本模块负责检测项目版本变化,并在必要时自动安装或更新 Python 依赖包。
4+
通过读取 interface.json 中的版本号与本地配置进行比对,
5+
决定是否需要执行 pip install 操作。
6+
"""
7+
8+
import os
9+
import sys
10+
import json
11+
import subprocess
12+
from pathlib import Path
13+
14+
# ==================== 路径初始化 ====================
15+
16+
current_file_path = os.path.abspath(__file__)
17+
current_dir = os.path.dirname(current_file_path)
18+
parent_dir = os.path.dirname(current_dir)
19+
os.chdir(parent_dir)
20+
21+
if current_dir not in sys.path:
22+
sys.path.insert(0, current_dir)
23+
24+
25+
# ==================== 全局路径常量 ====================
26+
27+
PIP_CONFIG_PATH = Path("./config/mddl/pip_config.json")
28+
29+
30+
# ==================== 配置文件读写 ====================
31+
32+
33+
def _read_interface_version() -> str:
34+
"""读取 interface.json 中的版本号。
35+
36+
Returns:
37+
版本号字符串,若文件不存在或读取失败则返回 "unknown"
38+
"""
39+
interface_path = Path("./interface.json")
40+
if not interface_path.exists():
41+
return "unknown"
42+
43+
try:
44+
with open(interface_path, "r", encoding="utf-8") as f:
45+
interface_data = json.load(f)
46+
return interface_data.get("version", "unknown")
47+
except Exception:
48+
return "unknown"
49+
50+
51+
def _read_pip_config() -> dict:
52+
"""读取 pip 安装配置文件。
53+
54+
若配置文件不存在,则自动创建并使用默认配置。
55+
默认配置包括:
56+
- enable_pip_install:是否启用自动安装,默认 True
57+
- last_version:上次安装时的版本号,默认 "unknown"
58+
- mirrors:pip 镜像源地址列表,按顺序尝试,失败后用默认源兜底
59+
60+
Returns:
61+
包含 pip 配置的字典
62+
"""
63+
config_dir = Path("./config/mddl")
64+
config_dir.mkdir(parents=True, exist_ok=True)
65+
66+
default_config = {
67+
"enable_pip_install": True,
68+
"last_version": "unknown",
69+
"mirrors": [
70+
"https://pypi.tuna.tsinghua.edu.cn/simple",
71+
"https://mirrors.ustc.edu.cn/pypi/simple",
72+
"https://mirrors.aliyun.com/pypi/simple",
73+
],
74+
}
75+
76+
if not PIP_CONFIG_PATH.exists():
77+
with open(PIP_CONFIG_PATH, "w", encoding="utf-8") as f:
78+
json.dump(default_config, f, indent=4)
79+
return default_config
80+
81+
try:
82+
with open(PIP_CONFIG_PATH, "r", encoding="utf-8") as f:
83+
return json.load(f)
84+
except Exception:
85+
return default_config
86+
87+
88+
def _update_pip_config(version) -> bool:
89+
"""更新 pip 配置文件中的版本号。
90+
91+
Args:
92+
version:要更新的版本号
93+
94+
Returns:
95+
更新成功返回 True,失败返回 False
96+
"""
97+
try:
98+
config = _read_pip_config()
99+
config["last_version"] = version
100+
101+
with open(PIP_CONFIG_PATH, "w", encoding="utf-8") as f:
102+
json.dump(config, f, indent=4)
103+
return True
104+
except Exception:
105+
return False
106+
107+
108+
# ==================== 依赖安装 ====================
109+
110+
111+
def _install_requirements(req_file=None, mirrors=None) -> bool:
112+
"""安装 requirements.txt 中列出的依赖包。
113+
114+
Args:
115+
req_file:依赖文件路径,默认为 "requirements.txt"
116+
mirrors:pip 镜像源地址列表,按顺序尝试,最后用默认源兜底
117+
118+
Returns:
119+
安装成功返回 True,失败或文件不存在返回 False
120+
"""
121+
req_path = Path(req_file) if req_file else Path("requirements.txt")
122+
if not req_path.exists():
123+
return False
124+
125+
# 准备镜像源列表,最后添加默认源作为兜底
126+
mirror_list = []
127+
if mirrors:
128+
if isinstance(mirrors, list):
129+
mirror_list.extend(mirrors)
130+
else:
131+
mirror_list.append(mirrors)
132+
133+
# 默认源兜底(PyPI 官方源)
134+
mirror_list.append(None)
135+
136+
# 逐个尝试镜像源
137+
for idx, mirror in enumerate(mirror_list):
138+
try:
139+
if mirror:
140+
print(
141+
f"正在使用镜像源安装或更新环境... ({idx + 1}/{len(mirror_list)}): {mirror}"
142+
)
143+
else:
144+
print(f"正在使用默认源安装或更新环境... ({idx + 1}/{len(mirror_list)})")
145+
146+
cmd = [
147+
sys.executable,
148+
"-m",
149+
"pip",
150+
"install",
151+
"-r",
152+
str(req_path),
153+
"--no-warn-script-location",
154+
]
155+
156+
if mirror:
157+
cmd.extend(["-i", mirror])
158+
159+
subprocess.check_call(
160+
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
161+
)
162+
print("安装完成!")
163+
return True
164+
except Exception as e:
165+
if idx < len(mirror_list) - 1:
166+
print(f"当前镜像源失败,尝试下一个...")
167+
continue
168+
else:
169+
print("环境加载失败,请检查网络与环境后重新尝试!")
170+
return False
171+
172+
return False
173+
174+
175+
# ==================== 主入口函数 ====================
176+
177+
178+
def check_and_install_dependencies():
179+
"""检查并自动安装或更新项目依赖。
180+
181+
该函数会:
182+
1、读取当前项目版本号(从 interface.json)
183+
2、读取上次安装时的版本号(从 pip_config.json)
184+
3、若版本号不一致或当前版本为 unknown,则执行依赖安装
185+
4、安装成功后更新配置文件中的版本号
186+
187+
注意:
188+
- 可通过修改 config/pip_config.json 中的 enable_pip_install 字段禁用自动安装
189+
- 可通过修改 mirrors 字段自定义 pip 镜像源列表,支持多个镜像源,按顺序尝试
190+
- 所有镜像源失败后会自动使用 PyPI 官方默认源兜底
191+
"""
192+
pip_config = _read_pip_config()
193+
current_version = _read_interface_version()
194+
last_version = pip_config.get("last_version", "unknown")
195+
enable_pip_install = pip_config.get("enable_pip_install", True)
196+
mirrors = pip_config.get("mirrors", None)
197+
198+
# 版本不一致时触发依赖安装
199+
if enable_pip_install and (
200+
current_version != last_version or current_version == "unknown"
201+
):
202+
if _install_requirements(mirrors=mirrors):
203+
_update_pip_config(current_version)

0 commit comments

Comments
 (0)