MaaAssistantArknights 远程控制协议实战:基于 getTask / reportStatus 双端点的轮询式任务调度实现
【免费下载链接】MaaAssistantArknights《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients.项目地址: https://gitcode.com/GitHub_Trending/ma/MaaAssistantArknights
本文基于 MaaAssistantArknights(MAA)仓库中的远程控制协议文档,完整讲解如何搭建一套 HTTP 服务来远程控制 MAA 实例:你需要理解"获取任务端点(getTask)+ 汇报任务端点(reportStatus)"这一双端点轮询架构、各类任务 type 的取值与执行语义(顺序任务 vs 立即任务),并结合 GUI 端源码(RemoteControlService.cs)了解轮询、去重、双队列调度与结果上报的底层实现,最终能独立开发 QQBot、Web 控制台等第三方远程控制后端。
一、协议总览:MAA 是被轮询的"执行端"
MAA 的远程控制协议是一种**客户端轮询式(Pull-based)**设计:MAA 本身不对外暴露端口、不需要回调地址,它只会做两件事——
- 定时轮询你提供的"获取任务端点",拉取要执行的任务列表;
- 任务执行完毕后,向"汇报任务端点" POST 执行结果。
因此,要实现远程控制,控制方(你的服务器)必须提供一个 http(s) 服务,并且该服务提供两个可匿名访问的 web 端点。两个端点的路径可以随意命名,例如:
https://your-control-host.net/maa/getTask https://your-control-host.net/maa/reportStatus被控 MAA 需要分别把这两个地址填写到设置界面"远程控制"页签的获取任务端点和汇报任务端点文本框中。
两个关键的安全与格式注意事项(原文档明确强调):
- 如果端点使用 http 明文协议,MAA 会在每次连接时发出不安全警告。在公网部署明文传输服务非常危险,官方文档仅建议测试环境使用。源码中这一点由 RemoteControlService.cs 的
IsEndpointValid实现:https://直接通过;http://会弹出"连接端点未启用 https,可能不安全"的提示但仍返回有效;非 http(s) 地址则判定为无效。 - JSON 文件不支持注释,协议文档中的注释仅用于演示说明,实际返回的 JSON 中不能携带注释。
二、获取任务端点(getTask):请求与响应格式
2.1 请求格式
MAA 以固定间隔持续轮询该端点,默认间隔 1 秒,可在设置中调整(对应配置项RemoteControlPollIntervalMs,默认值 1000ms,见 RemoteControl.cs)。
端点必须能够接受一个Content-Type=application/json的 POST 请求,请求体至少包含以下字段:
{ "user": "ea6c39eb-a45f-4d82-9ecc-33a7bf2ae4dc", // 用户在 MAA 设置中填写的用户标识符 "device": "f7cd9682-3de9-4eef-9137-ec124ea9e9ec" // MAA 自动生成的设备标识符 }user是用户自己填写的任意标识(如 QQ 号、网站账号密钥);device由 MAA 自动生成,可在界面中"重新生成"(源码实现为Guid.NewGuid().ToString("N"),见 RemoteControlService.cs);- 如果端点还有其他用途,你可以自行添加可选参数,但MAA 只会传递 user 和 device 两个字段,不会传更多。
2.2 响应格式:任务列表
端点必须返回 JSON 格式的 Response,且必须包含tasks数组(不存在 tasks 字段视为连接无效)。完整示例如下(注释仅供阅读):
{ "tasks": [ // —— 顺序执行的任务:按下发顺序排队执行 —— { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "CaptureImage" // 截图任务:截取当前模拟器的一张截图,以 Base64 字符串放在汇报任务的 payload 里。 // 注意:截图可能有数十 MB,若端点部署在网关之后,务必调大网关允许的最大请求体大小。 }, { "id": "15be4725-5bd3-443d-8ae3-0a5ae789254c", "type": "LinkStart" // 启动"一键长草" }, { "id": "15be4725-5bd3-443d-8ae3-0a5ae789254c", "type": "LinkStart-Recruiting" // 立即根据当前配置,单独执行一键长草中的对应子功能, // 无视主界面上该功能的勾选框。可选 Type 见下表。 }, { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "Toolbox-GachaOnce" // 工具箱抽卡任务,可选值:Toolbox-GachaOnce、Toolbox-GachaTenTimes }, { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "Settings-ConnectAddress", "params": "value" // 修改配置项任务,等同于设置连接设置中的 ConnectAddress 属性。 // 出于安全考虑并非所有配置都可以远程修改,可选值见下表。 }, // —— 立即执行任务:可穿插在顺序任务运行中,MAA 保证其尽快返回 —— { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "CaptureImageNow" // 立刻截图:与普通截图任务基本相同,区别是不会排队等待其他任务 }, { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "StopTask" // "结束当前任务":尝试停止当前运行的任务。若队列中还有其他任务会继续执行下一个。 // 该任务不等待确认停止完成就返回,因此需用 HeartBeat 确认停止是否生效。 }, { "id": "b353c469-b902-4357-bd8f-d133199eea31", "type": "HeartBeat" // 心跳任务:立即返回,并把顺序任务队列中"正在执行"的任务 Id 作为 payload 返回; // 若当前没有任务在执行,返回空字符串。 } ] }除了tasks之外的其他返回字段 MAA 不会读取,你可以根据自身业务自由扩展。
2.3 任务 type 完整取值表
| type | 执行类别 | 说明 |
|---|---|---|
CaptureImage | 顺序执行 | 截图排队执行,截图 PNG 的 Base64 随汇报 payload 返回 |
LinkStart | 顺序执行 | 启动一键长草(按当前配置执行全部已勾选功能) |
LinkStart-Base | 顺序执行 | 单独执行基建,无视主界面勾选框 |
LinkStart-WakeUp | 顺序执行 | 单独执行启动(WakeUp) |
LinkStart-Combat | 顺序执行 | 单独执行作战(Combat) |
LinkStart-Recruiting | 顺序执行 | 单独执行公招(Recruiting) |
LinkStart-Mall | 顺序执行 | 单独执行贸易站(Mall) |
LinkStart-Mission | 顺序执行 | 单独执行赏金(Mission) |
LinkStart-AutoRoguelike | 顺序执行 | 单独执行自动集成战略 |
LinkStart-Reclamation | 顺序执行 | 单独执行物资回收站 |
Toolbox-GachaOnce | 顺序执行 | 工具箱牛牛抽卡(单抽) |
Toolbox-GachaTenTimes | 顺序执行 | 工具箱牛牛抽卡(十连) |
Settings-ConnectAddress | 顺序执行 | 修改连接地址配置(params为新值) |
Settings-Stage1 | 顺序执行 | 修改第一作战任务的关卡(params为关卡号) |
CaptureImageNow | 立即执行 | 立刻截图,不等待队列中的其他任务 |
StopTask | 立即执行 | 请求停止当前任务,不等待确认 |
HeartBeat | 立即执行 | 立即返回,payload 为当前正在执行的顺序任务 Id(空闲时为空字符串) |
执行顺序的语义要点:
- 顺序任务严格按下发顺序排队。例如先下发一个公招任务再下发一个截图任务,截图会在公招结束后执行;
- 立即执行任务可以穿插在顺序任务运行期间,MAA 保证这类任务尽快返回,通常用于远程控制功能本身的控制(截图、停止、心跳);多个立即任务之间也按下发顺序执行,但执行速度都很快,一般无需关心;
Settings-*系列任务仍是顺序执行,不会在收到时立刻生效,而是排在前一个任务之后;- 端点应当可以重入(允许反复轮询)且重复返回相同任务也没关系:MAA 会自动记录已入队的任务 id,相同 id 不会重复执行;
- 未知 type 会被 MAA 直接忽略(源码中会解锁一个"404"成就彩蛋)。
三、汇报任务端点(reportStatus):结果上报
每当 MAA 执行完一个任务,就会向该端点 POST 一次执行结果。端点路径同样随意,例如https://your-control-host.net/maa/reportStatus。
端点必须能够接受一个Content-Type=application/json的 POST 请求,请求体格式为:
{ "user": "ea6c39eb-a45f-4d82-9ecc-33a7bf2ae4dc", // 用户标识符 "device": "f7cd9682-3de9-4eef-9137-ec124ea9e9ec", // 设备标识符 "task": "15be4725-5bd3-443d-8ae3-0a5ae789254c", // 要汇报的任务 Id,与下发时的 id 对应 "status": "SUCCESS", // 任务执行结果:SUCCESS 或 FAILED "payload": "" // 汇报时携带的数据,字符串类型 }status一般不论任务执行成功与否都返回SUCCESS,只有特殊情况才返回FAILED(哪些情况会 FAILED,协议在对应任务介绍中会明确说明,例如截图失败);payload具体内容取决于任务类型:截图任务汇报时,payload 携带截图的 Base64 字符串;HeartBeat任务则携带当前正在执行的顺序任务 Id。
该端点的返回内容任意:MAA 不会读取返回内容,也不校验状态码;汇报请求失败时 MAA 只会在日志中记录错误,不会重试或阻塞后续任务。因此后端实现时可以把该接口做得非常"无脑"——接收并落库即可。
四、源码解析:MAA 端如何调度这些任务
GUI 端的全部远程控制逻辑集中在 RemoteControlService.cs,理解它可以帮助你精确把握协议语义。
4.1 三个并行轮询循环
服务初始化时(InitializePollJobTask,RemoteControlService.cs)会启动三个无限循环,每个循环都以RemoteControlPollIntervalMs为节拍:
| 循环 | 职责 |
|---|---|
PollJobTaskLoop | 向 getTask 端点 POST{user, device},解析tasks数组并入队 |
ExecuteSequentialJobLoop | 从顺序队列取出任务执行,执行完向 reportStatus 上报 |
ExecuteInstantJobLoop | 从立即队列取出任务执行,执行完向 reportStatus 上报 |
这就是文档所说"立即执行任务可以在顺序任务运行中执行"的机制来源——立即任务走独立队列和独立循环,与顺序队列互不阻塞。
4.2 任务入队与 id 去重
PollJobTaskLoop中,对每个下发的任务先取type(空则跳过),再用_enqueueTaskIds列表做去重(RemoteControlService.cs):
LinkStart、LinkStart-*、Toolbox-*、CaptureImage、Settings-*进入_sequentialTaskQueue;CaptureImageNow、HeartBeat、StopTask进入_instantTaskQueue;- 已见过的 id 直接跳过——这就是"相同 id 不会重复执行"的去重实现,也意味着你可以放心地让 getTask 重入并重复返回历史任务;
- 无法识别的 type 不会入队,而是解锁一个
NotFound404成就(源码中的小彩蛋)。
4.3 各任务的执行细节
从ExecuteSequentialJobLoop的 switch 分支(RemoteControlService.cs)可以看到每类任务的实际行为:
- LinkStart / LinkStart-*:先等待当前实例进入空闲(
_runningState.UntileIdleAsync),随后调用私有的LinkStart方法。该方法与主界面"一键长草"按钮行为一致,但只执行指定子集:连接模拟器 → 按名称(Base/WakeUp/Combat/Recruiting/Mall/Mission/AutoRoguelike/Reclamation)序列化对应任务配置 →AsstStart启动 → 再次等待空闲。对LinkStart-XX类型,它直接取type.Split('-')[1]得到子功能名,完全绕过主界面的勾选框,这正是文档所说"无视主界面上该功能的勾选框"的实现。 - Toolbox-GachaOnce / GachaTenTimes:等待空闲后分别调用
ToolboxViewModel.GachaOnce()/GachaTenTimes(),完成后再次等待空闲。 - CaptureImage / CaptureImageNow:先
AsstConnect建立连接,再用AsstGetFreshImageAsync抓取一帧图像,经PngBitmapEncoder编码为 PNG 字节流后 Base64 放入 payload;连接失败或取图失败则status = "FAILED"。两类截图任务的代码路径几乎相同,区别仅在走哪个队列。 - Settings-ConnectAddress:把
params写入SettingsViewModel.ConnectSettings.ConnectAddress; - Settings-Stage1:把
params写入第一作战任务FightTask.StagePlan[0].Stage(若作战方案不止一个,会先清空只保留一个)。
两个值得注意的实现事实:
- 上报请求的字段名是
task而非id(new { user, device, status, task = id, payload }),与你后端收到的task字段一致; - StopTask 的"不等待确认"语义在源码中体现得很直白:调用
AsstStop()后注释明确写着"无需等待,甩出任务即可返回,远端应该用心跳来确认界面卡死和取消是否成功"。 - HeartBeat 的 payload就是成员变量
_currentSequentialTaskId——即顺序队列当前正在执行的任务 id;没有任务时为空字符串。这为远端提供了判断"MAA 是否卡死在某任务"的探针:持续轮询 HeartBeat,若 payload 长期不变且 StopTask 后仍不变,即可判定执行端无响应。
4.4 连接测试、端点校验与设备标识
设置页面上的"测试连接"按钮绑定到RemoteControlService.ConnectionTest(RemoteControlService.cs):它向 getTask 端点发送一次与真实轮询相同格式的 POST 请求(Accept: application/json),只看 HTTP 状态码——非 2xx 就弹出"连接测试失败,原因: {状态码}",异常则弹出异常信息,成功则提示"连接测试成功!"。这意味着你的后端只需保证 getTask 对"合法用户"返回 200(哪怕 tasks 为空),测试连接就能通过。
界面侧还有两个与协议相关的能力(RemoteControlUserControl.xaml):
- 设备标识符为只读文本框,旁边有"重新生成"按钮;
- 界面上方有固定提示:"随意填入未知来源的地址可能会导致您的账户受到损失"——远程控制端点拥有执行任务、修改配置的权限,只应指向自己信任的服务。
另外一个本地安全细节:四个文本框的输入值(两个端点、用户标识、设备标识)在写入本地配置文件前会经 SimpleEncryptionHelper 使用系统数据保护 API(DPAPI,CurrentUser 范围)加密存储(见 RemoteControlUserControlModel.cs)。这只保护本地配置文件不被直接读取明文,网络传输层面的安全仍然依赖你选择 https。
五、MAA 客户端的配置步骤
作为被控端,配置流程如下(对应设置界面"远程控制"页签):
- 在
获取任务端点填入 getTask 的完整 http(s) 地址; - 在
汇报任务端点填入 reportStatus 的完整 http(s) 地址; - 在
用户标识符中填写你自定义的标识(如 QQ 号、网站密钥); - 从只读的
设备标识符文本框复制 MAA 自动生成的 device id 并发送给你的后端; - 如需调整轮询频率,可修改
轮询间隔 (ms),默认 1000ms; - 点击
测试连接验证后端可达性。
界面还提供了一个指向远程控制开发者文档的链接(URL 常量见 MaaUrls.cs),方便开发者直接查阅本协议。
六、范例工作流一:用 QQBot 控制 MAA
文档给出了一个完整的 QQBot 控制场景,其核心思路是利用 getTask 请求天然携带 user/device 的特性,把轮询请求兼职当作"注册与认证"通道:
- 开发者部署后端,暴露两个端点:
https://myqqbot.com/maa/getTask与https://myqqbot.com/maa/reportStatus; - getTask 接口不管收到什么参数都默认返回 200OK 和空的 tasks 列表;
- 每次收到 getTask 请求时,后端检查数据库里有没有重复的 device:没有,就把该 device 与 user 记录入库。该接口由此同时承担了用户注册功能;
- Bot 提供一条 QQ 指令,让用户提交自己的 deviceId。使用说明告诉用户:在 MAA 的
用户标识符中填写自己的 QQ 号,再把设备标识符通过 QQ 聊天发给 Bot; - Bot 收到标识符后,按消息中的 QQ 号(user)在数据库查找记录。找不到,则提示用户先去配置 MAA。由于 MAA 配置好后会持续发轮询请求,只要用户填对了 user 与 device,数据库里必然有匹配记录;
- 匹配成功后,Bot 给该记录打上"已验证"标记。此后 getTask 再收到这套 device + user 请求时,才返回真正的任务列表;
- 用户在 QQ 上提交指令后,Bot 将一条任务写入数据库,稍后 getTask 轮询即可拉到它。该 Bot 还在每次提交指令后默认追加一个截图任务;
- MAA 执行完任务后调用 reportStatus 汇报结果,Bot 收到后在 QQ 端发送消息通知用户并展示截图。
这个工作流展示了协议的三个实用特性:空 tasks + 200 可作为"握手未通过"的通用响应;轮询请求可兼作设备注册;截图任务 + reportStatus 的 payload 天然构成"执行结果可视化"闭环。
七、范例工作流二:用网站批量管理 MAA
第二个场景是通过自有网站的批量管理,与 QQBot 例子的区别在于认证模型:
- 网站暴露
https://mywebsite.com/maa/getTask与https://mywebsite.com/maa/reportStatus; - 网站自带用户管理系统,界面上有"连接 MAA 实例"功能:展示一个开发者称之为
用户密钥的随机字符串,并提供填入设备 id 的文本框; - 用户要求:在 MAA 的
用户标识符中填写自己的用户密钥,把设备标识符填入网站; - 只有网站上成功创建了 MAA 连接(密钥 + device 配对登记)后,getTask 才返回 200OK,其他情况一律返回 401Unauthorized。这样如果用户在 MAA 上填错了密钥或设备 id,按下"测试连接"按钮就会得到失败提示,形成即时反馈;
- 用户即可在网站上为任意实例下发任务、排队、查看截图——实现方式与 QQBot 例子相同,全部通过 getTask / reportStatus 两个端点组合完成。
八、后端实现的要点清单
综合协议文档与源码行为,开发一个符合规范的 MAA 远程控制后端需要满足:
| 要求 | 说明 |
|---|---|
| 端点协议 | 必须 http(s);公网务必使用 https,否则 MAA 每次连接都会报警告 |
| getTask | 接受匿名 POST,请求体含user、device;响应必须含tasks数组,空闲时返回空数组并给 200 |
| 可重入 | getTask 会被以 1 秒(默认)为间隔反复轮询,必须幂等、无状态或可从数据库重建任务列表;重复返回相同 id 是安全的 |
| 任务去重 | MAA 侧已按 id 去重,后端只需维护"待下发任务"集合,下发后可保留(MAA 不会重复执行)或移除 |
| reportStatus | 接受user/device/task/status/payload;返回内容 MAA 完全不读取,按"fire and forget"处理 |
| 大报文 | 截图任务汇报的 payload 是 PNG 的 Base64,可达数十 MB,需调大网关/反代的 body 大小限制 |
| 停止确认 | StopTask 不保证停止完成,需用 HeartBeat 轮询确认当前执行任务 id 变化或为空 |
| 认证策略 | 参考两个范例:空 tasks + 200(QQBot 式注册)或 401 拦截(网站式密钥校验),二者都能与"测试连接"按钮正确配合 |
【免费下载链接】MaaAssistantArknights《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients.项目地址: https://gitcode.com/GitHub_Trending/ma/MaaAssistantArknights
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考