Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,46 @@ OAS 在其基础上进行了如下优化:
- [ppocr-onnx](https://github.com/triwinds/ppocr-onnx): OCR 库,基于 onnxruntime 和 PaddleOCR
- [gurs](https://github.com/2833844911/gurs): 基于赛贝尔曲线模拟滑动轨迹, 引入其轨迹模拟人手滑动

## macOS PlayCover / MaaTools(实验性支持)

### 中文说明

本实验性接入借鉴了 MAA(明日方舟小助手)的 [macOS PlayCover / MaaTools 接入思路](https://docs.maa.plus/zh-cn/manual/device/macos.html),并使用 [OASX](https://github.com/runhey/OASX) 作为界面。我们已从 OASX Flutter 源码编译出一个可在 macOS 本地运行的 app,用于本集成验证;本 PR 不上传或分发该二进制。此功能仍处于试验性阶段,不代表官方 release 已经包含 macOS app。

Flutter app 仅是界面,不内置 Python 解释器、OCR、本地 OAS 服务或其依赖。请在 macOS 主机上安装 Python 3.10,创建 venv,并手动安装 [requirements-macos-playcover.txt](requirements-macos-playcover.txt) 中的 pip 包:

```bash
python3.10 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements-macos-playcover.txt
Comment on lines +84 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "README size and relevant sections"
wc -l README.md
sed -n '70,125p' README.md

echo
echo "Search macOS/OASX/playcover/OAS service mentions"
rg -n "OASX|PlayCover|MaaTools|requirements-macos-playcover|MacPlayTools|OAS|OASX|playcover|本地 OAS|macOS" -S . | head -200

echo
echo "Files named requirements-macos-playcover.txt and docs"
fd -a 'requirements-macos-playcover\.txt|README|.*mac.*|.*OASX.*' . | sed 's#^\./##' | head -200

Repository: runhey/OnmyojiAutoScript

Length of output: 21602


补齐 OASX app 和本地 OAS 服务的可执行使用说明。

README 现在仅说明 PR 不分发 OASX 二进制,并给出 Python 依赖安装与 PlayCover 配置。但没有经过验证的 OASX 构建/获取入口,也没有启动本地 OAS 服务的命令和配置确认;当前说明无法让用户实际到达选择 MacPlayTools 前的环境准备状态。请补充构建或获取步骤,以及 OAS 服务的启动与配置方式;若仅为开发者验证流程,请在中英文说明中明确标注范围。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 84 - 92, 完善 README 中 macOS PlayCover
实验性集成的中英文说明:补充经过验证的 OASX app 获取或构建入口,并说明如何启动本地 OAS 服务及确认其配置,使用户能完成选择
MacPlayTools 前的环境准备。若流程仅支持开发者验证,请在两种语言版本中明确标注其范围;保留现有不分发二进制和 Python 依赖安装说明。

```

这份文件只是 pip 包清单,不包含 Python 解释器或 OCR 模型文件;这些内容也不会由 Flutter app 内置提供。

PlayCover 需要 MaaTools。请先按 [MAA macOS 手册](https://docs.maa.plus/zh-cn/manual/device/macos.html) 配置 PlayCover/MaaTools,然后在 OASX 现有下拉框中选择 PlayCover(`control=MacPlayTools`),`screenshot` 选择 `MacBGR`、`RGBA` 或 `MacSCK`,并将 `serial` 填为标题栏中的 `localhost:1718`。PlayCover、ADB 和 minitouch 是并列可选的控制方式;选择 `adb` 或 `minitouch` 时使用 Android 模拟器。

请在 PlayCover 中将游戏分辨率设置为 1280×720。OAS 要求使用该分辨率,否则无法正常识别和点击。

### English

This experimental integration follows the [macOS PlayCover / MaaTools approach used by MAA (MaaAssistantArknights)](https://docs.maa.plus/zh-cn/manual/device/macos.html) and uses [OASX](https://github.com/runhey/OASX) as its UI. We compiled an app from the OASX Flutter source that runs locally on macOS for integration validation; this PR does not upload or distribute that binary. This remains experimental support and does not claim that an official release already includes a macOS app.

The Flutter app is UI-only. It does not bundle a Python interpreter, OCR, the OAS service, or its dependencies. On the macOS host, install Python 3.10, create a venv, and manually install the pip packages in [requirements-macos-playcover.txt](requirements-macos-playcover.txt):

```bash
python3.10 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements-macos-playcover.txt
```

That file is only a pip package list; it does not include the Python interpreter or OCR model files, and the Flutter app does not bundle or provide them.

PlayCover requires MaaTools. First configure PlayCover/MaaTools according to the [MAA macOS manual](https://docs.maa.plus/zh-cn/manual/device/macos.html), then in OASX's existing dropdowns select PlayCover (`control=MacPlayTools`), choose `MacBGR`, `RGBA`, or `MacSCK` for `screenshot`, and set `serial` to `localhost:1718` shown in the title bar. PlayCover, ADB, and minitouch are parallel control options; selecting `adb` or `minitouch` uses the Android emulator.

Set the game resolution in PlayCover to 1280×720. OAS requires this resolution; otherwise recognition and tapping will not work correctly.

## 联系/加入我们 Contact/Join Us


Expand Down Expand Up @@ -123,4 +163,4 @@ OAS 在其基础上进行了如下优化:

![](https://profile-counter.glitch.me/runhey-OnmyojiAutoScript/count.svg)

</div>
</div>
9 changes: 8 additions & 1 deletion module/device/app_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
# from module.device.method.wsa import WSA
from module.logger import logger


class AppControl(Adb, Uiautomator2):
hierarchy: etree._Element
_app_u2_family = ['uiautomator2', 'minitouch', 'scrcpy']

def app_is_running(self) -> bool:
if self.is_playcover:
return True
method = self.config.script.device.control_method
# if self.is_wsa:
# package = self.app_current_wsa()
Comment on lines +14 to 18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 对于 PlayCover,从 dump_hierarchy 返回 None 可能会破坏那些期望获得 XML 元素的调用方。

函数签名和文档字符串承诺返回 etree._Element,但现在 PlayCover 分支返回 None,这会在调用方将 self.hierarchy 当作元素(例如调用 .xpath(...))时导致运行时错误。如果这是有意为之,要么更新所有调用点以显式处理 None,要么返回一个最小的空层级元素,以保持类型契约的一致性。

Original comment in English

issue (bug_risk): Returning None from dump_hierarchy for PlayCover may break callers expecting an XML element.

The signature and docstring promise an etree._Element, but the PlayCover path now returns None, which can cause runtime errors when callers use self.hierarchy as an element (e.g., .xpath(...)). If this is intentional, either update call sites to handle None or return a minimal empty hierarchy element to keep the type contract consistent.

Expand All @@ -25,6 +26,8 @@ def app_is_running(self) -> bool:
return package == self.package

def app_start(self):
if self.is_playcover:
return
method = self.config.script.device.screenshot_method
logger.info(f'App start: {self.package}')
# if self.config.Emulator_Serial == 'wsa-0':
Expand All @@ -35,6 +38,8 @@ def app_start(self):
self.app_start_adb()

def app_stop(self):
if self.is_playcover:
return
method = self.config.script.device.screenshot_method
logger.info(f'App stop: {self.package}')
if method in AppControl._app_u2_family:
Expand All @@ -47,6 +52,8 @@ def dump_hierarchy(self) -> etree._Element:
Returns:
etree._Element: Select elements with `self.hierarchy.xpath('//*[@text="Hermit"]')` for example.
"""
if self.is_playcover:
return None
method = self.config.script.device.screenshot_method
if method in AppControl._app_u2_family:
self.hierarchy = self.dump_hierarchy_uiautomator2()
Expand Down
9 changes: 9 additions & 0 deletions module/device/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from module.base.decorator import Config, cached_property, del_cached_property
from module.base.utils import ensure_time
from module.device.connection_attr import ConnectionAttr
from module.device.method.playcover import PlayCoverClient
from module.device.method.utils import (
RETRY_TRIES, remove_shell_warning, retry_sleep,
handle_adb_error, PackageNotInstalled,
Expand Down Expand Up @@ -95,6 +96,14 @@ def __init__(self, config):
config (AzurLaneConfig, str): Name of the user config under ./config
"""
super().__init__(config)
if self.is_playcover:
self.playcover_client = PlayCoverClient(
self.serial,
screenshot_mode=self.config.script.device.screenshot_method,
)
self.playcover_client.connect()
self.package = 'com.netease.onmyoji'
return
if not self.is_over_http:
self.detect_device()

Expand Down
10 changes: 7 additions & 3 deletions module/device/connection_attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from module.exception import RequestHumanTakeover
from module.logger import logger


class ConnectionAttr:
config: Config
serial: str
Expand All @@ -34,6 +35,12 @@ def __init__(self, config):
else:
self.config = config

self.serial = str(self.config.script.device.serial)
self.is_playcover = self.config.script.device.control_method == 'MacPlayTools'
if self.is_playcover:
logger.attr('PlayCover', self.serial)
return

# Init adb client
logger.attr('AdbBinary', self.adb_binary)
# Monkey patch to custom adb
Expand Down Expand Up @@ -65,7 +72,6 @@ def __init__(self, config):

# Parse custom serial
# self.serial = str(self.config.Emulator_Serial)
self.serial = str(self.config.script.device.serial)
self.serial_check()
self.config.DEVICE_OVER_HTTP = self.is_over_http

Expand Down Expand Up @@ -282,5 +288,3 @@ def u2(self) -> u2.Device:

logger.attr('u2.Device', f'Device(atx_agent_url={device._get_atx_agent_url()})')
return device


17 changes: 16 additions & 1 deletion module/device/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def click_methods(self):
'uiautomator2': self.click_uiautomator2,
'minitouch': self.click_minitouch,
'window_message': self.click_window_message if IS_WINDOWS else None,
'MacPlayTools': self.click_playcover,
# 'Hermit': self.click_hermit,
# 'MaaTouch': self.click_maatouch,
}
Expand All @@ -35,7 +36,8 @@ def long_click_methods(self):
'uiautomator2': self.long_click_uiautomator2,
'minitouch': self.long_click_minitouch,
'window_message': self.long_click_window_message if IS_WINDOWS else None,
'scrcpy': self.long_click_scrcpy
'scrcpy': self.long_click_scrcpy,
'MacPlayTools': self.long_click_playcover,
# 'Hermit': self.click_hermit,
# 'MaaTouch': self.click_maatouch,
}
Expand Down Expand Up @@ -81,6 +83,15 @@ def click(self, x: int, y: int, control_check=True, control_name='Click') -> Non
)
method(x, y)

def click_playcover(self, x, y):
self.playcover_client.click(x, y)

def long_click_playcover(self, x, y, duration=0.8):
self.playcover_client.long_click(x, y, duration=duration)

def swipe_playcover(self, p1, p2, duration=0.1):
self.playcover_client.swipe(p1, p2, duration=duration)


def multi_click(self, button, n, interval=(0.1, 0.2)):
"""
Expand Down Expand Up @@ -163,6 +174,8 @@ def swipe(self, p1, p2, duration=(0.1, 0.2), control_name='SWIPE', distance_chec
logger.info('Swipe %s -> %s, %s' % (point2str(*p1), point2str(*p2), duration))
elif method == 'scrcpy':
logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
elif method == 'MacPlayTools':
logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
# elif method == 'MaaTouch':
# logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
else:
Expand Down Expand Up @@ -192,6 +205,8 @@ def swipe(self, p1, p2, duration=(0.1, 0.2), control_name='SWIPE', distance_chec
self.swipe_uiautomator2(p1, p2, duration=duration)
elif method == 'scrcpy':
self.swipe_scrcpy(p1, p2)
elif method == 'MacPlayTools':
self.swipe_playcover(p1, p2, duration=duration)
# elif method == 'MaaTouch':
# self.swipe_maatouch(p1, p2)
else:
Expand Down
Loading