js:带状态机的账号切换 (#2949)

* js:带状态机的账号切换

使用状态机管理账号切换过程中的各种页面
提高流程稳定性并方便管理和新增异常处理
支持自动截图保存,双模式切换账号

* Update main.js

将状态切换需要用到的函数挂载到globalThis,以适配AutoJs作为其中的账号切换工具

* 修复提到的问题

* Update main.js

修复提到的问题

* Update main.js

* Update main.js

* Update main.js
This commit is contained in:
mno
2026-03-04 18:35:10 +08:00
committed by GitHub
parent 597098a476
commit b69f61d3dd
31 changed files with 1846 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
# 账号切换工具使用说明
## 快速开始
### 方式一:账号图片识别(推荐)
1. **准备账号图片**
- **推荐方式**:使用脚本的"截图模式"自动获取并保存截图(详见截图模式部分)
- **手动方式**:在 `accounts/` 目录下放置账号截图
- 图片命名为 `{UID}.png`,如 `284073800.png`
- 截图应包含账号选择界面中该账号的明显标识
2. **配置脚本**
- 打开BetterGI配置面板
- 填写目标账号UID
- 确保"跳过搜索直接输入账号密码"未勾选
3. **运行脚本**
- 脚本会自动定位到账号选择界面
- 识别并点击对应账号图片
- 自动进入游戏并返回主界面
### 方式二:账号密码输入
1. **配置脚本**
- 打开BetterGI配置面板
- 填写目标账号UID用于通知显示
- 填写账号和密码
- 勾选"跳过搜索直接输入账号密码"
2. **运行脚本**
- 脚本会自动进入账号密码输入界面
- 自动点击同意按钮、输入账号密码
- 自动进入游戏并返回主界面
### 说明
- **两种方式可同时使用**:方式二作为方式一的补充,当图片识别失败时会自动回退到账号密码方式
- **推荐配置**:同时填写账号密码作为备用,提高切换成功率
## 截图模式
截图模式是一个辅助功能,用于自动获取并保存账号图片,方便后续使用账号图片识别方式进行切换。
**使用步骤**
1. **配置脚本**
- 打开BetterGI配置面板
- 填写目标账号UID优先使用用于命名图片
- 勾选"截图模式"
2. **运行脚本**
- 脚本会优先使用配置中的"要切换到的账号UID"命名图片
- 如果未填写该项会尝试自动识别当前账号的UID
- 导航到账号选择界面
- 自动截图并保存账号图片
- 返回主界面并发送成功通知
**保存位置**
- 图片保存在 `accounts/` 目录下
- 文件名格式为 `{UID}.png`
## 配置说明
在BetterGI的配置面板中进行以下设置
### 1. 目标账号UID
- 输入要切换到的账号UID
- 用于识别账号图片文件名、通知显示和截图模式命名
### 2. 账号(可选)
- 当无法通过图片识别找到账号时备用
- 建议填写,以防图片识别失败
### 3. 密码(可选)
- 当无法通过图片识别找到账号时备用
- 建议填写,以防图片识别失败
### 4. 跳过搜索直接输入账号密码
- **勾选**:不尝试查找账号图片,直接使用账号密码登录
- **不勾选**:优先尝试通过账号图片识别切换,失败时回退到账号密码方式
### 5. UID校验
- **勾选**在切换前后检查当前账号UID辅助判断切换是否成功
- **不勾选**不进行UID校验
### 6. 截图模式
- **勾选**进入截图模式自动截图保存对应UID的账号图片
- **不勾选**:正常执行账号切换流程
## 常见问题
### Q: 账号图片识别失败怎么办?
A: 确保:
1. 图片存放在 `accounts/` 目录下
2. 图片命名为正确的UID格式
3. 截图清晰,包含账号的明显标识
4. 同时填写账号密码作为备用方案
### Q: 切换失败后会怎样?
A: 脚本会尝试返回主界面,并发送失败通知。如果配置了账号密码,建议手动检查账号状态。
### Q: 为什么需要填写UID
A: UID用于
1. 确定账号图片文件名
2. 在通知中显示切换的账号信息
3. 截图模式下命名保存的图片文件
## 注意事项
1. 首次使用前建议先手动测试一遍切换流程
2. 确保游戏分辨率为1920x1080
3. 账号图片建议截取账号选择界面中该账号的清晰截图
4. 密码输入时不会显示,属于正常现象
5. 截图模式下默认使用设置的目标UID未设置时会尝试自动识别
## 问题反馈
如果在使用过程中遇到预料之外的弹窗或其他问题,脚本可能无法处理,请按照以下方式反馈:
1. **截图保存**:截取全屏原图,确保包含完整的游戏界面和弹窗
2. **联系作者**QQ号 718135749
3. **提供信息**:简要描述遇到的问题和操作步骤
感谢您的支持!
<details>
<summary>项目介绍(点击展开)</summary>
## 项目介绍
基于状态机实现的自动账号切换脚本,支持通过账号图片识别或手动输入账号密码两种方式切换原神账号。
### 功能特性
- **双模式切换**:支持账号图片自动识别和账号密码手动输入两种方式
- **状态机管理**:使用有限状态机管理切换流程,稳定可靠
- **自动返回**:切换失败时自动尝试返回原账号
- **通知提醒**:切换成功或失败时发送系统通知
- **循环防护**:防止状态循环卡死
- **UID校验**支持切换前后检查当前账号UID提高切换准确性
- **截图模式**:自动截图保存账号图片,方便后续切换
### 目录结构
```
AccountSwitchStateMachine/
├── main.js # 主脚本文件
├── manifest.json # 脚本清单
├── settings.json # 配置定义
├── assets/
│ ├── states.json # 状态机配置
│ └── RecognitionObjects/ # 状态识别图片
│ ├── Paimon.png
│ ├── Menu.png
│ ├── PrepareToLogOut.png
│ ├── SwitchAccount.png
│ ├── Exit.png
│ ├── EnterGame.png
│ ├── LoginOther.png
│ ├── SelectAccount.png
│ ├── EnterAccountAndPassword.png
│ ├── Agree.png
│ ├── EnterAccount.png
│ ├── EnterPassword.png
│ ├── EnterGame2.png
│ ├── Login.png
│ └── Confirm.png
└── accounts/ # 账号图片存放目录
└── {uid}.png # 以UID命名的账号截图
```
### 工作流程
```
开始
├─► 截图模式(勾选时)
│ ├─► 使用设置的UID或识别当前UID
│ ├─► 导航到账号选择界面
│ ├─► 截图保存账号图片
│ └─► 返回主界面
│ ├─► 成功 → 发送成功通知
│ └─► 失败 → 发送失败通知
├─► 尝试账号图片方式(未勾选跳过时)
│ ├─► 预加载账号图片
│ ├─► 切换到选择账号界面
│ ├─► 查找并点击账号图片
│ └─► 切换到主界面
│ ├─► 成功 → 发送成功通知
│ └─► 失败 → 回退到账号密码方式
└─► 账号密码方式
├─► 检查账号密码
├─► 切换到输入账号密码界面
├─► 点击同意按钮
├─► 输入账号密码
├─► 点击进入游戏
└─► 切换到主界面
├─► 成功 → 发送成功通知
└─► 失败 → 尝试返回原账号 → 发送失败通知
```
### 状态说明
| 状态名 | 说明 |
|--------|------|
| mainUI | 主界面(派蒙图标) |
| menuUI | 菜单界面ESC菜单 |
| prepareToLogOut | 预备退出登录界面 |
| loginScreen | 登录界面(切换账号按钮) |
| exitAccount | 退出账号界面 |
| enterGame | 进入游戏或登录其他账号界面 |
| selectAccount | 选择账号界面 |
| enterAccountAndPassword | 输入账号密码界面 |
| noAccount | 无账号登录界面 |
### 更新日志
#### v1.0
- 初始版本
- 实现状态机管理
- 支持账号图片和账号密码两种切换方式
- 添加自动返回和通知功能
</details>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

View File

@@ -0,0 +1,412 @@
[
{
"name": "mainUI",
"description": "处于主界面",
"transitions": [
{
"targetState": "menuUI",
"action": "keyPress('VK_ESCAPE'); await sleep(1000);"
}
],
"detection": {
"conditions": [
{
"id": "paimon",
"template": "assets/RecognitionObjects/Paimon.png",
"region": {
"x": 0,
"y": 0,
"width": 150,
"height": 150
}
}
],
"logic": "paimon"
}
},
{
"name": "menuUI",
"description": "处于菜单界面",
"transitions": [
{
"targetState": "mainUI",
"action": "await genshin.returnMainUi();"
},
{
"targetState": "prepareToLogOut",
"action": "click(50, 1025); await findAndClick('assets/RecognitionObjects/PrepareToLogOut.png', false);"
}
],
"detection": {
"conditions": [
{
"id": "menu",
"template": "assets/RecognitionObjects/Menu.png",
"region": {
"x": 0,
"y": 0,
"width": 150,
"height": 150
}
},
{
"id": "prepareToLogOut",
"template": "assets/RecognitionObjects/PrepareToLogOut.png",
"region": {
"x": 646,
"y": 488,
"width": 639,
"height": 97
}
}
],
"logic": "menu && !prepareToLogOut"
}
},
{
"name": "prepareToLogOut",
"description": "预备退出登录",
"transitions": [
{
"targetState": "menuUI",
"action": "await findAndClick('assets/RecognitionObjects/PrepareToLogOut.png');"
},
{
"targetState": "loginScreen",
"action": "await findAndClick('assets/RecognitionObjects/PrepareToLogOut.png'); await sleep(500); await findAndClick('assets/RecognitionObjects/SwitchAccount.png', false, 30000);"
}
],
"detection": {
"conditions": [
{
"id": "prepareToLogOut",
"template": "assets/RecognitionObjects/PrepareToLogOut.png",
"region": {
"x": 646,
"y": 488,
"width": 639,
"height": 97
}
}
],
"logic": "prepareToLogOut"
}
},
{
"name": "loginScreen",
"description": "登录界面",
"transitions": [
{
"targetState": "exitAccount",
"action": "await findAndClick('assets/RecognitionObjects/SwitchAccount.png');"
},
{
"targetState": "mainUI",
"action": "click(10, 10); let attempts = 0; const maxAttempts = 30; while (!await findAndClick('assets/RecognitionObjects/Paimon.png', false, 200) && attempts < maxAttempts) { attempts++; await sleep(5000); click(10, 10); }"
}
],
"detection": {
"conditions": [
{
"id": "switchAccount",
"template": "assets/RecognitionObjects/SwitchAccount.png",
"region": {
"x": 1783,
"y": 940,
"width": 100,
"height": 100
}
},
{
"id": "exit",
"template": "assets/RecognitionObjects/Exit.png",
"region": {
"x": 911,
"y": 600,
"width": 341,
"height": 173
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "switchAccount && !exit && !confirm"
}
},
{
"name": "exitAccount",
"description": "退出账号界面",
"transitions": [
{
"targetState": "enterGame",
"action": "await findAndClick('assets/RecognitionObjects/Exit.png');"
}
],
"detection": {
"conditions": [
{
"id": "exit",
"template": "assets/RecognitionObjects/Exit.png",
"region": {
"x": 911,
"y": 600,
"width": 341,
"height": 173
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "exit && !confirm"
}
},
{
"name": "enterGame",
"description": "进入游戏或登录其他账号",
"transitions": [
{
"targetState": "selectAccount",
"action": "click(974,496);"
},
{
"targetState": "enterAccountAndPassword",
"action": "await findAndClick('assets/RecognitionObjects/LoginOther.png');"
},
{
"targetState": "loginScreen",
"action": "await findAndClick('assets/RecognitionObjects/EnterGame.png');"
}
],
"detection": {
"conditions": [
{
"id": "enterGame",
"template": "assets/RecognitionObjects/EnterGame.png",
"region": {
"x": 643,
"y": 565,
"width": 652,
"height": 186
}
},
{
"id": "loginOther",
"template": "assets/RecognitionObjects/LoginOther.png",
"region": {
"x": 643,
"y": 565,
"width": 652,
"height": 186
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "enterGame && loginOther && !confirm"
}
},
{
"name": "selectAccount",
"description": "选择账号界面",
"transitions": [
{
"targetState": "enterGame",
"action": "click(957, 404);"
}
],
"detection": {
"conditions": [
{
"id": "enterGame",
"template": "assets/RecognitionObjects/EnterGame.png",
"region": {
"x": 643,
"y": 565,
"width": 652,
"height": 186
}
},
{
"id": "loginOther",
"template": "assets/RecognitionObjects/LoginOther.png",
"region": {
"x": 643,
"y": 565,
"width": 652,
"height": 186
}
},
{
"id": "selectAccount",
"template": "assets/RecognitionObjects/SelectAccount.png",
"region": {
"x": 642,
"y": 441,
"width": 644,
"height": 580
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "!enterGame && !loginOther && selectAccount && !confirm"
}
},
{
"name": "enterAccountAndPassword",
"description": "输入账号密码界面",
"transitions": [
{
"targetState": "noAccount",
"action": "click(1260, 259);"
}
],
"detection": {
"conditions": [
{
"id": "enterAccountAndPassword",
"template": "assets/RecognitionObjects/EnterAccountAndPassword.png",
"region": {
"x": 0,
"y": 0,
"width": 1920,
"height": 1080
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "enterAccountAndPassword && !confirm"
}
},
{
"name": "ageConfirmation",
"description": "适龄提示界面",
"transitions": [
{
"targetState": "noAccount",
"action": "await findAndClick('assets/RecognitionObjects/Confirm.png');"
}
],
"detection": {
"conditions": [
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "confirm"
}
},
{
"name": "noAccount",
"description": "无账号登录界面",
"transitions": [
{
"targetState": "enterGame",
"action": "await findAndClick('assets/RecognitionObjects/Login.png');"
}
],
"detection": {
"conditions": [
{
"id": "login",
"template": "assets/RecognitionObjects/Login.png",
"region": {
"x": 1776,
"y": 934,
"width": 100,
"height": 100
}
},
{
"id": "enterGame",
"template": "assets/RecognitionObjects/EnterGame.png",
"region": {
"x": 643,
"y": 565,
"width": 652,
"height": 186
}
},
{
"id": "selectAccount",
"template": "assets/RecognitionObjects/SelectAccount.png",
"region": {
"x": 642,
"y": 441,
"width": 644,
"height": 580
}
},
{
"id": "enterAccountAndPassword",
"template": "assets/RecognitionObjects/EnterAccountAndPassword.png",
"region": {
"x": 0,
"y": 0,
"width": 1920,
"height": 1080
}
},
{
"id": "confirm",
"template": "assets/RecognitionObjects/Confirm.png",
"region": {
"x": 734,
"y": 826,
"width": 439,
"height": 116
}
}
],
"logic": "login && !enterGame && !selectAccount && !enterAccountAndPassword && !confirm"
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"manifest_version": 1,
"name": "带状态机的账号切换",
"version": "1.0",
"description": "",
"authors": [
{
"name": "mno"
}
],
"settings_ui": "settings.json",
"main": "main.js"
}

View File

@@ -0,0 +1,32 @@
[
{
"name": "targetUid",
"type": "input-text",
"label": "要切换到的账号UID"
},
{
"name": "account",
"type": "input-text",
"label": "账号(非必要,查找不到账号图片时备用)"
},
{
"name": "password",
"type": "input-text",
"label": "密码(非必要,查找不到账号图片时备用)"
},
{
"name": "skipSearch",
"type": "checkbox",
"label": "勾选后不尝试查找账号图片,直接使用账号密码登录"
},
{
"name": "verifyUid",
"type": "checkbox",
"label": "勾选后在切换前后检查当前账号UID辅助判断切换是否成功"
},
{
"name": "screenshotMode",
"type": "checkbox",
"label": "勾选后进入截图模式自动截图保存对应UID的账号图片"
}
]