diff --git a/MAA_README.md b/MAA_README.md new file mode 100644 index 0000000..1a54c5c --- /dev/null +++ b/MAA_README.md @@ -0,0 +1,128 @@ +--- +icon: ph:sword-bold +--- +# 远程控制协议 + +要实现对MAA的远程控制,你需要提供一个服务,该服务必须是https服务,并且提供下面两个可匿名访问的端点(Endpoint)。 + +## 获取任务端点 + +MAA会以1秒的间隔持续轮询这个端点,尝试获取他要执行的任务,并按照获取到的列表按顺序执行。 + +端点路径随意,但是必须是https端点。比如:`https://your-control-host.net/maa/getTask` + +被控MAA需要将该端点填写到MAA配置的`获取任务端点`文本框中。 + +该端点必需能够接受一个Content-Type=application/json的POST请求,并该请求必须可以接受下面这个Json作为请求的content: + +::: tip +请注意 JSON 文件是不支持注释的,文本中的注释仅用于演示,请勿直接复制使用 +::: + +```json +{ + "user":"ea6c39eb-a45f-4d82-9ecc-33a7bf2ae4dc", // 用户在MAA设置中填写的用户标识符。 + "device":"f7cd9682-3de9-4eef-9137-ec124ea9e9ec" // MAA自动生成的设备标识符。 + ... // 如果你的这个端点还有其他用途,你可以自行添加可选的参数,但是MAA只会传递user和device +} +``` + +该端点必须返回一个Json的Response,并且至少要满足下列格式: + +::: tip +请注意 JSON 文件是不支持注释的,文本中的注释仅用于演示,请勿直接复制使用 +::: + +```json +{ + "tasks": // 需要让MAA执行的Task的列表,目前可以支持的类型如示例中所示,如果不存在tasks则视为连接无效。 + [ + { + "id": "b353c469-b902-4357-bd8f-d133199eea31", //任务的唯一id,字符串类型,在汇报任务时会使用 + "type": "CaptureImage", //截图任务,会截取一张当前模拟器的截图,并以Base64字符串的形式放在汇报任务的payload里。如果你需要下发这种类型的任务,请务必注意你的端点可接受的最大请求大小,因为截图会有数十MB,会超过一般网关的默认大小限制。 + }, + { + "id": "15be4725-5bd3-443d-8ae3-0a5ae789254c", //任务的唯一id,字符串类型,在汇报任务时会使用 + "type": "LinkStart-Recruiting", //一键长草->自动公招。立即根据当前配置,执行一键长草中的对应子功能,无视主界面上该功能的勾选框。 + }, + + ], + ... // 如果你的这个端点还有其他用途,你可以自行添加可选的返回值,但是MAA只会读取tasks +} +``` + +这些任务会被按顺序执行,也就是说如果你先发下一个公招任务,再发下一个截图任务,则截图会在公招任务结束后执行。 +该端点应当可以重入并且重复返回需要执行的任务,MAA会自动记录任务Id,对于相同的Id,不会重复执行。 + +## 汇报任务端点 + +每当MAA执行完一个任务,他就会通过该端点将任务的执行结果汇报给远端。 + +端点路径随意,但是必须是https端点。比如:`https://your-control-host.net/maa/reportStatus` + +被控MAA需要将该端点填写到MAA配置的`汇报任务端点`文本框中。 + +该端点必需能够接受一个Content-Type=application/json的POST请求,并该请求必须可以接受下面这个Json作为请求的content: + +::: tip +请注意 JSON 文件是不支持注释的,文本中的注释仅用于演示,请勿直接复制使用 +::: + +```json +{ + "user":"ea6c39eb-a45f-4d82-9ecc-33a7bf2ae4dc", // 用户在MAA设置中填写的用户标识符。 + "device":"f7cd9682-3de9-4eef-9137-ec124ea9e9ec", // MAA自动生成的设备标识符。 + "task":"15be4725-5bd3-443d-8ae3-0a5ae789254c", // 要汇报的任务的Id,和获取任务时的Id对应。 + "status":"SUCCESS", // 任务执行结果,SUCCESS或者FAILED。一般不论任务执行成功与否只会返回SUCCESS,只有特殊情况才会返回FAILED,会返回FAILED的情况,会在上面的任务介绍时明确说明。 + "payload":"", //汇报时携带的数据,字符串类型。具体取决于任务类型,比如截图任务汇报时,这里就会携带截图的Base64字符串。 + ... // 如果你的这个端点还有其他用途,你可以自行添加可选的参数,但是MAA只会传递user和device +} +``` + +该端点的返回内容任意,但是如果你不返回200OK,会在MAA端弹出一个Notification,显示`上传失败` + +## 范例工作流-用QQBot控制MAA + +A开发者想要用自己的QQBot控制MAA,于是他开发了一个后端,暴露在公网上,提供两个端点: + +``` +https://myqqbot.com/maa/getTask +https://myqqbot.com/maa/reportStatus。 +``` + +为了让用户用的更方便,他的getTask接口不管接收什么参数都默认返回200OK和一个空的tasks列表。 +每次他接收到一个请求,他就去数据库里看一下有没有重复的device,如果没有,他就将该device和user记录在数据库。 +也就是说,在这个工作流下,这个接口同时还承担了用户注册的功能。 + +他在QQBot上提供了一条指令,供用户提交自己的deviceId。 + +在它的QQBot的使用说明上,他告诉用户,在MAA的`用户标识符`中填写自己的QQ号,然后将`设备标识符`通过QQ聊天发送给Bot。 + +QQBot在收到标识符后,再根据消息中的用户QQ号,寻找数据库中是否有对应的数据,如果没有,则叫用户先去配置MAA。 + +因为MAA在配置好后就会持续的发送请求,因此如果用户配置好了MAA,在他通过QQ提交时,数据库内应该有匹配的记录。 + +这时Bot将数据库内的该记录设置一个已验证标记,未来getTask再使用这套device和user请求时,就会返回真正的任务列表。 + +当用户通过QQBot提交指令后,Bot将一条任务写入数据库,这样稍后,getTask就会返回这条任务。并且,该QQbot还很贴心的,在每次用户提交指令后,都默认再附加一个截图任务。 + +MAA在任务执行完后,会调用reportStatus汇报结果,Bot在收到结果后,在QQ端发送消息通知用户以及展示截图。 + +## 范例工作流-用网站控制MAA + +B开发者写了一个网站,设想通过网站批量管理MAA,因此,他拥有一套自己的用户管理系统。但是它的后端在公网上,提供两个可匿名访问的端点: + +``` +https://mywebsite.com/maa/getTask +https://mywebsite.com/maa/reportStatus。 +``` + +在网站上,有个连接MAA实例的界面,会展示一个B开发者称之为`用户密钥`的随机字符串,并有一个填入设备id的文本框。 + +网站要求用户在MAA的的`用户标识符`中填写自己的用户密钥,然后将`设备标识符`填入网站。 + +只有在网站上成功创建了MAA连接,getTask才会返回200OK,其他时候都返回401Unauthorized。 + +因此如果用户在MAA上填错了,按下测试连接按钮,会得到测试失败的提示。 + +用户可以在网站上下发任务,为任务排队,查看截图等等,这些功能的实现和上面QQBot例子类似,都是通过getTask和reportStatus组合完成。 diff --git a/README.md b/README.md index 3a42a61..095e993 100644 --- a/README.md +++ b/README.md @@ -47,29 +47,59 @@ 我是兔兔的群友 -1. 你需要下载特殊版本的MAA.exe文件并用它替换MAA文件夹下的对应文件。 -2. 启动这个特制的MAA,确定能用它连接模拟器并配置好了各项功能。至少配置好一键长草并确定可以执行。具体流程请参照MAA的教程。 -3. 打开MAA的设置,进入“远程控制”,在用户标识符中填入你的QQ号。 -4. 如果设备标识符为空,按一下旁边的重新生成按钮,生成一个。 -5. 在群聊中说`兔兔如何连接MAA`,兔兔会回复你要填写的地址。 -6. 将兔兔说的地址,填写到MAA中`远程控制`设置的`获取任务端点`和`汇报任务端点`文本框里 -7. 复制设备标识符,然后到群里,说`兔兔记录MAA设备<设备标识符>`让兔兔记住。不必担心其他人看到,他们就算复制了这个标识符也没用。 +1. 首先你需要下载安装一个MAA,目前仅支持Windows版本。 +2. 你需要下载特殊版本的MAA.exe文件并用它替换MAA文件夹下的对应文件。 +3. 启动这个特制的MAA,确定能用它连接模拟器并配置好了各项功能。至少配置好一键长草并确定可以执行。具体流程请参照MAA的教程。 +4. 打开MAA的设置,进入“远程控制”,在用户标识符中填入你的QQ号。 +5. 如果设备标识符为空,按一下旁边的重新生成按钮,生成一个。 +6. 在群聊中说`兔兔如何连接MAA`,兔兔会回复你要填写的地址。 +7. 将兔兔说的地址,填写到MAA中`远程控制`设置的`获取任务端点`和`汇报任务端点`文本框里 +8. 复制设备标识符,然后到群里,说`兔兔记录MAA设备<设备标识符>`让兔兔记住。不必担心其他人看到,他们就算复制了这个标识符也没用。 ![记住密钥](https://raw.githubusercontent.com/hsyhhssyy/amiyabot-arknights-hsyhhssyy-maa/master/docs/remember_did.png) -8. 接下来就是挂机,然后去群聊里和兔兔聊天吧。 +9. 接下来就是挂机,然后去群聊里和兔兔聊天吧。 ## 兔兔支持的命令 +### 独立功能: + | 命令 | 说明 | 引入版本 | | ---- | ---- | ---- | | 兔兔MAA一键长草 | 执行MMA的一键长草功能,等效于按下主界面的LinkStart | 1.0 | | 兔兔MAA截图 | 在当前所有任务执行完毕后截图并返回给你,可以用于帮你了解任务什么时候执行完。 | 1.0 | +### 一键长草子功能 + +下面的功能相当于单独执行一键长草中的对应子功能,不过会无视主界面上的勾选框。也就是说哪怕你没有勾选自动肉鸽,你也可以发送`兔兔MAA自动肉鸽`。 +各个项目的配置遵循一键长草中的配置。 + +| 命令 | 说明 | 引入版本 | +| ---- | ---- | ---- | +| 兔兔MAA基建换班 | 无 | 2.1 | +| 兔兔MAA开始唤醒 | 无,这个功能好像没啥用 | 2.1 | +| 兔兔MAA刷理智 | 无 | 2.1 | +| 兔兔MAA自动公招 | 无 | 2.1 | +| 兔兔MAA获取信用及购物 | 无 | 2.1 | +| 兔兔MAA领取奖励 | 无 | 2.1 | +| 兔兔MAA自动肉鸽 | 记得去高级设置里,把执行次数调小不然任务停不下来 | 2.1 | +| 兔兔MAA生息演算 | 因为活动未开所以未测试! | 2.1 | + +### 小工具子功能 + +| 命令 | 说明 | 引入版本 | +| ---- | ---- | ---- | +| 兔兔MAA十连抽 | 进行一次十连抽(这是真实抽卡!) | 2.1 | +| 兔兔MAA单抽 | 进行一次单抽(这是真实抽卡!) | 2.1 | + 截图存储在resource/maa-adapter/screenshots文件夹下,请注意定时清理。 更多命令请等待后续推出。 +## 我也想做一个控制端 + +如果也想要利用这个特制的MAA.exe实现一个自己的控制端,你可以 [看这里](https://github.com/hsyhhssyy/amiyabot-arknights-hsyhhssyy-maa/blob/master/MAA_README.md) + ## 鸣谢 > [插件项目地址:Github](https://github.com/hsyhhssyy/amiyabot-arknights-hsyhhssyy-maa/) diff --git a/README_USE.md b/README_USE.md index f445d16..e4b14de 100644 --- a/README_USE.md +++ b/README_USE.md @@ -1,13 +1,14 @@ ## 如何连接 -1. 你需要下载特殊版本的MAA.exe文件并用它替换MAA文件夹下的对应文件。 -2. 启动这个特制的MAA,确定能用它连接模拟器并配置好了各项功能。至少配置好一键长草并确定可以执行。具体流程请参照MAA的教程。 -3. 打开MAA的设置,进入“远程控制”,在用户标识符中填入你的QQ号。 -4. 如果设备标识符为空,按一下旁边的重新生成按钮,生成一个。 -5. 在群聊中说`兔兔如何连接MAA`,兔兔会回复你要填写的地址。 -6. 将兔兔说的地址,填写到MAA中`远程控制`设置的`获取任务端点`和`汇报任务端点`文本框里 -7. 复制设备标识符,然后到群里,说`兔兔记录MAA设备<设备标识符>`让兔兔记住。不必担心其他人看到,他们就算复制了这个标识符也没用。 -8. 接下来就是挂机,然后去群聊里和兔兔聊天吧。 +1. 首先你需要下载安装一个MAA,目前仅支持Windows版本。 +2. 你需要下载特殊版本的MAA.exe文件并用它替换MAA文件夹下的对应文件。 +3. 启动这个特制的MAA,确定能用它连接模拟器并配置好了各项功能。至少配置好一键长草并确定可以执行。具体流程请参照MAA的教程。 +4. 打开MAA的设置,进入“远程控制”,在用户标识符中填入你的QQ号。 +5. 如果设备标识符为空,按一下旁边的重新生成按钮,生成一个。 +6. 在群聊中说`兔兔如何连接MAA`,兔兔会回复你要填写的地址。 +7. 将兔兔说的地址,填写到MAA中`远程控制`设置的`获取任务端点`和`汇报任务端点`文本框里 +8. 复制设备标识符,然后到群里,说`兔兔记录MAA设备<设备标识符>`让兔兔记住。不必担心其他人看到,他们就算复制了这个标识符也没用。 +9. 接下来就是挂机,然后去群聊里和兔兔聊天吧。 ## 兔兔支持的命令 diff --git a/main.py b/main.py index 4ff535e..b48e078 100644 --- a/main.py +++ b/main.py @@ -117,7 +117,7 @@ async def message_loop(): task = AmiyaBotMAATask.get(AmiyaBotMAATask.uuid == task_uuid) # log.info(f'{task.payload}') if task.payload is not None and task.payload != "": - await data.send(Chain(data).text('博士,指挥终端返回了如下截图:').image(task.payload)) + await data.send(Chain(data).text('博士,任务完成了。指挥终端返回了如下截图:').image(task.payload)) return asyncio.create_task(message_loop()) @@ -155,14 +155,13 @@ async def maa_fight(data: Message): wait_snapshot(data,task_uuid) -@bot.on_message(keywords=['一键长草'], level=5) -async def maa_fight(data: Message): - +async def assign_simple_task_with_snapshot(data,maa_type,mission_str): + valid, conn = await get_connection(data) if not valid: return - AmiyaBotMAATask.create(connection=conn.id, uuid=str(uuid.uuid4()), type="LinkStart", + AmiyaBotMAATask.create(connection=conn.id, uuid=str(uuid.uuid4()), type=maa_type, parameter="", status="ASSIGNED", create_at=datetime.now()) snapshot_task_uuid = str(uuid.uuid4()) @@ -171,4 +170,92 @@ async def maa_fight(data: Message): wait_snapshot(data,snapshot_task_uuid) - return Chain(data).text(f'博士,一键长草任务已布置,干员们会努力做好罗德岛的日常工作的,任务结束后将发送截图给您。') + return Chain(data).text(f'博士,{mission_str}任务已布置,干员们会努力做好罗德岛的日常工作的,任务结束后将发送截图给您。') + +@bot.on_message(keywords=['MAA一键长草'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart","一键长草") + +@bot.on_message(keywords=['MAA基建换班'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-Base","基建换班") + +@bot.on_message(keywords=['MAA开始唤醒'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-WakeUp","开始唤醒") + +@bot.on_message(keywords=['MAA刷理智'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-Combat","战斗") + +@bot.on_message(keywords=['MAA自动公招'], level=999) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-Recruiting","公开招募") + +@bot.on_message(keywords=['MAA获取信用及购物'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-Mall","购物") + +@bot.on_message(keywords=['MAA领取奖励'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-Mission","领取奖励") + +@bot.on_message(keywords=['MAA自动肉鸽'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-AutoRoguelike","集成战略") + +@bot.on_message(keywords=['MAA生息演算'], level=5) +async def maa_fight(data: Message): + return await assign_simple_task_with_snapshot(data,"LinkStart-ReclamationAlgorithm","生息演算") + +@bot.on_message(keywords=['MAA十连抽'], level=20) +async def maa_gacha(data: Message): + + valid, conn = await get_connection(data) + + if not valid: + return + + confirm = await data.wait(Chain(data).text('博士,阿米娅即将通过指挥终端在当前活动寻访(寻访界面最左侧的寻访)中为您执行寻访十次。(该寻访为真实寻访),请您回复“确认”确认操作。'), + True,30) + log.info(f'{confirm} {confirm.text}') + if confirm is not None and confirm.text=='确认': + await data.send(Chain(data).text('博士,寻访十次任务已经下发')) + AmiyaBotMAATask.create(connection=conn.id, uuid=str(uuid.uuid4()), type="Toolbox-GachaTenTimes", + parameter="", status="ASSIGNED", create_at=datetime.now()) + + task_uuid = str(uuid.uuid4()) + AmiyaBotMAATask.create(connection=conn.id, uuid=task_uuid, type="CaptureImage", + parameter=None, status="ASSIGNED", create_at=datetime.now()) + + wait_snapshot(data,task_uuid) + else: + await data.send(Chain(data).text('博士,寻访十次任务已取消')) + + return + +@bot.on_message(keywords=['MAA单抽'], level=20) +async def maa_gacha(data: Message): + + valid, conn = await get_connection(data) + + if not valid: + return + + confirm = await data.wait(Chain(data).text('博士,阿米娅即将通过指挥终端在当前活动寻访(寻访界面最左侧的寻访)中为您进行一次寻访。(该寻访为真实寻访),请您回复“确认”确认操作。'), + True,30) + log.info(f'{confirm} {confirm.text}') + if confirm is not None and confirm.text=='确认': + await data.send(Chain(data).text('博士,单次寻访任务已经下发')) + AmiyaBotMAATask.create(connection=conn.id, uuid=str(uuid.uuid4()), type="Toolbox-GachaOnce", + parameter="", status="ASSIGNED", create_at=datetime.now()) + + task_uuid = str(uuid.uuid4()) + AmiyaBotMAATask.create(connection=conn.id, uuid=task_uuid, type="CaptureImage", + parameter=None, status="ASSIGNED", create_at=datetime.now()) + + wait_snapshot(data,task_uuid) + else: + await data.send(Chain(data).text('博士,单次寻访任务已取消')) + + return \ No newline at end of file