保留 Git 自动更新并简化 MAA 任务流程,加入 MaaEnd
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
*.log
|
||||
*.log
|
||||
__pycache__/
|
||||
|
||||
+76
-130
@@ -1,139 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import asyncio
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
# time record
|
||||
def fprint(s:str)->None:
|
||||
print(s)
|
||||
sys.stdout.flush()
|
||||
|
||||
fprint("-------------------------------------------------------")
|
||||
fprint(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
fprint("-------------------------------------------------------")
|
||||
time.sleep(1)
|
||||
|
||||
class CommandProcessor:
|
||||
|
||||
def Command(self, s: str) -> int:
|
||||
fprint("_> " + s)
|
||||
if (t := os.system(s)) != 0:
|
||||
fprint("Fatal Error: Command Failed:" + s)
|
||||
fprint("Error Number: " + str(t))
|
||||
return t
|
||||
return 0
|
||||
|
||||
def CommandWithNoProxy(self, s: str) -> int:
|
||||
fprint("_> " + s)
|
||||
# 保存并删除
|
||||
old_http = os.environ.pop('http_proxy', None)
|
||||
old_https = os.environ.pop('https_proxy', None)
|
||||
|
||||
t = os.system(s)
|
||||
|
||||
# 立即恢复
|
||||
if old_http:
|
||||
os.environ['http_proxy'] = old_http
|
||||
if old_https:
|
||||
os.environ['https_proxy'] = old_https
|
||||
|
||||
if t != 0:
|
||||
fprint("Fatal Error: Command Failed:" + s)
|
||||
fprint("Error Number: " + str(t))
|
||||
return t
|
||||
|
||||
def RetryCommand(self,s:str,retry:int=3) -> int:
|
||||
result = 0
|
||||
while retry > 0:
|
||||
result = cp.Command(s)
|
||||
if result == 0:
|
||||
break
|
||||
retry-=1
|
||||
time.sleep(1)
|
||||
return result
|
||||
|
||||
def RetryCommandWithNoProxy(self,s:str,retry:int=3) -> int:
|
||||
result = 0
|
||||
while retry > 0:
|
||||
result = cp.CommandWithNoProxy(s)
|
||||
if result == 0:
|
||||
break
|
||||
retry-=1
|
||||
time.sleep(1)
|
||||
return result
|
||||
|
||||
async def AsyncCommand(self,command:str,regex:str):
|
||||
self._asyncCommand = command
|
||||
fprint("_> await! " + command)
|
||||
process = await asyncio.create_subprocess_shell(command,stderr=asyncio.subprocess.PIPE)
|
||||
while True:
|
||||
line = (await process.stderr.readline()).decode()
|
||||
fprint(line)
|
||||
if re.search(regex, line):
|
||||
fprint("done")
|
||||
break
|
||||
ADDRESS = os.getenv("ADB_ADDRESS", "127.0.0.1:5555")
|
||||
REDROID = os.getenv("REDROID_CONTAINER", "redroid")
|
||||
TASKS = ["startup", "recruit", "infrast", "fight", "mall", "award"]
|
||||
DIRECT = {k: v for k, v in os.environ.items() if k.lower() not in ("http_proxy", "https_proxy", "all_proxy")}
|
||||
|
||||
|
||||
|
||||
|
||||
# cp.Command("command")
|
||||
# cp.RetryCommand("command",retry_time:int)
|
||||
# asyncio.run(cp.AsyncCommand("command","stop track regex"))
|
||||
# script_dir = os.path.dirname((os.path.abspath(__file__)))
|
||||
cp = CommandProcessor()
|
||||
haveError = False
|
||||
def TaskFailRetry(command:str)->int:
|
||||
retry = 3
|
||||
result = 0
|
||||
while retry > 0:
|
||||
retry -= 1
|
||||
result = cp.RetryCommand(command)
|
||||
if result == 0:
|
||||
return 0
|
||||
fprint(f"Task Command is {command}, ret code is {result}")
|
||||
fprint("Task Failed, retrying...")
|
||||
cp.RetryCommand("maa closedown")
|
||||
cp.RetryCommand("maa run startup")
|
||||
return result
|
||||
def command(args, proxy=False):
|
||||
print(f"[{datetime.now():%H:%M:%S}] {' '.join(args)}", flush=True)
|
||||
return subprocess.run(args, env=os.environ if proxy else DIRECT).returncode
|
||||
|
||||
def TaskFailRetryWithNoProxy(command:str)->int:
|
||||
retry = 3
|
||||
result = 0
|
||||
while retry > 0:
|
||||
retry -= 1
|
||||
result = cp.RetryCommandWithNoProxy(command)
|
||||
if result == 0:
|
||||
return 0
|
||||
fprint(f"Task Command is {command}, ret code is {result}")
|
||||
fprint("Task Failed, retrying...")
|
||||
cp.RetryCommand("maa closedown")
|
||||
cp.RetryCommand("maa run startup")
|
||||
return result
|
||||
# update
|
||||
cp.Command("maa update")
|
||||
cp.Command("maa self update")
|
||||
# fire it up
|
||||
cp.Command("docker stop redroid")
|
||||
redroid_path = os.getenv('redroid_path', "/home/lichx/.config/redroid-rk3588/")
|
||||
adb_address = os.getenv('adb_address', "localhost:5555")
|
||||
log_dir = os.getenv('MAA_LOG', "/home/lichx/.config/redroid-rk3588/log/")
|
||||
os.chdir(redroid_path)
|
||||
cp.Command("docker compose up -d")
|
||||
time.sleep(15)
|
||||
cp.RetryCommand(f"adb connect {adb_address}")
|
||||
cp.RetryCommand(f"adb -s {adb_address} shell am start --windowingMode 4 com.hypergryph.arknights/com.u8.sdk.U8UnityContext")
|
||||
# run maa
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run startup") != 0
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run recruit") != 0
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run infrast") != 0
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run fight") != 0
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run mall") != 0
|
||||
haveError |= TaskFailRetryWithNoProxy("maa run award") != 0
|
||||
# storage error log
|
||||
if haveError:
|
||||
cp.Command(f"cp {log_dir}/maa_pyshell.log {log_dir}/maa_pyshell.{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log")
|
||||
# shutdown
|
||||
cp.RetryCommand("maa closedown")
|
||||
cp.Command("docker stop redroid")
|
||||
|
||||
def retry(args):
|
||||
for attempt in range(3):
|
||||
if command(args) == 0:
|
||||
return True
|
||||
print(f"命令失败,第 {attempt + 1}/3 次", flush=True)
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
|
||||
def maa(*args):
|
||||
return ["maa", *args, "--batch", "-a", ADDRESS]
|
||||
|
||||
|
||||
def wait_android():
|
||||
print("等待 Android 启动……", flush=True)
|
||||
deadline = time.monotonic() + 180
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
subprocess.run(["adb", "connect", ADDRESS], env=DIRECT, capture_output=True, timeout=10)
|
||||
result = subprocess.run(["adb", "-s", ADDRESS, "shell", "getprop", "sys.boot_completed"],
|
||||
env=DIRECT, capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == 0 and result.stdout.strip() == "1":
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
time.sleep(2)
|
||||
print("Android 启动超时", flush=True)
|
||||
return False
|
||||
|
||||
|
||||
def task(name):
|
||||
for _ in range(3):
|
||||
if retry(maa("run", name)):
|
||||
return True
|
||||
print(f"任务 {name} 失败,重新打开游戏后重试", flush=True)
|
||||
retry(maa("closedown"))
|
||||
retry(maa("run", "startup"))
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
# 更新走代理;失败时仍尝试使用已有版本,与原脚本一致。
|
||||
command(["maa", "update"], proxy=True)
|
||||
command(["maa", "self", "update"], proxy=True)
|
||||
if command(["docker", "restart", REDROID]):
|
||||
return 1
|
||||
failed = False
|
||||
ready = False
|
||||
try:
|
||||
ready = wait_android()
|
||||
if not ready:
|
||||
return 1
|
||||
retry(["adb", "-s", ADDRESS, "shell", "am", "start", "--windowingMode", "4",
|
||||
"com.hypergryph.arknights/com.u8.sdk.U8UnityContext"])
|
||||
for name in TASKS:
|
||||
if not task(name):
|
||||
failed = True
|
||||
finally:
|
||||
if ready and not retry(maa("closedown")):
|
||||
failed = True
|
||||
if command(["docker", "stop", REDROID]):
|
||||
failed = True
|
||||
return int(failed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""MaaEnd 只补齐当前官方终端包的路径和 ADB 界面适配。"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from ArknightsMaaConfig.maa import ADDRESS, REDROID, DIRECT, command, wait_android
|
||||
|
||||
|
||||
def prepare(configure=False):
|
||||
source = Path("/app/maaend")
|
||||
version = json.loads((source / "interface.json").read_text())["version"]
|
||||
target = Path("/state") / f"maaend-{version}"
|
||||
if not target.exists():
|
||||
shutil.copytree(source, target, symlinks=True)
|
||||
# 保留原文件;CLI 不支持 hotkey 设置,ADB 使用原始 pipeline 的默认值。
|
||||
original = target / "interface.upstream.json"
|
||||
if not original.exists():
|
||||
shutil.copyfile(target / "interface.json", original)
|
||||
interface = json.loads(original.read_text())
|
||||
interface["controller"] = [c for c in interface["controller"] if c["name"] == "ADB"]
|
||||
interface["import"] = [p for p in interface["import"] if p != "tasks/setting/Keymap.json"]
|
||||
(target / "interface.json").write_text(json.dumps(interface, ensure_ascii=False, indent=2))
|
||||
work = target / "maafw"
|
||||
config = json.loads(Path(__file__).with_name("maa_pi_config.json").read_text())
|
||||
if not configure and not config.get("task"):
|
||||
raise RuntimeError("请先配置 MaaEnd 任务并提交到 MAAsConfig,再启用 maaend。")
|
||||
config["adb"].update(address=ADDRESS, adb_path="/usr/bin/adb")
|
||||
(work / "config").mkdir(exist_ok=True)
|
||||
(work / "config/maa_pi_config.json").write_text(json.dumps(config, ensure_ascii=False, indent=2))
|
||||
return work
|
||||
|
||||
|
||||
def main():
|
||||
configure = sys.argv[1:] == ["configure"]
|
||||
work = prepare(configure)
|
||||
if command(["docker", "restart", REDROID]):
|
||||
return 1
|
||||
try:
|
||||
if not wait_android():
|
||||
return 1
|
||||
return subprocess.run([str(work / "MaaPiCli")] + ([] if configure else ["-d"]), cwd=work, env=DIRECT).returncode
|
||||
finally:
|
||||
command(["docker", "stop", REDROID])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"controller": {"name": "ADB"},
|
||||
"adb": {"name": "ReDroid", "adb_path": "/usr/bin/adb", "address": "127.0.0.1:5555"},
|
||||
"resource": "官服",
|
||||
"task": []
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
## Lichx's Maa Like software config
|
||||
目前包括明日方舟(ArknightsMAA)
|
||||
# MAAsConfig(个人使用)
|
||||
|
||||
计划加入重返未来·1999(M9A)
|
||||
镜像构建时克隆此仓库,容器每次启动由 run_all.sh 拉取更新,再运行游戏脚本。修改配置和脚本后提交、推送,下轮容器启动就会取得更新。
|
||||
|
||||
- `ArknightsMaaConfig/maa.py`:保留 MAA 更新、ReDroid 启停、六步日常任务、失败重试和重开游戏。更新走代理,ADB 和游戏任务不走代理。
|
||||
- `ArknightsMaaConfig/tasks/`、`infrast/`:沿用原任务和基建配置,包括旧活动关卡和理智药策略。
|
||||
- `MaaEndConfig/maa.py`:运行官方 MaaPiCli,补齐当前发行包的 ADB 路径和界面适配。
|
||||
- `MaaEndConfig/maa_pi_config.json`:MaaEnd 任务选择,目前为空。配置后再启用 `GAMES="arknights maaend"`。
|
||||
- `run_all.sh`:更新 Git、按 GAMES 依次执行脚本,用 tee 同时输出和保存日志。
|
||||
|
||||
`MAA_LOG=info` 控制基本进度;日志目录是 `LOG_DIR`,不再把路径放进 MAA_LOG。默认由 MAAsDocker 的 Compose 提供这些环境变量。
|
||||
|
||||
+24
-11
@@ -1,12 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$(dirname $(readlink -f "$0"))
|
||||
MAA_PY="$SCRIPT_DIR/ArknightsMaaConfig/maa.py"
|
||||
MAA_LOG_DIR="${MAA_LOG:-/var/log/maa.log}"
|
||||
cd $SCRIPT_DIR
|
||||
http_proxy="" https_proxy="" git fetch origin && http_proxy="" https_proxy="" git reset --hard origin/master && http_proxy="" https_proxy="" git pull
|
||||
if [ -x "$MAA_PY" ]; then
|
||||
"$MAA_PY" > "$MAA_LOG_DIR/maa_pyshell.log" 2>&1
|
||||
else
|
||||
python3 "$MAA_PY" > "$MAA_LOG_DIR/maa_pyshell.log" 2>&1
|
||||
fi
|
||||
set -euo pipefail
|
||||
cd -- "$(dirname -- "$(readlink -f -- "$0")")"
|
||||
case "${1:-}" in
|
||||
""|maaend-config) ;;
|
||||
*) echo "未知参数:$1(仅支持无参数运行或 maaend-config)"; exit 2 ;;
|
||||
esac
|
||||
mkdir -p "${LOG_DIR:-/state/logs}" /state/tmp
|
||||
{
|
||||
date
|
||||
http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= git pull --ff-only || exit 1
|
||||
if [[ "${1:-}" == maaend-config ]]; then
|
||||
python3 -u MaaEndConfig/maa.py configure
|
||||
else
|
||||
result=0
|
||||
for game in ${GAMES-arknights}; do
|
||||
case "$game" in
|
||||
arknights) python3 -u ArknightsMaaConfig/maa.py || result=1 ;;
|
||||
maaend) python3 -u MaaEndConfig/maa.py || result=1 ;;
|
||||
*) echo "未知游戏:$game"; result=1 ;;
|
||||
esac
|
||||
done
|
||||
exit "$result"
|
||||
fi
|
||||
} 2>&1 | tee -a "${LOG_DIR:-/state/logs}/maa-$(date +%Y%m%d-%H%M%S).log"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from unittest.mock import call, patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location("maa_script", Path(__file__).resolve().parents[1] / "ArknightsMaaConfig/maa.py")
|
||||
maa = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(maa)
|
||||
|
||||
|
||||
class MaaTest(unittest.TestCase):
|
||||
def test_update_uses_proxy_but_game_commands_do_not(self):
|
||||
with patch.object(maa.os, "environ", {"http_proxy": "http://proxy:7890"}), \
|
||||
patch.object(maa, "DIRECT", {"MAA_LOG": "info"}), \
|
||||
patch.object(maa.subprocess, "run") as run:
|
||||
run.return_value.returncode = 0
|
||||
maa.command(["maa", "update"], proxy=True)
|
||||
self.assertEqual(run.call_args.kwargs["env"]["http_proxy"], "http://proxy:7890")
|
||||
maa.command(["maa", "run", "startup"])
|
||||
self.assertNotIn("http_proxy", run.call_args.kwargs["env"])
|
||||
|
||||
def test_failed_task_does_not_skip_other_tasks_or_shutdown(self):
|
||||
with patch.object(maa, "command", return_value=0) as command, \
|
||||
patch.object(maa, "wait_android", return_value=True), \
|
||||
patch.object(maa, "retry", return_value=True), \
|
||||
patch.object(maa, "task", side_effect=[True, False, True, True, True, True]) as task:
|
||||
self.assertEqual(maa.main(), 1)
|
||||
self.assertEqual(task.call_args_list, [call(name) for name in maa.TASKS])
|
||||
self.assertEqual(command.call_args.args[0], ["docker", "stop", maa.REDROID])
|
||||
|
||||
def test_task_failure_reopens_game_then_retries(self):
|
||||
with patch.object(maa, "retry", side_effect=[False, True, True, True]) as retry:
|
||||
self.assertTrue(maa.task("fight"))
|
||||
self.assertEqual(retry.call_args_list, [
|
||||
call(maa.maa("run", "fight")), call(maa.maa("closedown")),
|
||||
call(maa.maa("run", "startup")), call(maa.maa("run", "fight")),
|
||||
])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user