保留 Git 自动更新并简化 MAA 任务流程,加入 MaaEnd
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
*.log
|
*.log
|
||||||
|
__pycache__/
|
||||||
|
|||||||
+74
-128
@@ -1,139 +1,85 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import asyncio
|
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
# time record
|
|
||||||
def fprint(s:str)->None:
|
|
||||||
print(s)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
fprint("-------------------------------------------------------")
|
ADDRESS = os.getenv("ADB_ADDRESS", "127.0.0.1:5555")
|
||||||
fprint(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
REDROID = os.getenv("REDROID_CONTAINER", "redroid")
|
||||||
fprint("-------------------------------------------------------")
|
TASKS = ["startup", "recruit", "infrast", "fight", "mall", "award"]
|
||||||
time.sleep(1)
|
DIRECT = {k: v for k, v in os.environ.items() if k.lower() not in ("http_proxy", "https_proxy", "all_proxy")}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
# cp.Command("command")
|
def retry(args):
|
||||||
# cp.RetryCommand("command",retry_time:int)
|
for attempt in range(3):
|
||||||
# asyncio.run(cp.AsyncCommand("command","stop track regex"))
|
if command(args) == 0:
|
||||||
# script_dir = os.path.dirname((os.path.abspath(__file__)))
|
return True
|
||||||
cp = CommandProcessor()
|
print(f"命令失败,第 {attempt + 1}/3 次", flush=True)
|
||||||
haveError = False
|
time.sleep(1)
|
||||||
def TaskFailRetry(command:str)->int:
|
return False
|
||||||
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 TaskFailRetryWithNoProxy(command:str)->int:
|
|
||||||
retry = 3
|
def maa(*args):
|
||||||
result = 0
|
return ["maa", *args, "--batch", "-a", ADDRESS]
|
||||||
while retry > 0:
|
|
||||||
retry -= 1
|
|
||||||
result = cp.RetryCommandWithNoProxy(command)
|
def wait_android():
|
||||||
if result == 0:
|
print("等待 Android 启动……", flush=True)
|
||||||
return 0
|
deadline = time.monotonic() + 180
|
||||||
fprint(f"Task Command is {command}, ret code is {result}")
|
while time.monotonic() < deadline:
|
||||||
fprint("Task Failed, retrying...")
|
try:
|
||||||
cp.RetryCommand("maa closedown")
|
subprocess.run(["adb", "connect", ADDRESS], env=DIRECT, capture_output=True, timeout=10)
|
||||||
cp.RetryCommand("maa run startup")
|
result = subprocess.run(["adb", "-s", ADDRESS, "shell", "getprop", "sys.boot_completed"],
|
||||||
return result
|
env=DIRECT, capture_output=True, text=True, timeout=10)
|
||||||
# update
|
if result.returncode == 0 and result.stdout.strip() == "1":
|
||||||
cp.Command("maa update")
|
return True
|
||||||
cp.Command("maa self update")
|
except subprocess.TimeoutExpired:
|
||||||
# fire it up
|
pass
|
||||||
cp.Command("docker stop redroid")
|
time.sleep(2)
|
||||||
redroid_path = os.getenv('redroid_path', "/home/lichx/.config/redroid-rk3588/")
|
print("Android 启动超时", flush=True)
|
||||||
adb_address = os.getenv('adb_address', "localhost:5555")
|
return False
|
||||||
log_dir = os.getenv('MAA_LOG', "/home/lichx/.config/redroid-rk3588/log/")
|
|
||||||
os.chdir(redroid_path)
|
|
||||||
cp.Command("docker compose up -d")
|
def task(name):
|
||||||
time.sleep(15)
|
for _ in range(3):
|
||||||
cp.RetryCommand(f"adb connect {adb_address}")
|
if retry(maa("run", name)):
|
||||||
cp.RetryCommand(f"adb -s {adb_address} shell am start --windowingMode 4 com.hypergryph.arknights/com.u8.sdk.U8UnityContext")
|
return True
|
||||||
# run maa
|
print(f"任务 {name} 失败,重新打开游戏后重试", flush=True)
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run startup") != 0
|
retry(maa("closedown"))
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run recruit") != 0
|
retry(maa("run", "startup"))
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run infrast") != 0
|
return False
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run fight") != 0
|
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run mall") != 0
|
|
||||||
haveError |= TaskFailRetryWithNoProxy("maa run award") != 0
|
def main():
|
||||||
# storage error log
|
# 更新走代理;失败时仍尝试使用已有版本,与原脚本一致。
|
||||||
if haveError:
|
command(["maa", "update"], proxy=True)
|
||||||
cp.Command(f"cp {log_dir}/maa_pyshell.log {log_dir}/maa_pyshell.{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log")
|
command(["maa", "self", "update"], proxy=True)
|
||||||
# shutdown
|
if command(["docker", "restart", REDROID]):
|
||||||
cp.RetryCommand("maa closedown")
|
return 1
|
||||||
cp.Command("docker stop redroid")
|
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
|
# MAAsConfig(个人使用)
|
||||||
目前包括明日方舟(ArknightsMAA)
|
|
||||||
|
|
||||||
计划加入重返未来·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
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
SCRIPT_DIR=$(dirname $(readlink -f "$0"))
|
cd -- "$(dirname -- "$(readlink -f -- "$0")")"
|
||||||
MAA_PY="$SCRIPT_DIR/ArknightsMaaConfig/maa.py"
|
case "${1:-}" in
|
||||||
MAA_LOG_DIR="${MAA_LOG:-/var/log/maa.log}"
|
""|maaend-config) ;;
|
||||||
cd $SCRIPT_DIR
|
*) echo "未知参数:$1(仅支持无参数运行或 maaend-config)"; exit 2 ;;
|
||||||
http_proxy="" https_proxy="" git fetch origin && http_proxy="" https_proxy="" git reset --hard origin/master && http_proxy="" https_proxy="" git pull
|
esac
|
||||||
if [ -x "$MAA_PY" ]; then
|
mkdir -p "${LOG_DIR:-/state/logs}" /state/tmp
|
||||||
"$MAA_PY" > "$MAA_LOG_DIR/maa_pyshell.log" 2>&1
|
{
|
||||||
else
|
date
|
||||||
python3 "$MAA_PY" > "$MAA_LOG_DIR/maa_pyshell.log" 2>&1
|
http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= git pull --ff-only || exit 1
|
||||||
fi
|
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