Skip to content

Commit 5b0ea8d

Browse files
committed
feat: 捆绑 AFA
1 parent 14ede8f commit 5b0ea8d

4 files changed

Lines changed: 109 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ jobs:
2727
- run: pnpm check:py
2828
- name: Build (PyInstaller)
2929
run: uv run pyinstaller aao.spec --noconfirm
30+
- name: Download AFA (ArknightsFrameAssistant)
31+
shell: bash
32+
run: |
33+
mkdir -p dist/aao.app/afa
34+
# AFA 是 GPL-3.0(CloudTracey/arknights-frame-assistant),凹图热键依赖。
35+
# 查最新 release tag,下对应 AFA.exe + LICENSE(满足 GPL 分发要求)。
36+
repo="CloudTracey/arknights-frame-assistant"
37+
tag="$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')"
38+
echo "AFA latest tag: $tag"
39+
curl -fsSL "https://github.com/$repo/releases/download/$tag/AFA.exe" -o dist/aao.app/afa/AFA.exe
40+
curl -fsSL "https://raw.githubusercontent.com/$repo/$tag/LICENSE" -o dist/aao.app/afa/LICENSE || echo "[WARN] AFA LICENSE 未取到"
41+
ls -la dist/aao.app/afa/
3042
- name: Convert logo.png to ico
3143
shell: bash
3244
run: uv run python -c "from PIL import Image; Image.open('logo.png').save('logo.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])"

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ loguru 双 sink:
6262
- [loguru](https://github.com/Delgan/loguru) — 日志
6363
- [numpy](https://numpy.org/) / [websockets](https://websockets.readthedocs.io/) / [Pillow](https://python-pillow.org/) / [json5](https://github.com/dpranke/pyjson5)
6464

65+
### 捆绑组件
66+
67+
- [**ArknightsFrameAssistant (AFA)**](https://github.com/CloudTracey/arknights-frame-assistant)(GPL-3.0)— 凹图的暂停/步进/技能/撤退依赖 AFA 的全局热键。**发版包自带 AFA.exe**(从上游 release 下载),启动时自动拉起。AFA 源码与许可见上游仓库;本仓库不持有其版权,仅作分发。
68+
6569
### 参考项目
6670

6771
- [MaaAssistantArknights](https://github.com/MaaAssistantArknights/MaaAssistantArknights) — 地图数据(`Arknights-Tile-Pos`,运行时同步)+ 粗流程参照
6872
- [ArknightsCostBarRuler](https://github.com/ZeroAd-06/ArknightsCostBarRuler) — 费用条计时 / 悬浮窗 / 打轴对轴(测量层、校准、tick 检测来源)
6973
- [prts-plus](https://github.com/jue-ce-zhe/prts-plus) — 帧级自动操作的执行器算法(action / 投影 / 配置)
70-
- [ArknightsFrameAssistant](https://github.com/CloudTracey/arknights-frame-assistant) — PC 端帧操、AFA 热键输入模型

aao/app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ def main() -> int:
393393
_ensure_admin()
394394
# 先 configure_paths 让 settings_page 能读到 config/settings.json
395395
configure_paths()
396+
# 确保 AFA 在运行(凹图热键依赖;没在则拉起自带 AFA.exe)
397+
from aao.core.afa import ensure_afa
398+
399+
ensure_afa()
396400
from aao.ui.settings_page import load_settings
397401

398402
saved = load_settings()

aao/core/afa.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""AFA(明日方舟帧操小助手)进程管理:检测 + 拉起自带 AFA.exe。
2+
3+
AFA 是独立常驻 AutoHotkey 工具,注册全局热键完成暂停/步进/技能/撤退时序。
4+
executor.afa_hotkey 通过 SendInput 模拟按键触发 AFA 热键——故 AFA 必须先运行。
5+
6+
本模块:
7+
- is_afa_running():查 AFA.exe 进程是否在
8+
- ensure_afa():没在则拉起自带的 AFA.exe(打包在 exe 同级 afa/AFA.exe)
9+
10+
AFA 用默认热键配置(F/Space/R/W/S/A),与 afa_hotkey 假设一致。
11+
AFA 是 GPL-3.0(CloudTracey/arknights-frame-assistant),自带分发见 README 许可声明。
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import subprocess
17+
import sys
18+
from pathlib import Path
19+
20+
from aao.utils.logger import logger
21+
from aao.utils.runtime_paths import project_root
22+
23+
_AFA_PROCESS = "AFA.exe" # AFA 进程名(编译后)
24+
_AFA_REL_PATH = "afa" / Path(_AFA_PROCESS) # 相对项目根/exe 同级
25+
26+
27+
def _afa_exe_path() -> Path:
28+
"""自带 AFA.exe 路径(项目根 或 exe 同级的 afa/AFA.exe)。"""
29+
return project_root() / _AFA_REL_PATH
30+
31+
32+
def is_afa_running() -> bool:
33+
"""AFA.exe 进程是否在运行。非 Windows 返回 False。"""
34+
if sys.platform != "win32":
35+
return False
36+
try:
37+
import ctypes
38+
39+
# 用 CreateToolhelp32Snapshot 枚举进程,避免依赖 tasklist/PowerShell
40+
# 简单起见用 tasklist(Windows 自带)
41+
out = subprocess.run(
42+
["tasklist", "/FI", f"IMAGENAME eq {_AFA_PROCESS}", "/NH", "/FO", "CSV"],
43+
capture_output=True,
44+
text=True,
45+
timeout=5,
46+
check=False,
47+
)
48+
running = _AFA_PROCESS.lower() in out.stdout.lower()
49+
# 引用 ctypes 防止 pyright 报未用(保留 Windows 判断接口)
50+
_ = ctypes
51+
return running
52+
except (OSError, subprocess.SubprocessError):
53+
logger.warning("无法检测 AFA 进程,假定未运行")
54+
return False
55+
56+
57+
def ensure_afa() -> bool:
58+
"""确保 AFA 在运行:没在则拉起自带 AFA.exe(后台、无窗口阻塞)。
59+
60+
Returns:
61+
True 若 AFA 已在运行 或 成功拉起;False 若拉起失败。
62+
"""
63+
if is_afa_running():
64+
logger.info("AFA 已在运行")
65+
return True
66+
67+
exe = _afa_exe_path()
68+
if not exe.exists():
69+
logger.warning("未找到自带 AFA.exe(%s),请手动启动 AFA 或重新安装", exe)
70+
return False
71+
72+
try:
73+
# 后台启动 AFA( detached,不随本进程退出而终止)
74+
# CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS(Windows)
75+
flags = 0x00000008 | 0x00000200 if sys.platform == "win32" else 0 # noqa: S107
76+
subprocess.Popen(
77+
[str(exe)],
78+
cwd=str(exe.parent),
79+
stdin=subprocess.DEVNULL,
80+
stdout=subprocess.DEVNULL,
81+
stderr=subprocess.DEVNULL,
82+
close_fds=True,
83+
creationflags=flags,
84+
)
85+
logger.info("已拉起自带 AFA: %s", exe)
86+
return True
87+
except (OSError, subprocess.SubprocessError):
88+
logger.exception("拉起 AFA 失败")
89+
return False

0 commit comments

Comments
 (0)