体力计划JS (#2947)

* feat(AutoPlanDomain): 添加自动秘境计划功能

- 新增domain.json配置文件,包含天赋、武器、圣遗物三大类秘境数据
- 创建main.js核心逻辑文件,实现秘境数据映射和排序功能
- 添加manifest.json插件配置文件,定义插件基本信息和依赖
- 创建settings.json空配置文件用于后续设置界面
- 实现领域名称到物品列表的映射关系
- 支持按顺序排列的秘境配置功能

* feat(domain): 添加原神秘境自动规划功能

- 新增配置文件config.js用于管理秘境数据结构
- 实现秘境配置初始化和数据映射功能
- 添加domain.json配置文件包含所有秘境信息
- 实现秘境自动执行核心逻辑
- 添加秘境顺序列表初始化和排序功能
- 实现秘境批量执行处理机制
- 添加错误处理和配置验证机制

* feat(config): 添加设置初始化和多复选框配置管理功能

- 新增info对象用于存储manifest和settings信息
- 添加manifest.json路径配置
- 实现initSettings函数用于读取和解析设置文件
- 实现getMultiCheckboxMap函数用于获取多复选框映射表
- 实现getValueByMultiCheckboxName函数用于获取复选框组对应值
- 在main.js中引入并调用initSettings进行设置初始化
- 扩展导出接口包含新的设置管理函数

* fix(AutoPlanDomain): 解决秘境任务执行中的字符串转数字问题

- 修复了order变量解析时缺少空字符串转换的问题
- 添加了物品名称到秘境名称的映射查询功能
- 实现了域名临时变量获取和周日选择值更新逻辑
- 增加了输入错误时的异常处理机制
- 优化了秘境名称不存在时的错误提示信息

* feat(AutoPlanDomain): 添加自动秘境计划配置功能

- 在配置文件中添加manifest对象结构
- 为domainList添加默认空数组配置避免解析错误
- 重构order解析逻辑增加空值和类型验证保护
- 添加自动秘境计划配置设置项支持文本输入
- 实现配置初始化异步加载功能
- 增加多复选框映射表获取函数注释文档

* feat(AutoPlanDomain): 添加域配置文件支持并优化初始化逻辑

- 新增 domainConfig 配置项用于加载 ./config/domain_config.json 文件
- 在 initDomainOrderList 函数中添加对 domainConfig 的空值检查
- 修复变量名错误:将 DomainRoundNum 统一改为 domainRoundNum
- 添加秘境配置为空时的错误检查和提示
- 移除重复的秘境配置空值检查代码
- 优化代码结构和错误处理机制

* feat(config): 添加用户UID配置并优化秘境顺序列表初始化逻辑

- 在配置文件中添加用户UID字段用于个性化配置
- 将秘境顺序列表从数组改为Set以避免重复数据
- 实现基于UID的配置读取功能
- 添加配置文件解析逻辑支持多用户配置
- 修改返回逻辑将Set转换为数组确保兼容性
- 更新错误检查条件适配新的数据结构

* feat(config): 添加密钥验证功能

- 在配置中新增 key 字段用于存储密钥
- 实现 checkKey 函数用于验证密钥正确性
- 添加密钥错误时的异常处理机制
- 将 checkKey 函数导出供外部使用
- 添加初始化秘境配置的相关注释文档

* feat(config): 添加OCR识别UID功能

- 在配置文件中导入ocrUid工具函数
- 修复checkKey函数中参数赋值的格式问题
- 在initConfig函数中集成OCR识别获取用户UID
- 将识别到的UID存储到配置对象中
- 在主程序中使用配置中的UID替代未定义变量
- 新增tool.js工具模块实现区域OCR识别功能
- 新增uid.js模块专门处理UID识别逻辑
- 实现了屏幕指定区域的文字识别功能

* feat(config): 添加UID配置加载开关功能

- 在配置域中新增load_uid_config开关字段
- 实现基于配置开关的OCR UID加载条件判断
- 确保配置缺失时的错误处理机制正常运行
- 保持原有配置验证逻辑完整性
- 添加对settings.load_uid_config的优先级支持

* feat(domain): 添加多加载方式支持

- 添加 LoadType 枚举定义 uid 和 input 加载类型
- 添加 LoadMap 映射表关联加载方式名称和类型
- 修改配置结构将 load_uid_config 替换为 loads 数组
- 实现多加载方式切换逻辑支持 UID 加载和输入加载
- 更新 initDomainOrderList 函数支持异步 UID 识别
- 在设置界面添加 auto_load 多选框配置加载模式
- 实现基于配置的动态加载策略选择机制

* fix(config): 解决配置密钥验证和加载列表处理问题

- 添加了配置密钥验证逻辑,确保密钥匹配
- 实现了密钥不匹配时的错误抛出机制
- 修复了自动加载列表的获取和映射处理
- 统一了代码格式和空格规范

* refactor(AutoPlanDomain): 重构秘境配置加载逻辑

- 将原来的初始化函数拆分为独立的加载模式处理函数
- 提取loadMode函数专门处理不同加载方式的配置
- 保留initDomainOrderList函数作为入口点调用加载模式
- 优化代码结构提高可读性和维护性
- 为关键变量和逻辑添加更详细的注释说明

* feat(AutoPlanDomain): 更新插件配置和密钥设置

- 在 manifest.json 中添加了新的 key 字段用于标识插件
- 重构 settings.json 配置项,将 domain_config 重命名为 key
- 更新了配置项的标签说明,改为密钥配置提示
- 恢复了 domain_config 配置项到设置列表末尾
- 为新旧配置项提供了清晰的用户指引说明

* feat(bgi_tools): 添加BGI工具HTTP配置拉取推送功能

- 实现pullJsonConfig函数用于拉取指定uid的JSON配置数据
- 实现pushAllJsonConfig函数用于推送全部JSON配置数据
- 在配置中添加bgi_tools相关API接口地址配置项
- 新增bgi_tools加载类型支持并添加到加载映射表中
- 更新加载模式界面选项增加bgi_tools加载选项
- 修改加载顺序逻辑支持按优先级排序
- 修复自动域参数初始化时副本轮数传递问题
- 更新多选框组件标签文本增加http获取配置说明
- 添加OCR区域资源释放后的空行格式化调整

* feat(AutoPlanDomain): 添加bgi_tools配置同步功能

- 在bgi_tools.js中导出pullJsonConfig和pushAllJsonConfig方法
- 在main.js中导入并集成bgi_tools配置加载逻辑
- 新增LoadType.bgi_tools分支处理远程配置拉取
- 添加settings.json配置项用于设置bgi_tools的HTTP接口地址
- 实现从远程JSON配置文件读取自动战斗计划功能

* feat(bgi_tools): 添加配置推送开关和API配置功能

- 在配置文件中添加open对象用于控制推送功能
- 实现bgi_tools相关API配置的动态设置
- 将uid配置移动到合适位置避免重复执行
- 修复pullJsonConfig调用时的参数类型转换问题
- 在设置界面添加推送开关复选框和推送API输入框
- 集成OCR获取UID功能到配置初始化流程

* feat(AutoPlanDomain): 添加bgi_tools配置推送功能

- 在bgi_tools方式加载配置时添加开始拉取日志记录
- 添加bgi_tools配置推送功能,在main函数中根据开关判断是否推送配置
- 实现pushAllJsonConfig调用以推送本地域配置文件内容

* feat(AutoPlanDomain): 添加秘境配置按日期执行功能

- 新增 parseDay 辅助函数用于安全解析 day 字段
- 在配置加载过程中对 day 字段进行解析和验证
- 解析输入数据时新增 day 字段支持并调整索引位置
- 在秘境顺序对象中添加 day 执行日期字段
- 更新配置字符串格式说明文档
- 新增 getDayOfWeek 工具函数获取当前星期信息
- 添加星期几执行的逻辑判断功能

* feat(AutoPlanDomain): 添加按星期几过滤秘境功能

- 引入 getDayOfWeek 工具函数
- 在秘境顺序列表中添加按当前星期几过滤的逻辑
- 只有当配置了 day 字段且匹配当前星期几时才执行该秘境
- 未配置 day 字段的秘境仍会正常执行

* feat(AutoPlanDomain): 添加执行日期字段支持

- 在autoFight对象中新增day属性用于存储执行日期
- 为秘境战斗配置添加日期维度的规划能力

* docs(AutoPlanDomain): 添加自动秘境计划JS项目文档

- 创建项目README文件
- 添加版本密钥表格
- 记录版本历史信息
- 注明作者信息

* docs(AutoPlanDomain): 更新 README 文档添加 BetterGI 集成说明

- 添加了关于通过 BetterGI 本体实现体力计划的描述
- 补充了脚本功能说明,解释了与 BetterGI 的集成关系

* refactor(bgi_tools): 重构BGI工具配置管理功能

- 修改pullJsonConfig函数参数结构,移除默认值并优化HTTP请求处理逻辑
- 更新pushAllJsonConfig函数参数结构,修改数据传输格式为json对象包装
- 简化配置文件路径定义,移除冗余的相对路径前缀
- 移除配置文件中的大量硬编码领域数据,改为动态读取配置文件
- 在main.js中更新函数调用参数,传递正确的API端点地址
- 修复settings.json中的选项字段名从option改为options
- 移除配置文件中的重复分隔注释行
- 添加getConfig导出函数用于外部获取配置对象
- 增加调试日志输出以支持配置加载过程追踪

* docs(AutoPlanDomain): 更新README文档完善功能说明和配置指南

- 添加功能说明章节,详细介绍脚本的核心能力
- 新增配置项说明表格,清晰展示各项参数含义
- 补充计划配置语法详细说明和字段详解
- 添加配置示例和使用建议
- 完善常见问题解答
- 优化版本历史记录格式
- 更新版本密钥表格样式

* docs(AutoPlanDomain): 更新 README 文档中的表格格式和配置语法说明

- 调整表格对齐格式,统一使用居中对齐样式
- 修正周几执行说明,移除多余描述
- 删除表格底部多余的示例注释
- 新增计划配置语法章节,添加 config/domain_config.json 配置示例
- 补充配置对象的字段说明文档

* docs(AutoPlanDomain): 更新 README 中的配置示例注释

- 修改了速刷任务的时间安排描述,从周一~周六改为每天
- 更新了注释说明以更准确反映配置的实际行为

* feat(AutoPlanDomain): 支持多日期执行配置

- 将单日期执行配置改为多日期数组配置,支持多个执行日期
- 修改数据结构从 day 字段改为 days 数组字段存储执行日期
- 更新日期解析逻辑,支持通过 "/" 分割符配置多个执行日期
- 调整过滤条件以匹配新的多日期数组格式
- 更新 README 文档说明多日期配置的使用方法
- 修改示例配置展示多日期功能的使用场景

* refactor(bgi_tools): 优化pullJsonConfig函数的参数传递方式

- 将uid参数从请求体改为URL查询参数
- 简化HTTP请求调用方式
- 移除不必要的JSON序列化操作
- 保留调试日志功能

* fix(AutoPlanDomain): 修复日期过滤逻辑并添加默认配置

- 修复了日期过滤条件,将 undefined 检查改为布尔值检查
- 添加了对空任务列表的检查,避免执行空任务
- 更新了主函数调用方式为立即执行的异步函数
- 添加了新的测试函数 test1 用于配置拉取测试
- 简化了日志输出中的对象字符串化处理
- 为 API 配置字段添加了默认本地地址值

* docs(config): 更新配置项标签描述

- 简化加载模式选项的说明文字
- 简化自动秘境计划配置的语法说明
- 统一配置项标签为更简洁的文档引用格式

* feat(game): 添加原粹树脂识别功能

- 新增 physical.js 工具模块,实现原粹树脂识别逻辑
- 在配置文件中添加用户体力相关配置项
- 集成体力识别流程到初始化过程
- 添加图像模板匹配资源管理
- 实现主界面导航和返回功能
- 增加错误处理和异常捕获机制

* fix(config): 修复配置密钥验证逻辑

- 更新 checkKey 函数以安全访问嵌套的 manifest.key 属性
- 移除重复的密钥匹配检查逻辑
- 添加对 config.info.manifest.key 的空值检查
- 统一使用 checkKey 函数进行密钥验证
- 简化 initConfig 函数中的密钥处理流程

* ```
fix(domain): 解决体力检测逻辑错误问题

- 移除了配置初始化中的体力OCR检测代码
- 在主执行函数中重新实现体力检测逻辑
- 添加体力不足时的错误提示功能
- 确保体力检测在每次执行前进行验证
```

* chore(logging): 将bgi_tools配置日志级别从info调整为debug

- 修改config.js中的日志级别设置
- 将bgi_tools配置信息的日志输出从info级别降级为debug级别
- 减少生产环境中的冗余日志输出

* docs(readme): 更新文档添加工具部署说明和配置界面截图

- 添加了 bettergi-scripts-tools 部署要求说明
- 新增登录界面截图展示
- 添加初始化配置界面截图
- 补充完整配置界面多张截图
- 更新文档结构优化用户体验说明

* feat(config): 添加 bgi_tools 授权 token 配置项

- 在配置表格中新增 bgi_tools_token 字段
- 支持 tokenName=tokenValue 语法格式
- 用于 bgi_tools 推送功能的身份验证

* fix(domain): 更新哲学和武器配置列表

- 将「纷争」的哲学替换为「诗文」的哲学
- 调整武器列表顺序,将神合秘烟的启示与贡祭炽心的荣膺位置互换
- 更新配置文件中的哲学和武器数据结构

* fix(AutoPlanDomain): 修复秘境自动战斗配置过滤逻辑

- 修复了pushAllJsonConfig函数调用中的参数格式问题
- 添加了对autoFight.DomainRoundNum大于0的条件过滤
- 确保只有设置了有效刷取轮数的配置才会参与排序和执行
- 避免无效配置导致的自动战斗异常执行

* fix(AutoPlanDomain): 修复体力检查逻辑并优化异步处理

- 修正体力不足判断条件,从 current >= min 改为 current < min
- 添加throwError函数导入以支持错误抛出功能
- 修复initDomainOrderList调用缺少await导致的异步问题
- 添加日志输出显示处理后的秘境顺序列表
- 修复physical.js中工具函数导入路径错误
- 添加原神分辨率配置常量定义

* fix(AutoPlanDomain): 修复秘境任务执行中的代码错误和日志输出问题

- 修复了导入语句中的多余空格问题
- 添加了秘境任务开始执行的日志输出
- 为秘境参数的各个属性添加了详细的日志记录
- 在设置副本轮数时添加了异常处理和类型转换
- 修复了日志输出中的格式化字符串语法错误
- 修复了异步函数调用中的语法错误

* feat(auto-plan): 添加秘境退出界面处理功能

- 新增 outDomainUI 函数用于处理秘境退出界面
- 添加 isInOutDomainUI 函数检测是否在秘境退出界面
- 实现 findTextAndClick 通用文本查找点击功能
- 在 main.js 中关闭自动分解和原粹树脂榨干功能
- 修复代码格式化问题和拼写错误
- 添加退出秘境相关的图片资源映射配置

* feat(AutoPlanDomain): 添加体力类型优先级配置功能

- 实现物理体力类型的排序和优先级列表配置
- 添加原粹树脂和其他类型树脂的数量统计逻辑
- 修改体力不足判断条件以支持多种体力类型组合
- 配置秘境参数中的树脂使用优先级列表
- 注释掉当前暂不启用的优先级功能代码

* ```
refactor(AutoPlanDomain): 更新领域配置逻辑以支持开关控制

- 将物理领域配置从数量控制改为开关控制
- 添加open字段来控制领域是否启用
- 修改求和逻辑从count字段改为基于open状态计算
- 修复注释中的错别字和描述准确性
```

* feat(AutoPlanDomain): 添加树脂类型配置和使用优先级功能

- 在配置文件中新增树脂类型名称数组,支持原粹树脂、浓缩树脂、须臾树脂、脆弱树脂
- 实现动态初始化物理树脂配置,从配置中读取树脂类型并生成默认配置
- 修改秘境执行逻辑,只对启用的树脂类型进行优先级排序
- 更新树脂使用优先级判断条件,启用树脂优先级配置功能
- 更新 README 文档,添加树脂使用顺序配置说明和示例
- 在计划配置语法中添加 physical 配置项说明

* fix(physical): 修复物理域过滤逻辑和求和计算问题

- 修改了物理域为空或无开启项目时的判断条件
- 优化了非原粹树脂项目的求和计算方式
- 修正了原粹树脂项目的求和计算方法
- 移除了冗余的 map 和 reduce 操作,改用 filter.length 计算

* fix(utils): 修正日期获取逻辑以适配4点刷新需求

- 在获取当前日期后减去4小时以适配4点刷新机制
- 确保日期计算准确性避免跨日问题

* feat(utils): 添加游戏刷新时间校准参数到getDayOfWeek函数

- 在getDayOfWeek函数中添加calibrationGameRefreshTime参数,默认值为true
- 当calibrationGameRefreshTime为true时执行4小时时间校准逻辑
- 添加函数参数说明注释
- 保留原有的星期信息获取功能

* fix(AutoPlanDomain): 修复原粹树脂数量计算逻辑

- 将 find 方法改为 filter 方法以正确计算原粹树脂项目数量
- 确保只统计开启状态的原粹树脂项目
- 修复了因方法使用错误导致的数量统计不准确问题

* refactor(physical): 更新工具函数导入并调整操作延迟时间

- 从 ./tool 模块导入 throwError 函数
- 将原粹树脂识别操作的延迟时间从 1000ms 增加到 2000ms

* fix(AutoPlanDomain): 解决原粹树脂识别和按钮点击问题

- 在物理值OCR识别前添加1秒延迟以提高准确性
- 将操作延迟时间从2000毫秒调整为1000毫秒
- 修正增加按钮的坐标和尺寸参数以匹配当前界面布局
- 注释掉衍生裁剪区域的释放操作避免潜在错误

* fix(AutoPlanDomain): 修复物理域过滤和周日选择值处理逻辑

- 修复了原粹树脂过滤条件中的多余空格问题
- 统一了过滤器链中的空格格式
- 添加了周日选择值的空值检查,避免未定义值的赋值
- 将周日选择值转换为字符串类型以确保数据一致性
- 优化了条件赋值逻辑,提高代码健壮性

* refactor(AutoPlanDomain): 更新树脂优先级列表设置方法

- 将直接赋值方式改为调用 SetResinPriorityList 方法
- 使用展开运算符合并树脂优先级列表参数

* refactor(AutoPlanDomain): 优化秘境顺序处理逻辑

- 将 getDayOfWeek 函数调用改为异步方式
- 修改日志级别从 info 到 debug,并添加调试信息
- 为日期过滤逻辑添加详细的日志输出
- 移除重复的排序操作并统一在数据处理最后进行排序
- 修正代码执行顺序以提高性能和可读性

* refactor(AutoPlanDomain): 移除不必要的过滤操作

- 移除 item.days 数组中 parseDay 映射后的空值过滤
- 简化数据处理逻辑,保持原始数组长度不变
- 提升代码简洁性,移除冗余的 filter 操作

* refactor(AutoPlanDomain): 更新执行顺序列表初始化函数命名

- 将 initDomainOrderList 函数重命名为 initRunOrderList
- 更新相关注释描述,从"秘境顺序"改为"执行顺序"
- 修改函数调用处的函数名引用

* refactor(AutoPlanDomain): 更新变量命名以匹配功能实际用途

- 将 autoFightOrderList 重命名为 autoRunOrderList 以准确反映其功能
- 相应更新过滤操作中的变量引用
- 保持原有业务逻辑不变

* refactor(AutoPlanDomain): 重命名自动执行函数名称

- 将 autoDomainList 函数重命名为 autoRunList
- 更新函数调用以使用新的函数名称
- 保持原有功能逻辑不变

* refactor(AutoPlanDomain): 优化配置变量命名

- 将 domainConfig 重命名为 runConfig 以提高代码可读性
- 更新注释内容以准确描述配置获取功能
- 保持原有逻辑不变,仅调整变量命名规范

* refactor(AutoPlanDomain): 将域配置参数重命名为运行配置

- 将 config.js 中的 domainConfig 属性重命名为 runConfig
- 将 main.js 中的函数参数 domainConfig 重命名为 runConfig
- 更新函数内部对配置路径的引用,从 domainConfig 改为 runConfig
- 修改注释中参数名称的描述,保持文档一致性

* refactor(config): 将配置对象中的domain属性重命名为run

- 将config.js中的domain对象重命名为run对象
- 更新所有引用config.domain为config.run的地方
- 修改main.js中对配置对象的访问路径
- 确保配置加载逻辑保持一致性

* rename(config): 将 domain_config 重命名为 run_config

- 修改配置文件路径从 config/domain_config.json 到 config/run_config.json
- 更新代码中引用的配置项名称从 domain_config 到 run_config
- 更新 README.md 文档中的配置项说明
- 更新 settings.json 中的配置字段名称

* feat(AutoPlanDomain): 添加计划配置类型字段支持

- 在表格中新增类型字段作为必填项,支持秘境/地脉类型选择
- 更新配置示例格式,在开头添加类型标识符
- 在JSON配置中添加runType字段,默认为秘境类型
- 调整表格结构并优化字段说明文档
- 为后续地脉功能扩展预留字段支持

* feat(AutoPlanDomain): 添加运行类型配置支持秘境和地脉

- 在配置中新增runTypes数组,支持'秘境'和'地脉'两种运行类型
- 修改解析逻辑以支持运行类型参数作为第一个参数
- 添加运行类型验证检查,确保输入类型在允许范围内
- 更新数组索引解析,调整各参数对应的数组位置
- 过滤条件中添加运行类型为'秘境'的条件限制

* fix(AutoPlanDomain): 修复自动运行配置中的队伍类型判断逻辑

- 将硬编码的"秘境"字符串替换为配置项config.user.runTypes[0]
- 提高代码的可配置性和灵活性
- 避免因固定值导致的潜在错误

* docs(AutoPlan): 更新项目名称和功能描述

- 将项目名称从 AutoPlanDomain 重命名为 AutoPlan
- 更新功能描述从"自动秘境计划JS"为"自动体力计划JS"
- 移除关于BetterGI自动秘境的冗余说明

* fix(AutoPlan): 修正错误提示信息

- 将秘境配置错误提示修改为体力配置错误提示
- 确保错误信息与实际功能配置相匹配

* feat(AutoPlan): 添加运行类型判断逻辑

- 实现了根据runType字段控制autoDomain执行的功能
- 当runType为config.user.runTypes[0]时执行autoDomain
- 当runType为config.user.runTypes[1]时预留地脉花功能
- 保持了原有的自动域名执行列表循环结构

* refactor(AutoPlan): 优化自动执行列表处理函数命名

- 将函数参数名从 autoDomainOrderList 更改为 autoRunOrderList
- 更新函数注释描述以匹配新的通用功能
- 修改循环变量引用以使用新的参数名称
- 调整函数逻辑以支持更广泛的自动配置类型

* feat(AutoPlan): 添加复活重试机制和地脉花功能支持

- 实现复活重试逻辑,最多重试5次
- 只对秘境复活场景进行重试处理
- 添加地脉花任务的异步函数框架
- 完善自动执行列表中的地脉花调用
- 修复代码中的空格格式问题

* todo

* fix(AutoPlan): 修复树脂耗尽模式和开放模式计数配置

- 将 isResinExhaustionMode 配置项从 false 修改为 true
- 将 openModeCountMin 配置项从 false 修改为 true
- 更新自动运行列表过滤条件以支持多个运行类型
- 添加对 autoLeyLineOutcrop.count 的可选链检查
- 扩展运行配置以同时处理 DomainRoundNum 和 LeyLineOutcrop 计数

* ```
feat(AutoPlan): 添加地脉异常处理和参数配置功能

- 注释掉树脂耗尽模式和开放模式计数最小值配置项
- 添加地脉暂不支持的日志提示并返回
- 创建AutoLeyLineOutcropParam对象并设置各项参数
- 重新启用树脂耗尽模式和开放模式计数最小值
- 实现复活重试机制,最多重试5次
- 仅在包含复活错误信息时进行重试
- 移除其他场景的重试逻辑
```

* feat(AutoPlan): 添加秘境执行类型过滤功能

- 在自动战斗计划中添加配置过滤器,只执行指定类型的秘境
- 保留原有的日期过滤逻辑作为备用选项
- 通过配置项控制不同秘境类型的执行策略

* refactor(AutoPlan): 统一副本轮数参数命名规范

- 将 DomainRoundNum 属性名统一改为 domainRoundNum
- 更新 main.js 中的参数引用以保持一致性
- 修改 README.md 文档中的参数名称说明
- 确保所有相关代码使用相同的参数命名风格

* refactor(AutoPlan): 重命名解析函数以提高语义清晰度

- 将 parseDay 函数重命名为 parseInteger 以更准确反映其功能
- 更新所有调用点以使用新的函数名称
- 保持原有解析逻辑不变,仅改进函数命名的准确性

* refactor(AutoPlan): 重构秘境配置解析逻辑并更新文档

- 重命名 autoFightOrderSet 为 autoOrderSet 以统一变量命名
- 重构 loadMode 函数中的参数解析逻辑,调整数组索引对应关系
- 重新组织秘境顺序对象的数据结构,支持地脉功能扩展
- 更新 README.md 中的配置参数表格和示例格式
- 调整配置参数的顺序映射关系,统一索引计算逻辑
- 修复 UID 配置加载的变量引用错误
- 优化代码缩进格式保持一致性

* refactor(AutoPlan): 优化自动规划配置解析逻辑

- 使用索引变量替代硬编码数组下标提高代码可读性
- 重构运行类型配置解析,支持更灵活的参数结构
- 完善地脉开采功能配置项,增加更多可配置参数
- 添加详细的注释说明各类配置参数的作用和格式
- 优化数组遍历逻辑,避免直接使用固定下标访问元素

* fix(auto-plan): 修复地脉功能布尔值解析和配置文档

- 修复 useFragileResin、useTransientResin、isGoToSynthesizer、useAdventurerHandbook、isNotification 字段的布尔值解析逻辑
- 修改 timeout 字段解析使用 parseInteger 函数
- 更新 README.md 中的地脉功能配置说明和参数详解表格
- 添加地脉类型的详细参数说明和配置示例
- 修复版本密钥表格格式问题
- 补充 JSON 配置格式示例和地脉功能相关配置项说明

* fix(AutoPlan): 解决地脉数据解析中的数组越界问题

- 在访问friendshipTeam字段前添加数组长度检查
- 在访问useFragileResin字段前添加数组长度检查
- 在访问useTransientResin字段前添加数组长度检查
- 在访问isGoToSynthesizer字段前添加数组长度检查
- 在访问useAdventurerHandbook字段前添加数组长度检查
- 在访问isNotification字段前添加数组长度检查
- 在访问timeout字段前添加数组长度检查
- 防止因数组索引超出范围导致的解析错误

* fix(AutoPlan): 修复副本配置解析中的数组越界问题

- 添加数组长度检查以防止访问超出边界的索引
- 为sundaySelectedValue设置默认值避免未定义错误
- 在解析域选择值前验证数组索引范围
- 保持原有逻辑功能的同时增强代码健壮性

* fix(AutoPlan): 修复队伍名称为空时的设置问题

- 添加了队伍名称非空判断条件
- 防止空字符串被设置为队伍名称
- 确保只有有效名称才会被赋值给autoFight.partyName

* Revert "fix(AutoPlan): 修复队伍名称为空时的设置问题"

This reverts commit 2dc019f4cc.

* refactor(AutoPlan): 优化秘境战斗配置对象初始化逻辑

- 将秘境信息对象autoFight的创建位置从错误处理分支后移至条件判断前
- 统一了对象属性的初始化值,sundaySelectedValue默认值设为1
- 删除了重复的对象声明代码,提高了代码可读性
- 保持了原有的功能逻辑不变,仅调整代码结构

* ```
refactor(AutoPlan): 注释地脉花功能配置对象

- 将 autoLeyLineOutcrop 配置对象注释掉以禁用该功能
- 保留原始配置代码便于后续功能启用
- 添加功能暂不支持的日志提示信息
```

* fix(auto-plan): 修复UID配置读取问题

- 确保UID作为字符串类型进行配置映射查找
- 更新文档中UID字段的数据类型说明

* feat(auto-plan): 添加运行类型支持并优化名称和资源管理

- 在自动计划配置中添加runType字段用于指定运行类型
- 将插件名称从"自动秘境计划"更改为"自动体力计划"
- 为图像识别添加调试日志输出功能
- 使用try-finally块确保图像捕获区域资源正确释放
- 优化退出界面逻辑,增加主界面检测和确认按钮点击处理
- 添加循环跳出条件避免无限重试

* feat(config): 添加复活重试次数配置选项

- 在配置文件中添加 retry_count 参数,默认值为 3
- 从设置界面读取重试次数并转换为整数值
- 将硬编码的重试次数 5 替换为配置值
- 在设置界面添加下拉选择框供用户自定义重试次数
- 添加复活重试功能的 UI 输出调用

* feat(bgi_tools): 添加全部国家配置推送功能

- 新增 pushAllCountryConfig 函数用于推送国家配置数据
- 配置文件中添加 httpPushAllCountryConfig API 地址配置项
- 添加 countryList.json 文件存储国家列表数据
- 在 main.js 中集成国家配置推送逻辑
- 设置文件中添加国家配置推送API设置选项
- 完善相关依赖注入和配置初始化流程

* fix(config): 更新 BGI 工具推送国家配置 API 默认地址

- 修改默认 API 地址从 /country/all 到 /country/json/all
- 保持其他配置项不变

* docs(AutoPlan): 更新 README 中限时/周日参数说明

- 将限时/周日参数说明从具体的可用时间描述改为与本体一致的数值说明
- 保持参数选项 1 / 2 / 3 不变
- 简化了参数含义的表述方式

* docs(AutoPlan): 更新战斗超时配置说明

- 将战斗超时选项的默认值从"启用随便填个值"更正为"可选,默认 120"
- 修正README文档中的配置参数描述准确性

* feat(AutoPlan): 实现自动运行列表的循环执行机制

- 添加while循环结构支持重复执行autoRunList
- 集成体力检测逻辑防止体力耗尽时继续运行
- 添加循环控制条件避免无限执行
- 实现体力不足时的自动中断机制
- 优化运行流程确保按需执行和停止

* refactor(AutoPlan): 优化秘境自动化任务执行逻辑

- 将复活重试逻辑包装在try-finally块中确保退出秘境操作始终执行
- 修复多处代码格式问题包括空格和缩进统一
- 优化条件判断语句的格式和可读性
- 统一变量赋值语句的空格格式
- 改进字符串拼接和条件表达式的写法
- 优化while循环中的退出条件判断逻辑

* fix(AutoPlan): 移除体力检查逻辑

- 删除了配置中的体力值下限判断条件
- 移除了当用户当前体力低于最低要求时的中断逻辑
- 简化了循环执行的条件判断流程

* Revert "fix(AutoPlan): 移除体力检查逻辑"

This reverts commit 71126e94dc.

* fix(AutoPlan): 修复数组越界检查逻辑

- 将条件判断从 index < arr.length 修改为 index <= arr.length-1
- 防止在解析配置数组时发生索引越界错误
- 确保所有可选配置项都能正确解析而不会抛出异常

* fix(AutoPlan): 修复自动运行循环的条件判断逻辑

- 修改了循环终止条件,将硬编码的 true 改为 false
- 添加了体力值检查条件 config.user.physical.current < config.user.physical.min
- 调整了体力不足时的 break 逻辑位置
- 移除了冗余的 else if 分支,简化代码结构

* fix(AutoPlan): 修复自动计划循环逻辑

- 修改了循环条件判断逻辑
- 更新了体力不足时的循环行为
- 添加了正确的循环控制注释

* refactor(AutoPlan): 调整体力检查逻辑结构

- 重构了循环中的条件判断结构
- 将体力检查逻辑嵌套到新的条件分支中
- 添加了体力耗尽的注释说明
- 优化了代码执行流程的控制逻辑

* chore(AutoPlan): 添加循环注释

- 在自动运行列表循环中添加了循环注释标记

* feat(plan): 添加循环体力计划功能

- 在配置文件中添加 loop_plan 选项,默认为 false
- 将设置项 loop_plan 与配置项进行绑定
- 修改主循环逻辑,根据 loop_plan 配置决定是否启用循环体力计划
- 在设置界面添加循环体力计划的复选框选项

* feat(AutoPlan): 添加循环体力计划和复活重试功能

- 新增 loop_plan 配置项支持循环体力计划
- 新增 retry_count 配置项控制复活重试次数
- 更新战斗超时默认值显示格式
- 扩展自动加载模式的功能选项

* feat(settings): 更新循环体力计划配置项标签

- 在循环体力计划标签中添加体力耗尽自动终止说明

* 等本体地脉花 先上个草稿 (#2883)

* feat(bettergi): 添加自动地脉花任务参数类型定义

- 定义 AutoLeyLineOutcropParam 类型
- 添加刷取次数、国家地区、地脉花类型等基本属性
- 实现树脂耗尽模式、好感队、战斗队伍等相关配置
- 添加自动合成浓缩树脂和使用各类树脂的功能选项
- 包含超时设置和通知功能配置
- 生成自动生成的别名声明

* feat(dispatcher): 添加自动地脉花任务功能

- 添加 runAutoLeyLineOutcropTask 方法用于运行自动地脉花任务
- 添加 RunAutoLeyLineOutcropTask 别名用于兼容性支持
- 提供自定义取消令牌参数支持
- 完善类型定义文件中的相关接口声明

(cherry picked from commit ca0eb532c4)

* refactor(AutoPlan): 移除地脉功能的调试代码

- 注释掉地脉暂不支持的日志输出逻辑
- 保留AutoLeyLineOutcropParam参数初始化代码
- 清理相关调试标记以优化代码结构

* fix(config): 修复配置文件中的数组格式问题

- 移除了 options 数组中多余的逗号,修正了 JSON 格式错误
- 确保配置文件符合标准 JSON 语法规范

* feat(AutoPlan): 更新秘境退出检测逻辑

- 将 isInOutDomainUI 函数改为异步函数,使用 OCR 文本识别替代模板匹配
- 添加 findText 函数用于在指定区域内查找文本内容,支持多次尝试和重试机制
- 修改 outDomainUI 函数逻辑,优化退出秘境流程和错误处理
- 增加最大尝试次数限制,避免无限循环等待
- 添加详细的 JSDoc 注释文档说明 findText 函数参数和返回值

* style(utils): 格式化代码并添加测试功能

- 修复 tool.js 中的代码格式问题,包括括号位置和空格调整
- 将 main.js 中的日志级别从 info 调整为 debug 以减少冗余输出
- 在 main.js 中添加 test2 测试函数用于初始化和退出秘境界面测试
- 在 outDomainUI 函数中添加主界面状态检测逻辑
- 优化退出秘境流程的条件判断,增加 inMainUI 状态变量
- 修复代码中的语法格式问题,统一代码风格

* ```
feat(AutoPlan): 添加退出秘境的UI处理功能

- 实现了退出秘境界面处理函数
- 添加了点击确认按钮的交互逻辑
- 集成了界面状态检测机制
- 完善了相关操作的日志记录
```

* ```
fix(AutoPlan): 优化自动秘境执行流程并修复索引逻辑错误

- 添加秘境任务开始和完成的日志记录
- 将地脉功能的info日志改为warn日志并更新开始执行日志
- 在执行列表循环中添加1秒延迟避免过快执行
- 修复索引递增逻辑错误,移除重复的index++操作
- 统一数组长度比较中的空格式
- 优化循环计划的条件判断格式
- 添加测试函数的空行分隔
```

* perf(AutoPlan): 优化秘境任务执行逻辑

- 在秘境任务开始时添加警告日志提示等待时间可能较长
- 保留体力消耗相关配置参数以支持后续功能扩展

* chore(version): 更新版本号到 0.57.1

- 将 bgi_version 从 0.55.0 更新到 0.57.1

* chore(version): 降级版本号从 0.57.1 到 0.57.0

- 将 manifest.json 中的 bgi_version 从 0.57.1 修改为 0.57.0

* fix(AutoPlan): 修复地脉异常处理逻辑中的无限循环问题

- 调整了 ESCAPE 键按压和睡眠操作的位置以避免重复执行
- 确保在主界面检测成功时能够正确跳出循环
- 保持原有的重试机制和错误处理逻辑不变

* docs(readme): 更新AutoPlan文档标题和描述

- 将标题从"AutoPlan"更改为"AutoPlan自动体力计划"
- 更新脚本描述为中文说明
- 保留原有的功能介绍内容

* fix(AutoPlan): 修正地脉刷取配置格式

- 修正了地脉刷取配置的字段格式
- 统一了配置文件的语法结构
- 修正了注释格式以匹配新的配置规范

* fix(AutoPlan): 调整自动化脚本执行间隔和界面切换逻辑

- 将计划执行间隔从 1 秒增加到 3 秒
- 在界面切换后添加延迟以确保操作稳定
- 在地图打开操作前添加延迟等待

* fix(AutoPlan): 解决地脉任务参数解析问题

- 移除地脉任务的临时禁用代码
- 修复参数传递中的类型转换问题,使用 parseInteger 处理计数参数
- 更新 BGI 版本号从 0.57.0 到 0.57.2
- 添加参数对象的构造注释代码作为备选实现方案

* fix(AutoPlan): 添加延迟等待解决复活重试问题

- 在复活重试逻辑前添加1秒延迟
- 防止重试过程中的时间冲突问题

* chore(AutoPlan): 移除多余调试日志并添加关键参数输出

- 注释掉秘境名称、队伍名称、周日选择值和副本轮数的详细调试日志
- 添加秘境参数的JSON字符串化输出便于调试
- 添加地脉参数的JSON字符串化输出便于调试

* fix(AutoPlan): 修复冒险手册使用逻辑错误

- 和本体保持一致,反转 useAdventurerHandbook 参数值
- 修正了自动规划功能中的布尔参数取反逻辑

* Revert "chore(AutoPlan): 移除多余调试日志并添加关键参数输出"

This reverts commit a348a2cdbc.

* fix(config): 更新bgi_tools拉取配置api默认地址

- 修正了bgi_tools拉取配置api的默认路径从/domain/json改为/json

* fix(config): 修复配置项默认值处理逻辑

- 修改 loop_plan 配置项的默认值处理,避免 undefined 值覆盖原有配置
- 优化物理识别模块中的按钮匹配逻辑,增加空值检查防止程序崩溃

* fix(AutoPlan): 修复自动规划模块中的空指针和配置读取问题

- 修复了地脉任务参数构造中的字符串拼接问题
- 将配置文件读取的数据结构从Map改为Object以避免兼容性问题
- 添加了对配置列表长度和大小的双重检查以防止空指针异常
- 为数字解析函数添加了异常处理和默认值参数
- 修复了日志输出格式中的空格问题

* fix(AutoPlan): 修复计划解析中的空值处理问题

- 添加了对null和undefined值的安全检查
- 使用String()确保类型转换的一致性
- 在parseInt中指定基数参数以避免潜在问题
- 注释掉旧代码以便后续清理
- 提高了日期解析的健壮性

* fix(AutoPlan): 解决物理域数据访问中的空值错误

- 添加可选链操作符以防止访问未定义对象属性
- 修复过滤器中对空值的处理逻辑
- 确保数组映射操作的安全性避免运行时错误

* fix(AutoPlan): 修复数组元素空值检查逻辑

- 添加 null 检查以避免对空值进行 trim 操作
- 确保 useFragileResin 属性正确处理空值情况
- 确保 useTransientResin 属性正确处理空值情况
- 确保 isGoToSynthesizer 属性正确处理空值情况
- 确保 useAdventurerHandbook 属性正确处理空值情况
- 确保 isNotification 属性正确处理空值情况
- 在条件判断前添加额外的换行符以提高代码可读性

* refactor(utils): 优化代码逻辑和资源管理

- 修改 bgi_tools.js 中注释掉不必要的返回语句
- 格式化 config.js 中数组和条件判断的代码风格
- 重构 token 解析逻辑,使用更安全的分割方法
- 修复 physical.js 中方法名大小写错误 (Dispose -> dispose)
- 优化 main.js 中循环逻辑,添加体力值重新获取功能
- 完善 uid.js 中数字提取逻辑,增加边界情况处理

* fix(autoPlan): 解决无效加载方式导致的错误处理问题

- 添加对无效加载方式的检查和错误抛出
- 防止当LoadMap中不存在对应项时程序崩溃
- 提供更清晰的错误信息帮助调试

* refactor(config): 优化重试次数配置解析逻辑

- 使用更安全的 Number.parseInt 和 Number.isFinite 替代 parseInt
- 添加空值检查和默认值回退机制
- 移除旧的配置解析代码
- 更新工具函数文档注释类型定义

* refactor(AutoPlan): 更新体力识别函数返回值字段命名

- 将返回对象中的remainder字段重命名为current,提高语义清晰度
- 保持其他功能逻辑不变,仅调整字段名称以更好地反映其含义

* fix(config): 修正配置文件中的文本错误

- 修正了 domain.json 中 "长夜火的烈辉" 为正确的 "长夜燧火的烈辉"
- 确保配置项的一致性和准确性
This commit is contained in:
云端客
2026-03-02 18:10:15 +08:00
committed by GitHub
parent d5889776c3
commit 3d7bd2b385
21 changed files with 2143 additions and 0 deletions

267
repo/js/AutoPlan/README.md Normal file
View File

@@ -0,0 +1,267 @@
# AutoPlan自动体力计划
**自动体力计划JS**
本脚本通过调用 **BetterGI** 本体自动秘境实现体力计划
## 功能说明
本脚本为 BetterGI 的自动秘境功能提供**计划/排期**能力,让你能:
- 为不同队伍配置不同的秘境刷取目标
- 设置刷取轮次
- 限制只在特定星期几执行(例如周日限定本、周本)
- 设置执行优先级(数字越大越先跑)
- 支持三种配置来源(输入 / UID专属 / bgi_tools远程
## 配置项说明
脚本通过以下界面配置项进行设置(对应 settings.json
| 配置项 | 类型 | 说明 | 示例值 |
|-----------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------|------------------------------------------|
| **key** | input-text | 版本密钥(必填,仔细看本文档) | xxxx |
| **auto_load** | multi-checkbox | 加载模式(可多选)<br>・输入加载:使用下方 run_config 字段<br>・UID加载读取 config/run_config.json每个UID独立<br>・bgi_tools加载从远程API拉取 | 默认:["输入加载","UID加载"] |
| **run_config** | input-text | 自动秘境计划配置(当选择“输入加载”时生效)<br>格式见下方“计划配置语法” | 速刷\|苍白的遗荣\|3\|1-3\|0,6\|9<br>(多条用英文逗号分隔) |
| **loop_plan** | checkbox | 启用循环体力计划 | |
| **retry_count** | select | 复活重试次数 | |
| **bgi_tools_http_pull_json_config** | input-text | bgi_tools 拉取配置的 API 地址(当选择 bgi_tools加载 时生效) | https://example.com/api/pull |
| **bgi_tools_open_push** | checkbox | 是否在脚本结束时推送当前全部配置给 bgi_tools用于同步/备份) | 勾选 = 开启推送 |
| **bgi_tools_http_push_all_json_config** | input-text | bgi_tools 推送全部配置的 API 地址(当开启推送时使用) | https://example.com/api/push-all |
| **bgi_tools_token** | input-text | bgi_tools授权token 语法:tokenName=tokenValue | tokenName=tokenValue |
### 计划配置语法run_config 字段)
```
队伍名称|秘境名称/刷取物品名称|刷几轮|限时/周日(1-3和本体的一致)|周几执行(0-6)不填默认执行|树脂使用顺序|执行顺序(越大越先执行)
```
- 多条计划用 **英文半角逗号 ,** 分隔
- 字段之间用 **英文半角竖线 |** 分隔
- **必须** 填写:秘境名称/物品名称
- 其他字段可省略(留空即可,但竖线不能省)
**字段详解**
| 位置 | 字段 | 是否必填 | 说明 | 示例 |
|:--:|:-----------:|:------:|:--------------------------------------:|:-----------------------:|
| 1 | 类型 | **必填** | 秘境/地脉(后期支持地脉冗余字段) | 秘境/地脉 |
| 2 | 周几执行(0-6) | 可选 | 0=周日,1=周一,...,6=周六;可多选按`/`分割,不填=每天都可执行 | 0/3,3 |
| 3 | 执行顺序 | 可选 | 数字越大越优先执行(同时间点先跑优先级高的) | 9 / 5 / 1 |
| | | | ***`秘境类型后几位参数说明`***
| 4 | 队伍名称 | 可选 | BetterGI 中已保存的队伍名称(用于切换队伍) | 速刷 / 雷国 / 国家队 |
| 5 | 秘境名称/刷取物品名称 | **必填** | 与 BetterGI 自动秘境识别的名称保持一致 | 苍白的遗荣 / 炽烈的炎之魔女 / 辰砂往生录 |
| 6 | 刷几轮 | 可选 | 整数,执行几轮(每轮 = 1次完整秘境 | 3 / 5 / 10 |
| 7 | 限时/周日 | 可选 | 和本体的1-3一致 | 1 / 2 / 3 |
| 8 | 树脂使用顺序 | 可选 | 原粹树脂,浓缩树脂,须臾树脂,脆弱树脂`/`分割,不填=默认执行 原粹树脂 | 原粹树脂/浓缩树脂,须臾树脂 |
| | | | ***`地脉类型后几位参数说明`***
| 4 | 队伍名称 | 可选 | BetterGI 中已保存的队伍名称(用于切换队伍) | 速刷 / 雷国 / 国家队 |
| 5 | 国家 | **必填** | 识别国家(用于切换国家) | 纳塔 ... |
| 6 | 刷几轮 | **必填** | 刷几轮 | 1 |
| 7 | 地脉类型 | **必填** | 启示之花/藏金之花 | 启示之花 / 藏金之花 |
| 8 | 好感队 | 可选 | | |
| 9 | 使用脆弱树脂 | 可选 | 启用随便填个值 |
| 10 | 使用须臾树脂 | 可选 | 启用随便填个值 |
| 11 | 合成浓缩树脂 | 可选 | 启用随便填个值 |
| 12 | 使用冒险家之书 | 可选 | 启用随便填个值 |
| 13 | 详细通知 | 可选 | 启用随便填个值 |
| 14 | 战斗超时 | 可选 | 可选,默认 120 |
**配置示例**
```
秘境|0/3|9|速刷|苍白的遗荣|3|1, # 优先级最高,周日,周三刷3轮遗荣
秘境||5|国家队|炽烈的炎之魔女|5||, # 优先级次之每天刷5轮魔女
秘境|0|2|雷国|无想之刃狭间|2|2|浓缩树脂/原粹树脂, # 只在周日刷,优先使用浓缩树脂后使用原粹树脂,优先级较低
地脉||1||蒙德|1|启示之花|||||||120 #优先级较低 刷1轮蒙德经验书
```
(注意:最后一条也可以不带逗号)
### 计划配置语法config/run_config.json 配置)
```json
[
[
"uid",
[
{
"order": 1,
// 顺序值
"days": [
0
],
// 执行日期
"runType": "秘境",
// 预留地脉 类型支持。 秘境、地脉
"autoFight": {
"physical": [
{
"order": 0,
"name": "原粹树脂",
"open": true
},
{
"order": 1,
"name": "浓缩树脂",
"open": false
},
{
"order": 2,
"name": "须臾树脂",
"open": false
},
{
"order": 3,
"name": "脆弱树脂",
"open": false
}
],
//树脂开启和使用顺序
"domainName": undefined,
//秘境名称
"partyName": undefined,
//队伍名称
"sundaySelectedValue": undefined,
//周日|限时选择的值
"domainRoundNum": undefined//副本轮数
}
// 秘境信息对象,
"autoLeyLineOutcrop": {
"count": 0,
//刷几轮
"country": "",
//国家
"leyLineOutcropType": "启示之花",
//地脉类型 启示之花/藏金之花
"useAdventurerHandbook": false,
//使用冒险家之书
"friendshipTeam": "",
//好感队伍名称
"team": "",
//队伍名称
"timeout": 120,
//战斗超时
"isGoToSynthesizer": false,
//合成浓缩树脂
"useFragileResin": false,
//使用脆弱树脂
"useTransientResin": false,
//使用须臾树脂
"isNotification": false
//详细通知
}
//地脉信息对象
}
]
]
]
```
```json
[
[
"10000002",
[
{
"order": 1,
"days": [
0
],
"runType": "秘境",
"autoFight": {
"physical": [
{
"order": 0,
"name": "原粹树脂",
"open": true
},
{
"order": 1,
"name": "浓缩树脂",
"open": false
},
{
"order": 2,
"name": "须臾树脂",
"open": false
},
{
"order": 3,
"name": "脆弱树脂",
"open": false
}
],
"domainName": "",
"partyName": "",
"sundaySelectedValue": 1,
"domainRoundNum": 1
}
},
{
"order": 2,
"days": [],
"runType": "地脉",
"autoLeyLineOutcrop": {
"count": 1,
"country": "纳塔",
"leyLineOutcropType": "启示之花",
"useAdventurerHandbook": false,
"friendshipTeam": "",
"team": "",
"timeout": 120,
"isGoToSynthesizer": false,
"useFragileResin": false,
"useTransientResin": false,
"isNotification": false
}
}
]
]
]
```
### 如果你不想研究语法 请部署[bettergi-scripts-tools](https://github.com/Kirito520Asuna/bettergi-scripts-tools) v0.0.4以上版本
***`话不多说直接上图:`***
![login](md/bgi-tools-login.jpg)
![init](md/bgi-tools-config-ui-init.jpg)
![config](md/bgi-tools-config-ui.jpg)
![config-01](md/bgi-tools-config-ui-01.jpg)
![config-02](md/bgi-tools-config-ui-02.jpg)
## 使用建议
1. 第一次使用建议只勾选「输入加载」,把计划写在 `run_config` 里测试
2. 熟练后可改为「UID加载」把配置保存为 `config/run_config.json`(每个账号独立)
3. 如果你有自己的云端配置服务可使用「bgi_tools加载」 + 推送功能实现多端同步
4. 确保 BetterGI 的**自动秘境**功能已正常可用(钟离识别、战斗策略等)
## 常见问题
- 计划没有执行?
→ 检查密钥是否正确、是否勾选了对应的加载模式、秘境名称是否与 BetterGI 完全一致
- 周几限制不生效?
→ 确认 BetterGI 系统时间正确,且格式为 0-60=周日)
欢迎提交 issue 或 PR 改进脚本~
祝刷本愉快!
## 版本密钥
| 版本 | 密钥 |
|-------|---------------------|
| 0.0.1 | oiJbmjU2R0NniiwiZxh |
## 版本历史(简要)
### 0.0.1 2026.01.30
- 基本功能完成
- 支持三种配置加载方式
- 支持 bgi_tools http 拉取/推送配置
- 支持按队伍、秘境、轮次、周几、优先级执行计划
**作者**:云端客 (Kirito520Asuna)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,279 @@
import {ocrUid} from "../utils/uid";
const config = {
//setting设置放在这个json
run: {
loop_plan: false,//启用循环体力计划
retry_count: 3,//复活重试次数
config: '',
// load_uid_config: false,
loads: [],//加载方式list
},
bgi_tools: {
//授权token
token: {
name: 'Authorization',
value: ''
},
api: {
httpPullJsonConfig: undefined,
httpPushAllJsonConfig: undefined,
httpPushAllCountryConfig: undefined,
},
open: {open_push: false}
},
info: {
key: undefined,//密钥
manifest: {},
settings: undefined
},
user: {
uid: undefined,
physical: {
min: 20,//最小体力
current: 0,//当前体力
names: ["原粹树脂", "浓缩树脂", "须臾树脂", "脆弱树脂"]
},
runTypes: ['秘境', '地脉']
},
//
path: {
manifest: "manifest.json",
domain: "config/domain.json",
runConfig: "config/run_config.json",
countryList: "config/countryList.json"
},
//所有秘境信息
domainList: [],
//所有秘境名称
domainNames: new Set(),
//物品名称(只记录顶级的名称->金色物品名称)
itemNames: new Set(),
//秘境名称映射物品列表
domainMap: new Map(),
//秘境名称映射秘境列表顺序
domainOrderMap: new Map(),
//物品名称映射秘境名称
domainItemsMap: new Map(),
}
const LoadType = Object.freeze({
uid: 'uid',//uid加载
input: 'input',//input加载
bgi_tools: 'bgi_tools',//bgi_tools加载
fromValue(value) {
return Object.keys(this).find(key => this[key] === value);
}
})
const LoadMap = new Map([
['UID加载', LoadType.uid],
['输入加载', LoadType.input],
['bgi_tools加载', LoadType.bgi_tools],
])
/**
* 初始化设置函数
* 从配置文件中读取设置信息并返回
* @returns {Object} 返回解析后的JSON设置对象
*/
async function initSettings() {
// 默认设置文件路径
let settings_ui = "settings.json";
try {
// 读取并解析manifest.json文件
config.info.manifest = JSON.parse(file.readTextSync(config.path.manifest));
// 调试日志输出manifest内容
log.debug("manifest={key}", config.info.manifest);
// 调试日志输出manifest中的settings_ui配置
log.debug("settings_ui={key}", config.info.manifest.settings_ui);
log.info(`|脚本名称:{name},版本:{version}`, config.info.manifest.name, config.info.manifest.version);
if (config.info.manifest.bgi_version) {
log.info(`|最小可执行BGI版本:{bgi_version}`, config.info.manifest.bgi_version);
}
log.info(`|脚本作者:{authors}\n`, config.info.manifest.authors.map(a => a.name).join(","));
// 更新settings_ui变量为manifest中指定的路径
settings_ui = config.info.manifest.settings_ui
} catch (error) {
// 捕获并记录可能的错误
log.warn("{error}", error.message);
}
// 读取并解析设置文件
const settingsJson = JSON.parse(file.readTextSync(settings_ui));
// 如果configSettings未定义则将其设置为解析后的设置对象
if (!config.info.settings) {
config.info.settings = settingsJson
}
// 调试日志:输出最终解析的设置对象
log.debug("settingsJson={key}", settingsJson);
// 返回设置对象
return settingsJson
}
/**
* 获取多复选框的映射表
* 该函数会从初始化的设置中提取所有类型为"multi-checkbox"的条目,
* 并将这些条目的名称和对应的选项值存储在一个Map对象中返回
* @returns {Promise<Map>} 返回一个Promise对象解析为包含多复选框配置的Map
*/
async function getMultiCheckboxMap() {
// 如果configSettings存在则使用它否则调用initSettings()函数获取
const settingsJson = config.info.settings ? config.info.settings : await initSettings();
// 创建一个新的Map对象用于存储多复选框的配置
// Map结构为: {名称: 选项数组}
let multiCheckboxMap = new Map();
// 遍历设置JSON中的每个条目
settingsJson.forEach((entry) => {
// 如果条目没有name属性或者类型不是"multi-checkbox",则跳过该条目
if (!entry.name || entry.type !== "multi-checkbox") return;
// 解构条目中的name和label属性便于后续使用
const {name, label} = entry;
// 获取当前name对应的设置值如果存在则转换为数组否则使用空数组
const options = settings[name] ? Array.from(settings[name]) : [];
// 记录调试信息,包含名称、标签、选项和选项数量
log.debug("name={key1},label={key2},options={key3},length={key4}", name, label, JSON.stringify(options), options.length);
// 将名称和对应的选项数组存入Map
multiCheckboxMap.set(name, options);
})
// 返回包含多复选框配置的Map
return multiCheckboxMap
}
/**
* 根据复选框组名称获取对应的值
* 这是一个异步函数,用于从复选框映射中获取指定名称的值
* @param {string} name - 复选框组的名称
* @returns {Promise<any>} 返回一个Promise解析为复选框组对应的值
*/
async function getValueByMultiCheckboxName(name) {
// 获取复选框映射表,这是一个异步操作
let multiCheckboxMap = await getMultiCheckboxMap()
// log.debug("multiCheckboxMap={key}", JSON.stringify(multiCheckboxMap))
// multiCheckboxMap.entries().forEach(([name, options]) => {
// log.debug("name={key1},options={key2}", name, JSON.stringify(options))
// })
// 从映射表中获取并返回指定名称对应的值
let values = multiCheckboxMap.get(name);
log.debug("values={key}", JSON.stringify(values))
return values
}
/**
* 检查密钥是否正确
*/
async function checkKey(key = "") {
if (config?.info?.manifest?.key !== key?.trim()) {
throw new Error("密钥错误");
}
}
/**
* 初始化秘境配置
* @returns {Promise<void>}
*/
async function initConfig() {
config.info.key = settings.key || config.info.key
await checkKey(config.info.key)
// //流程->返回主页 打开地图 返回主页
// const physical = await ocrPhysical(true, true)
// config.user.physical.current = physical.current
// config.user.physical.min = physical.min
// 初始化uid
config.user.uid = await ocrUid()
// config.run.retry_count = (settings.retry_count ? parseInt(settings.retry_count) : config.run.retry_count)
const retryCount = Number.parseInt(String(settings.retry_count ?? ""), 10);
config.run.retry_count = Number.isFinite(retryCount) && retryCount > 0
? retryCount
: config.run.retry_count;
config.run.loop_plan = settings.loop_plan !== undefined ? settings.loop_plan : config.run.loop_plan
const bgi_tools_token = settings.bgi_tools_token || "Authorization= "
// const list = Array.from(bgi_tools_token.split("=")).map(item => item.trim());
// config.bgi_tools.token.name = list[0]
// config.bgi_tools.token.value = list[1]
const separatorIndex = bgi_tools_token.indexOf("=");
if (separatorIndex !== -1) {
config.bgi_tools.token.name = bgi_tools_token.substring(0, separatorIndex).trim();
config.bgi_tools.token.value = bgi_tools_token.substring(separatorIndex + 1).trim();
} else {
config.bgi_tools.token.name = bgi_tools_token.trim();
config.bgi_tools.token.value = "";
}
config.bgi_tools.api.httpPullJsonConfig = settings.bgi_tools_http_pull_json_config
config.bgi_tools.api.httpPushAllJsonConfig = settings.bgi_tools_http_push_all_json_config
config.bgi_tools.api.httpPushAllCountryConfig = settings.bgi_tools_http_push_all_country_config
config.bgi_tools.open.open_push = settings.bgi_tools_open_push
log.debug(`|bgi_tools:{1}`, JSON.stringify(config.bgi_tools))
// const text = file.readTextSync(config.path.domain);
// log.info("config.path.domain:{1}",config.path.domain)
// log.info("text:{2}",text)
// const list = JSON.parse(text);
// log.info("list:{3}",[...list])
const domainList = JSON.parse(file.readTextSync(config.path.domain)) || [{
name: undefined,
type: undefined,
hasOrder: false,
list: []
}]
config.domainList.push(...domainList)
config.domainList.forEach(item => {
if (!config.domainNames.has(item.name)) {
config.domainNames.add(item.name)
}
config.domainMap.set(item.name, item.list);
if (item?.hasOrder) {
let index = 1
//设置顺序
item.list.forEach(item2 => {
if (!config.itemNames.has(item2)) {
config.itemNames.add(item2)
}
config.domainOrderMap.set(item2, index)
config.domainItemsMap.set(item2, item.name)
index++
})
}
})
config.run.config = settings.run_config || config.run.config
if (config.domainList.length <= 0) {
throw new Error("配置文件缺失或读取异常!")
}
let loadList = await getValueByMultiCheckboxName('auto_load') || []
const loads = loadList.map(item => {
const load = LoadMap.get(item);
if (!load) {
throw new Error(`无效加载方式: ${item}`);
}
let order = 1
switch (load) {
case LoadType.input:
order = 1;
break;
case LoadType.uid:
order = 2;
break;
case LoadType.bgi_tools:
order = 3;
break;
}
return {load: load, order: order}
})
loads.sort((a, b) => a.order - b.order)
config.run.loads = loads
}
export {
config, LoadType, LoadMap,
checkKey,
initSettings,
getMultiCheckboxMap,
getValueByMultiCheckboxName,
initConfig
}

View File

@@ -0,0 +1,9 @@
[
"蒙德",
"璃月",
"稻妻",
"须弥",
"枫丹",
"纳塔",
"挪德卡莱"
]

View File

@@ -0,0 +1,324 @@
[
{
"name": "无光的深都",
"type": "天赋",
"hasOrder": true,
"list": [
"「月光」的哲学",
"「乐园」的哲学",
"「浪迹」的哲学"
]
},
{
"name": "蕴火的幽墟",
"type": "天赋",
"hasOrder": true,
"list": [
"「角逐」的哲学",
"「焚燔」的哲学",
"「纷争」的哲学"
]
},
{
"name": "苍白的遗荣",
"type": "天赋",
"hasOrder": true,
"list": [
"「公平」的哲学",
"「正义」的哲学",
"「秩序」的哲学"
]
},
{
"name": "昏识塔",
"type": "天赋",
"hasOrder": true,
"list": [
"「诤言」的哲学",
"「巧思」的哲学",
"「笃行」的哲学"
]
},
{
"name": "董色之庭",
"type": "天赋",
"hasOrder": true,
"list": [
"「浮世」的哲学",
"「风雅」的哲学",
"「天光」的哲学"
]
},
{
"name": "太山府",
"type": "天赋",
"hasOrder": true,
"list": [
"「繁荣」的哲学",
"「勤劳」的哲学",
"「黄金」的哲学"
]
},
{
"name": "忘却之峡",
"type": "天赋",
"hasOrder": true,
"list": [
"「自由」的哲学",
"「抗争」的哲学",
"「诗文」的哲学"
]
}
,
{
"name": "失落的月庭",
"type": "武器",
"hasOrder": true,
"list": [
"奇巧秘器的真愿",
"长夜燧火的烈辉",
"终北遗嗣的煌熠"
]
},
{
"name": "深古瞭望所",
"type": "武器",
"hasOrder": true,
"list": [
"贡祭炽心的荣膺",
"谚妄圣主的神面",
"神合秘烟的启示"
]
},
{
"name": "深潮的余响",
"type": "武器",
"hasOrder": true,
"list": [
"悠古弦音的回响",
"纯圣露滴的真粹",
"无垢之海的金杯"
]
},
{
"name": "有顶塔",
"type": "武器",
"hasOrder": true,
"list": [
"谧林涓露的金符",
"绿洲花园的真谛",
"烈日威权的旧日"
]
},
{
"name": "砂流之庭",
"type": "武器",
"hasOrder": true,
"list": [
"远海夷地的金枝",
"鸣神御灵的勇武",
"今昔剧画之鬼人"
]
},
{
"name": "震雷连山密宫",
"type": "武器",
"hasOrder": true,
"list": [
"孤云寒林的神体",
"雾海云间的转还",
"漆黑陨铁的一块"
]
},
{
"name": "塞西莉亚苗圃",
"type": "武器",
"hasOrder": true,
"list": [
"高塔孤王的碎梦",
"凛风奔狼的怀乡",
"狮牙斗士的理想"
]
}
,
{
"name": "月童的库藏",
"type": "圣遗物",
"hasOrder": false,
"list": [
"风起之日",
"晨星与月的晓歌"
]
},
{
"name": "霜凝的机枢",
"type": "圣遗物",
"hasOrder": false,
"list": [
"纺月的夜歌",
"穹境示现之夜"
]
},
{
"name": "荒废砌造坞",
"type": "圣遗物",
"hasOrder": false,
"list": [
"深廊终曲",
"长夜之誓"
]
},
{
"name": "虹灵的净土",
"type": "圣遗物",
"hasOrder": false,
"list": [
"黑曜秘典",
"城勇者绘卷"
]
},
{
"name": "褪色的剧场",
"type": "圣遗物",
"hasOrder": false,
"list": [
"未竟的遐思",
"谐律异想断章"
]
},
{
"name": "临瀑之城",
"type": "圣遗物",
"hasOrder": false,
"list": [
"回声之林夜话",
"昔时之歌"
]
},
{
"name": "罪祸的终末",
"type": "圣遗物",
"hasOrder": false,
"list": [
"黄金剧团",
"逐影猎人"
]
},
{
"name": "熔铁的孤塞",
"type": "圣遗物",
"hasOrder": false,
"list": [
"花海甘露之光",
"水仙之梦"
]
},
{
"name": "赤金的城墟",
"type": "圣遗物",
"hasOrder": false,
"list": [
"乐园遗落之花",
"沙上楼阁史话"
]
},
{
"name": "缘觉塔",
"type": "圣遗物",
"hasOrder": false,
"list": [
"饰金之梦",
"深林的记忆"
]
},
{
"name": "沉眠之庭",
"type": "圣遗物",
"hasOrder": false,
"list": [
"海染砗磲",
"华馆梦醒形骸记"
]
},
{
"name": "花染之庭",
"type": "圣遗物",
"hasOrder": false,
"list": [
"绝缘之旗印",
"追忆之注连"
]
},
{
"name": "岩中幽谷",
"type": "圣遗物",
"hasOrder": false,
"list": [
"辰砂往生录",
"来歆余响"
]
},
{
"name": "华池岩柚",
"type": "圣遗物",
"hasOrder": false,
"list": [
"染血的骑士道",
"昔日宗室之仪"
]
},
{
"name": "无妄引答密宫",
"type": "圣遗物",
"hasOrder": false,
"list": [
"炽烈的炎之魔女",
"渡过烈火的贤人"
]
},
{
"name": "孤云凌霄之处",
"type": "圣遗物",
"hasOrder": false,
"list": [
"悠古的磐岩",
"逆飞的流星"
]
},
{
"name": "山脊守望",
"type": "圣遗物",
"hasOrder": false,
"list": [
"千岩牢固",
"苍白之火"
]
},
{
"name": "芬德尼尔之顶",
"type": "圣遗物",
"hasOrder": false,
"list": [
"冰风迷途的勇士",
"沉沦之心"
]
},
{
"name": "铭记之谷",
"type": "圣遗物",
"hasOrder": false,
"list": [
"翠绿之影",
"被怜爱的少女"
]
},
{
"name": "仲夏庭园",
"type": "圣遗物",
"hasOrder": false,
"list": [
"如雷的盛怒",
"平息鸣雷的尊者"
]
}
]

544
repo/js/AutoPlan/main.js Normal file
View File

@@ -0,0 +1,544 @@
import {config, initConfig, initSettings, LoadType} from './config/config';
import {ocrUid} from './utils/uid';
import {getDayOfWeek, outDomainUI, throwError} from './utils/tool';
import {pullJsonConfig, pushAllCountryConfig, pushAllJsonConfig} from './utils/bgi_tools';
import {ocrPhysical} from "./utils/physical";
/**
* 自动执行秘境任务的异步函数
* @param {Object} autoFight - 包含秘境自动配置参数的对象
* @returns {Promise<void>} - 执行完成后返回的Promise
*/
async function autoDomain(autoFight) {
log.info(`{0}`, "开始执行秘境任务")
log.warn(`{0}`, "非体力耗尽情况下(受本体限制),等待退出秘境时间较长")
//定死做预留冗余 先不实现 不能指定次数 只能指定启用
let physical_domain = autoFight?.physical
// || [
// {order: 0, name: "原粹树脂", count: 1, open: true},
// {order: 1, name: "浓缩树脂", count: 0, open: false},
// {order: 2, name: "须臾树脂", count: 0, open: false},
// {order: 3, name: "脆弱树脂", count: 0, open: false},
// ]
if ((!physical_domain) || physical_domain.filter(item => item?.open).length === 0) {
const names = config.user.physical.names;
physical_domain = []
names.forEach((name, index) => {
physical_domain.push({order: index, name: name, open: index === 0})
})
}
physical_domain.sort((a, b) => a.order - b.order)
// 不包含原粹树脂的和
const noOriginalSum = physical_domain.filter(item => item?.name.trim() !== "原粹树脂")
.filter(item => item?.open).length;//求和
// 只包含原粹树脂的和
const originalSum = physical_domain.filter(item => item?.name?.trim() === "原粹树脂")
.filter(item => item?.open).length;
const resinPriorityList = physical_domain.filter(item => item?.open).map(item => item?.name?.trim())
// /** 树脂使用优先级列表 */
// resinPriorityList: string[];
// /** 使用原粹树脂次数 */
// originalResinUseCount: number;
// /** 使用浓缩树脂次数 */
// condensedResinUseCount: number;
// /** 使用须臾树脂次数 */
// transientResinUseCount: number;
// /** 使用脆弱树脂次数 */
// fragileResinUseCount: number;
await sleep(1000)
//流程->返回主页 打开地图 返回主页
const physicalOcr = await ocrPhysical(true, true)
config.user.physical.current = physicalOcr.current
config.user.physical.min = physicalOcr.min
const physical = config.user.physical
if (physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
throwError(`体力不足,当前体力${physical.current},最低体力${physical.min},请手动补充体力后重试`)
}
// 创建秘境参数对象初始化值为0
let domainParam = new AutoDomainParam();
//关闭分解
domainParam.autoArtifactSalvage = false
//关闭榨干原粹树脂
domainParam.specifyResinUse = true
//配置树脂使用优先级
if (resinPriorityList.length > 0) {
domainParam.SetResinPriorityList(...resinPriorityList)
}
// log.debug(`开始执行秘境任务`)
//秘境名称
domainParam.DomainName = autoFight.domainName || domainParam.DomainName;
log.debug(`秘境名称:${domainParam.DomainName}`)
//队伍名称
domainParam.PartyName = autoFight.partyName || domainParam.PartyName;
log.debug(`队伍名称:${domainParam.PartyName}`)
if (autoFight.sundaySelectedValue) {
//周日|限时选择的值
domainParam.SundaySelectedValue = "" + (autoFight.sundaySelectedValue || domainParam.SundaySelectedValue);
}
log.debug(`周日|限时选择的值:${domainParam.SundaySelectedValue}`)
//副本轮数
try {
domainParam.DomainRoundNum = parseInt((autoFight.domainRoundNum || domainParam.DomainRoundNum) + "");
} catch (e) {
log.debug(`副本轮数:${autoFight.domainRoundNum}`)
throwError(e.message)
}
log.debug(`副本轮数:${domainParam.DomainRoundNum}`)
try {
// 复活重试
for (let i = 0; i < config.run.retry_count; i++) {
try {
await dispatcher.RunAutoDomainTask(domainParam);
// 其他场景不重试
break;
} catch (e) {
const errorMessage = e.message
// 只有选择了秘境的时候才会重试
if (errorMessage.includes("复活") && domainParam.DomainName) {
continue;
}
throw e;
}
}
} finally {
log.info(`{0}`, "执行完成")
// 退出秘境
await outDomainUI()
}
}
/**
* 自动执行地脉花任务的异步函数
* @param autoLeyLineOutcrop
* @returns {Promise<void>}
*/
async function autoLeyLineOutcrop(autoLeyLineOutcrop) {
//todo :地脉花
// autoLeyLineOutcrop = {
// "count": 0,
// "country": "country_cb3d792be8db",
// "leyLineOutcropType": "leyLineOutcropType_f259b77fabcb",
// // "isResinExhaustionMode": true,
// // "openModeCountMin": true,
// "useAdventurerHandbook": false,
// "friendshipTeam": "friendshipTeam_7122cab56b16",
// "team": "team_d0798ca3aa27",
// "timeout": 0,
// "isGoToSynthesizer": false,
// "useFragileResin": false,
// "useTransientResin": false,
// "isNotification": false
// }
log.info(`{0}`, "开始执行地脉任务")
// if (true) {
// log.warn("地脉 暂不支持")
// return
// }
let param = new AutoLeyLineOutcropParam(parseInteger(autoLeyLineOutcrop.count + ""), autoLeyLineOutcrop.country, autoLeyLineOutcrop.leyLineOutcropType);
// let param = new AutoLeyLineOutcropParam();
// param.count = parseInteger(autoLeyLineOutcrop.count+"");
// param.country = autoLeyLineOutcrop.country;
// param.leyLineOutcropType = autoLeyLineOutcrop.leyLineOutcropType;
//和本体保持一致
param.useAdventurerHandbook = !autoLeyLineOutcrop.useAdventurerHandbook;
param.friendshipTeam = autoLeyLineOutcrop.friendshipTeam;
param.team = autoLeyLineOutcrop.team;
param.timeout = autoLeyLineOutcrop.timeout;
param.isGoToSynthesizer = autoLeyLineOutcrop.isGoToSynthesizer;
param.useFragileResin = autoLeyLineOutcrop.useFragileResin;
param.useTransientResin = autoLeyLineOutcrop.useTransientResin;
param.isNotification = autoLeyLineOutcrop.isNotification;
param.isResinExhaustionMode = true;
param.openModeCountMin = true;
await sleep(1000)
// 复活重试
for (let i = 0; i < config.run.retry_count; i++) {
try {
await dispatcher.RunAutoLeyLineOutcropTask(param);
// 其他场景不重试
break;
} catch (e) {
const errorMessage = e.message
// 只有选择了秘境的时候才会重试
if (errorMessage.includes("复活")) {
continue;
}
throw e;
}
}
}
/**
* 自动执行列表处理函数
* @param {Array} autoRunOrderList - 包含自动配置的数组
*/
async function autoRunList(autoRunOrderList) {
//计划执行
for (const item of autoRunOrderList) {
await sleep(3000)
if (item.runType === config.user.runTypes[0]) {
await autoDomain(item.autoFight);
} else if (item.runType === config.user.runTypes[1]) {
await autoLeyLineOutcrop(item.autoLeyLineOutcrop);
}
}
}
// 辅助函数:安全地解析 day 字段
function parseInteger(day) {
if (day == null || String(day).trim() === "") {
return undefined; // 空值或无效值返回 undefined
}
const parsedDay = parseInt(String(day).trim(), 10);
return isNaN(parsedDay) ? undefined : parsedDay; // 非法数字返回 undefined
}
/**
* 根据不同的加载方式加载秘境配置
* @param {string} Load - 加载方式类型如uid或input
* @param {Set} autoOrderSet - 用于存储秘境顺序的Set集合
* @param {string} runConfig - 输入的配置字符串仅在Load为input时使用
*/
async function loadMode(Load, autoOrderSet, runConfig) {
switch (Load) {
case LoadType.input:
// 通过输入字符串方式加载配置
if (runConfig) {
// 处理输入字符串:去除首尾空格,将中文逗号替换为英文逗号,然后按逗号分割
runConfig.trim().replaceAll('', ',').split(",").forEach(
item => {
// 将当前项按"|"分割成数组
let arr = item.split("|")
// 类型|执行日期|执行顺序
let index = 0
let runType = arr[index]; // 解析运行类型
index++
const rawDays = arr[index];
let days = (rawDays != null && String(rawDays).trim() !== "")
? String(rawDays).split('/').map(d => parseInt(d.trim(), 10)).filter(d => !isNaN(d))
: [];
// let days = arr[index].trim() !== ""
// ? arr[index].split('/').map(d => parseInt(d.trim())).filter(d => !isNaN(d))
// : [];
index++
// 解析顺序值,处理可能的无效值
let order = (() => {
const rawOrder = arr[index]; // 获取原始值
if (rawOrder == null || String(rawOrder).trim() === "") {
return 0; // 若为空或无效值,默认返回 0
}
const parsedOrder = parseInt(String(rawOrder).trim(), 10); // 转换为整数
return isNaN(parsedOrder) ? 0 : parsedOrder; // 若转换失败,返回默认值 0
})();
index++
// 创建秘境顺序对象
let autoOrder = {
order: order, // 顺序值
// day: day,// 执行日期
runType: runType, // 运行类型
days: days, // 执行日期(数组)
autoFight: undefined, // 秘境信息对象
autoLeyLineOutcrop: undefined // 地脉信息对象
}
if (!config.user.runTypes.includes(runType)) {
throwError(`运行类型${runType}输入错误`)
} else if (config.user.runTypes[0] === runType) {
// 创建秘境信息对象
let autoFight = {
domainName: undefined,//秘境名称
partyName: undefined,//队伍名称
sundaySelectedValue: 1,//周日|限时选择的值
domainRoundNum: 0,//副本轮数
}
//"|队伍名称|秘境名称/刷取物品名称|刷几轮|限时/周日,..."
let partyName = arr[index]; // 解析队伍名称
index++
let domainName = arr[index]; // 解析秘境名称
index++
let domainRoundNum = arr[index]; // 解析副本轮数
index++
let sundaySelectedValue = "1"
if (index <= arr.length - 1)
sundaySelectedValue = arr[index]; // 解析周日|限时选择的值
// 检查秘境名称是否有效
if (!config.domainNames.has(domainName)) {
//秘境名称没有记录 查询是否是物品名称
if (config.itemNames.has(domainName)) {
const domainNameTemp = config.domainItemsMap.get(domainName);
if (!domainNameTemp) {
throw new Error(`${domainName} 输入错误`);
}
if (index <= arr.length - 1) {
const domainSelectedValue = parseInt(config.domainOrderMap.get(domainName) + "");
sundaySelectedValue = domainSelectedValue
}
domainName = domainNameTemp
} else {
throw new Error(`${domainName} 输入错误`);
}
}
// 设置秘境信息的各个属性
autoFight.partyName = partyName // 队伍名称
autoFight.domainName = domainName // 秘境名称
autoFight.domainRoundNum = domainRoundNum // 副本轮数
autoFight.sundaySelectedValue = sundaySelectedValue // 周日|限时选择的值
autoOrder.autoFight = autoFight // 将秘境信息对象添加到秘境顺序对象中
} else if (config.user.runTypes[1] === runType) {
//"|队伍名称|国家|刷几轮|花类型|好感队|是否使用脆弱树脂|是否使用须臾树脂|是否前往合成台合成浓缩树脂|是否使用冒险之证|发送详细通知|战斗超时时间,..."
let autoLeyLineOutcrop = {
count: 0, // 刷几次0=自动/无限)
country: undefined, // 国家地区
leyLineOutcropType: undefined, // 需映射为经验/摩拉
useAdventurerHandbook: false, // 是否使用冒险之证
friendshipTeam: "", // 好感队伍ID
team: "", // 主队伍ID
timeout: 120, // 超时时间(秒)
isGoToSynthesizer: false, // 是否前往合成台
useFragileResin: false, // 使用脆弱树脂
useTransientResin: false, // 使用须臾树脂(须臾=Transient
isNotification: false // 是否通知
}
autoLeyLineOutcrop.team = arr[index]
index++
autoLeyLineOutcrop.country = arr[index]
index++
autoLeyLineOutcrop.count = arr[index]
index++
autoLeyLineOutcrop.leyLineOutcropType = arr[index]
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.friendshipTeam = arr[index]
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.useFragileResin = (arr[index] != null && arr[index].trim() !== "")
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.useTransientResin = (arr[index] != null && arr[index].trim() !== "")
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.isGoToSynthesizer = (arr[index] != null && arr[index].trim() !== "")
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.useAdventurerHandbook = (arr[index] != null && arr[index].trim() !== "")
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.isNotification = (arr[index] != null && arr[index].trim() !== "")
index++
if (index <= arr.length - 1)
autoLeyLineOutcrop.timeout = parseInteger(arr[index])
autoOrder.autoLeyLineOutcrop = autoLeyLineOutcrop // 将地脉信息对象添加到顺序对象中
}
// 将秘境顺序对象添加到列表中
autoOrderSet.add(autoOrder)
}
)
}
break
case LoadType.uid:
// 通过UID方式加载配置
const uid = config.user.uid || (await ocrUid()) // 获取用户UID如果未配置则通过OCR识别获取
// const configAutoFightOrderMap = JSON.parse(file.readTextSync(config.path.runConfig)) || new Map() // 读取本地配置文件并转换为Map对象
// const uidConfigList = configAutoFightOrderMap.get(uid + "") || []; // 获取当前UID对应的配置列表
const configAutoFightOrderMap = JSON.parse(file.readTextSync(config.path.runConfig)) || {} // 读取本地配置文件
const uidConfigList = configAutoFightOrderMap[uid + ""] || []; // 获取当前UID对应的配置列表
if (uidConfigList?.length > 0) {
// 如果配置列表不为空,遍历并添加到结果集合中
uidConfigList.forEach(item => {
// 将秘境顺序对象添加到列表中
// 主逻辑优化
// if (item.day !== undefined) {
// item.day = parseInteger(item.day);
// }
if (item.days && item.days.length > 0) {
item.days = item.days.map(day => parseInteger(day))
// item.day = parseInteger(item.day);
}
autoOrderSet.add(item)
})
}
break
case LoadType.bgi_tools:
// 通过bgi_tools方式加载配置
log.info(`开始拉取bgi_tools配置`)
const uidConfigListBgiTools = await pullJsonConfig(config.user.uid + '', config.bgi_tools.api.httpPullJsonConfig) || []
if (uidConfigListBgiTools?.length > 0) {
// 如果配置列表不为空,遍历并添加到结果集合中
uidConfigListBgiTools.forEach(item => {
// 将秘境顺序对象添加到列表中
// 主逻辑优化
if (item.days && item.days.length > 0) {
item.days = item.days.map(day => parseInteger(day))
// item.day = parseInteger(item.day);
}
autoOrderSet.add(item)
})
}
break
default:
throw new Error("请先配置加载方式");
// break;
}
}
/**
* 初始化执行顺序列表
* @param {string} domainConfig - 输入的字符串,包含秘境顺序信息
* @returns {Array} 返回处理后的秘境顺序列表
*/
async function initRunOrderList(domainConfig) {
const autoFightOrderSet = new Set() // 存储秘境顺序列表的数组
/* let te = {
order: 1, // 顺序值
day: 0,// 执行日期
autoFight: {
domainName: undefined,//秘境名称
partyName: undefined,//队伍名称
sundaySelectedValue: undefined,//周日|限时选择的值
domainRoundNum: undefined,//副本轮数
} // 秘境信息对象
}*/
// let Load = LoadType.uid
for (const Load of config.run.loads) {
await loadMode(Load.load, autoFightOrderSet, domainConfig);
}
// 检查是否已配置秘境
if (!autoFightOrderSet || autoFightOrderSet.size <= 0) {
throw new Error("请先配置体力配置");
}
// 返回处理后的秘境顺序列表
let from = Array.from(autoFightOrderSet);
let dayOfWeek = await getDayOfWeek();
log.debug(`old-from:{0}`, JSON.stringify(from))
from = from
//过滤掉不执行的秘境
.filter(item => config.user.runTypes.includes(item.runType))
.filter(item => {
// if (item.day) {
// return item.day === dayOfWeek.day
// }
log.debug(`[{1}]item.days.length:{0}`, dayOfWeek.day, item?.days?.length || 0)
if (item.days && item.days.length > 0) {
const includes = item.days.includes(dayOfWeek.day);
log.debug(`[{1}]item.days:{0}`, dayOfWeek.day, JSON.stringify(item.days))
return includes;
}
return true
})
from.sort((a, b) => b.order - a.order)
log.debug(`from:{0}`, JSON.stringify(from))
return from;
}
/**
* 初始化函数
* 该函数用于执行初始化操作使用async/await处理异步操作
*/
async function init() {
// 调用initConfig函数并等待其完成
// 这是一个异步初始化配置的步骤
await initSettings()
await initConfig();
}
/**
* 主函数,用于执行秘境自动刷取任务
* @async
*/
async function main() {
// 初始化配置
await init();
if (config.bgi_tools.open.open_push) {
log.info(`开始推送bgi_tools配置`)
await pushAllJsonConfig(JSON.parse(file.readTextSync(config.path.domain)), config.bgi_tools.api.httpPushAllJsonConfig, config.bgi_tools.token)
await pushAllCountryConfig(JSON.parse(file.readTextSync(config.path.countryList)), config.bgi_tools.api.httpPushAllCountryConfig, config.bgi_tools.token)
}
// 获取配置
let runConfig = config.run.config;
//"队伍名称|秘境名称/刷取物品名称|刷几轮|限时/周日|周几执行(0-6)不填默认执行|执行顺序,..."
const autoRunOrderList = await initRunOrderList(runConfig);
const list = autoRunOrderList.filter(item =>
(item.runType === config.user.runTypes[0] && item?.autoFight.domainRoundNum > 0)
|| (item.runType === config.user.runTypes[1] && item?.autoLeyLineOutcrop.count > 0)
)
if (list?.length > 0) {
//循环跑
while (true) {
await autoRunList(list);
if (config.run.loop_plan) {
// 重新获取当前体力值
const physicalOcr = await ocrPhysical(true, true);
config.user.physical.current = physicalOcr.current;
//循环
if (config.user.physical.current < config.user.physical.min) {
//体力耗尽
break
}
} else {
//不循环
break
}
}
} else {
log.info(`本日无计划`)
}
}
(async function () {
// await test()
// await test1()
// await test2()
await main()
})()
async function test() {
await init();
const text = file.readTextSync(config.path.domain);
// log.info("settings:{1}",config.info.settings)
// log.info("text:{1}",text)
const list = JSON.parse(text);
// log.info("list:{1}",list)
log.info("httpPullJsonConfig:{1}", config.bgi_tools.api.httpPushAllJsonConfig)
log.info("|test==>config.bgi_tools:{1}", JSON.stringify(config.bgi_tools))
await pushAllJsonConfig(list, config.bgi_tools.api.httpPushAllJsonConfig)
}
async function test1() {
await init();
// log.info("text:{1}",text)
// log.info("list:{1}",list)
log.info("httpPullJsonConfig:{1}", config.bgi_tools.api.httpPullJsonConfig)
log.info("|test==>config.bgi_tools:{1}", JSON.stringify(config.bgi_tools))
const list = await pullJsonConfig(config.user.uid, config.bgi_tools.api.httpPullJsonConfig)
log.info("list:{1}", JSON.stringify(list))
}
async function test2() {
await init();
await outDomainUI();
}

View File

@@ -0,0 +1,20 @@
{
"name": "自动体力计划",
"version": "0.0.1",
"description": "",
"settings_ui": "settings.json",
"main": "main.js",
"bgi_version": "0.57.2",
"key": "oiJbmjU2R0NniiwiZxh",
"authors": [
{
"name": "云端客",
"links": "https://github.com/Kirito520Asuna"
}
],
"dependencies": [],
"http_allowed_urls": [
"https://*",
"http://*"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,71 @@
[
{
"name": "key",
"type": "input-text",
"label": "密钥(去看文档)"
},
{
"name": "auto_load",
"type": "multi-checkbox",
"label": "加载模式\n说明太长 去看文档",
"options": ["输入加载","UID加载","bgi_tools加载"] ,
"default": ["输入加载","UID加载"]
},
{
"name": "run_config",
"type": "input-text",
"label": "自动秘境计划配置\n语法:说明太长 去看文档",
"default": ""
},
{
"name": "loop_plan",
"type": "checkbox",
"label": "循环体力计划\n体力耗尽自动终止"
},
{
"name": "retry_count",
"type": "select",
"label": "复活重试次数",
"options": [
"1","2","3",
"4","5","6",
"7","8","9"
] ,
"default": "3"
},
{
"type": "separator"
},
{
"type": "separator"
},
{
"name": "bgi_tools_http_pull_json_config",
"type": "input-text",
"label": "bgi_tools拉取配置api(去看文档)",
"default": "http://127.0.0.1:8081/bgi/auto/plan/json"
},
{
"name": "bgi_tools_open_push",
"type": "checkbox",
"label": "开始推送bgi_tools配置"
},
{
"name": "bgi_tools_http_push_all_json_config",
"type": "input-text",
"label": "bgi_tools推送全部配置api(去看文档)",
"default": "http://127.0.0.1:8081/bgi/auto/plan/domain/json/all"
},
{
"name": "bgi_tools_http_push_all_country_config",
"type": "input-text",
"label": "bgi_tools推送全部国家配置api(去看文档)",
"default": "http://127.0.0.1:8081/bgi/auto/plan/country/json/all"
},
{
"name": "bgi_tools_token",
"type": "input-text",
"label": "bgi_tools授权token 语法:tokenName=tokenValue(去看文档)",
"default": "Authorization= "
}
]

View File

@@ -0,0 +1,80 @@
/**
* 拉取对应uid的Json数据
* @param uid
* @param http_api
* @returns {Promise<HttpResponse>}
*/
async function pullJsonConfig(uid, http_api) {
http_api += "?uid=" + uid
const res = await http.request("GET", http_api
// , JSON.stringify({"Content-Type": "application/json"})
)
log.debug(`[{0}]res=>{1}`, 'next', JSON.stringify(res))
if (res.status_code === 200 && res.body) {
let result_json = JSON.parse(res.body);
if (result_json?.code === 200) {
return result_json?.data
}
throw new Error("请求失败,error:" + result_json?.message)
}
// return undefined
}
/**
* 推送全部Json数据
* @param Json
* @param http_api
* @returns {Promise<HttpResponse>}
*/
async function pushAllJsonConfig(list = [], http_api,token={name: "Authorization", value: ''}) {
log.info(`list:{1},http:{2}`, list, http_api)
let value = {
"Content-Type": "application/json",
[token.name]: token.value
};
const res = await http.request("POST", http_api, JSON.stringify({json: JSON.stringify(list)}), JSON.stringify(value))
log.debug(`[{0}]res=>{1}`, 'next', JSON.stringify(res))
if (res.status_code === 200 && res.body) {
let result_json = JSON.parse(res.body);
if (result_json?.code === 200) {
return result_json?.data
}
throw new Error("请求失败,error:" + result_json?.message)
}
// return undefined
}
/**
* 推送全部国家Json数据
* @param list
* @param http_api
* @param token
* @returns {Promise<undefined|*>}
*/
async function pushAllCountryConfig(list = [], http_api,token={name: "Authorization", value: ''}) {
log.info(`list:{1},http:{2}`, list, http_api)
let value = {
"Content-Type": "application/json",
[token.name]: token.value
};
const res = await http.request("POST", http_api, JSON.stringify({json: JSON.stringify(list)}), JSON.stringify(value))
log.debug(`[{0}]res=>{1}`, 'next', JSON.stringify(res))
if (res.status_code === 200 && res.body) {
let result_json = JSON.parse(res.body);
if (result_json?.code === 200) {
return result_json?.data
}
throw new Error("请求失败,error:" + result_json?.message)
}
return undefined
}
export {
pullJsonConfig,
pushAllJsonConfig,
pushAllCountryConfig
}

View File

@@ -0,0 +1,165 @@
import {getJsonPath, toMainUi,throwError} from "./tool";
//====================================================
const genshinJson = {
width: 1920,//genshin.width,
height: 1080,//genshin.height,
}
// const MinPhysical = settings.minPhysical?parseInt(settings.minPhysical+''):parseInt(20+'')
// const OpenModeCountMin = settings.openModeCountMin
// let AlreadyRunsCount=0
// let NeedRunsCount=0
const TemplateOrcJson={x: 1568, y: 16, width: 225, height: 60,}
//====================================================
/**
* 从字符串中提取数字并组合成一个整数
* @param {string} str - 包含数字的字符串
* @returns {number} - 由字符串中所有数字组合而成的整数
*/
async function saveOnlyNumber(str,defaultValue=0) {
// 使用正则表达式匹配字符串中的所有数字
// \d+ 匹配一个或多个数字
// .join('') 将匹配到的数字数组连接成一个字符串
// parseInt 将连接后的字符串转换为整数
try {
return parseInt(str.match(/\d+/g).join(''));
}catch (e) {
return defaultValue
}
}
/**
* 识别原粹树脂(体力)的函数
* @param {boolean} [opToMainUi=false] - 是否操作到主界面
* @param {boolean} [openMap=false] - 是否打开地图界面
* @param {number} [minPhysical=20] - 最小可执行体力值
* @param {boolean} [isResinExhaustionMode=true] - 是否启用体力识别功能
* @returns {Promise<Object>} 返回一个包含识别结果的Promise对象
* - ok {boolean}: 是否可执行(体力是否足够)
* - min {number}: 最小可执行体力值
* - current {number}: 当前剩余体力值
*/
async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isResinExhaustionMode=true) {
// 检查是否启用体力识别功能,如果未启用则直接返回默认结果
if (!isResinExhaustionMode) {
log.info(`===未启用===`)
return {
ok: true,
min: 0,
current: 0,
}
}
log.debug(`===开始识别原粹树脂===`)
let ms = 1000 // 定义操作延迟时间(毫秒)
if (opToMainUi) {
await sleep(ms)
await toMainUi(); // 切换到主界面
}
if (openMap){
await sleep(ms)
//打开地图界面
await keyPress('M')
}
await sleep(ms)
log.debug(`===[点击+]===`)
//点击+ 按钮 x=1264,y=39,width=18,height=19
let add_buttonJSON = getJsonPath('add_button');
let add_objJson = {
path: `${add_buttonJSON.path}${add_buttonJSON.name}${add_buttonJSON.type}`,
x: 1373,
y: 22,
width: 52,
height: 49,
}
let templateMatchAddButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${add_objJson.path}`), add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
let regionA = captureGameRegion()
// let deriveCrop = regionA.DeriveCrop(add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
try {
let buttonA = regionA.find(templateMatchAddButtonRo);
await sleep(ms)
if (!buttonA.isExist()) {
log.error(`${add_objJson.path}匹配异常`)
throwError(`${add_objJson.path}匹配异常`)
}
await buttonA.click()
}finally {
// deriveCrop.dispose()
regionA.dispose()
}
await sleep(ms)
log.debug(`===[定位原粹树脂]===`)
//定位月亮
let jsonPath = getJsonPath('yue');
let tmJson = {
path: `${jsonPath.path}${jsonPath.name}${jsonPath.type}`,
x: TemplateOrcJson.x,
y: TemplateOrcJson.y,
width: TemplateOrcJson.width,
height: TemplateOrcJson.height,
}
let templateMatchButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${tmJson.path}`), tmJson.x, tmJson.y, tmJson.width, tmJson.height);
let region =captureGameRegion()
let button
try {
button = region.find(templateMatchButtonRo);
await sleep(ms)
if ((!button)||!button.isExist()) {
log.error(`${tmJson.path} 匹配异常`)
throwError(`${tmJson.path} 匹配异常`)
}
}finally {
region.dispose()
}
log.debug(`===[识别原粹树脂]===`)
//识别体力 x=1625,y=31,width=79,height=30 / x=1689,y=35,width=15,height=26
let ocr_obj = {
// x: 1623,
x: button.x + button.width,
// y: 32,
y: button.y,
// width: 61,
width: Math.abs(genshinJson.width - button.x - button.width),
height: 26
}
log.debug(`ocr_obj: x={x},y={y},width={width},height={height}`, ocr_obj.x, ocr_obj.y, ocr_obj.width, ocr_obj.height)
let region3 = captureGameRegion()
try {
let recognitionObjectOcr = RecognitionObject.Ocr(ocr_obj.x, ocr_obj.y, ocr_obj.width, ocr_obj.height);
let res = region3.find(recognitionObjectOcr);
log.debug(`[OCR原粹树脂]识别结果: ${res.text}, 原始坐标: x=${res.x}, y=${res.y},width:${res.width},height:${res.height}`);
let text=res.text.split('/')[0]
let current = await saveOnlyNumber(text)
let execute = (current - minPhysical) >= 0
log.debug(`最小可执行原粹树脂:{min},原粹树脂:{key}`, minPhysical, current,)
// await keyPress('VK_ESCAPE')
return {
ok: execute,
min: minPhysical,
current: current,
}
} catch (e) {
throwError(`识别失败,err:${e.message}`)
} finally {
region3.dispose()
//返回地图操作
if (opToMainUi) {
await toMainUi(); // 切换到主界面
}
}
}
export {
ocrPhysical,
}

View File

@@ -0,0 +1,348 @@
/**
* 对指定区域进行OCR文字识别
* @param {number} x - 区域左上角x坐标默认为0
* @param {number} y - 区域左上角y坐标默认为0
* @param {number} w - 区域宽度默认为1920
* @param {number} h - 区域高度默认为1080
* @returns {Promise<string|null>} 返回识别到的文本内容如果识别失败则返回null
*/
async function ocrRegion(x = 0,
y = 0,
w = 1920,
h = 1080) {
// 创建OCR识别对象使用指定的坐标和尺寸
let recognitionObjectOcr = RecognitionObject.Ocr(x, y, w, h);
// 捕获游戏区域图像
let region3 = captureGameRegion()
try {
// 在捕获的区域中查找OCR识别对象
let res = region3.find(recognitionObjectOcr);
// 返回识别到的文本内容如果不存在则返回undefined
return res?.text
} catch (e) {
// 捕获并记录错误信息
log.error("识别异常:{1}", e.message)
return null
} finally {
// 确保释放区域资源
region3.dispose()
}
}
/**
* 获取当前日期的星期信息
* @param {boolean} [calibrationGameRefreshTime=true] 是否进行游戏刷新时间校准
* @returns {Object} 返回包含星期数字和星期名称的对象
*/
async function getDayOfWeek(calibrationGameRefreshTime = true) {
// 获取当前日期对象
let today = new Date();//4点刷新 所以要减去4小时
if (calibrationGameRefreshTime) {
today.setHours(today.getHours() - 4); // 减去 4 小
}
// 获取当前日期是星期几0代表星期日1代表星期一以此类推
const day = today.getDay();
// 创建包含星期名称的数组
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
let weekDay = `${weekDays[day]}`;
log.debug(`今天是[{day}]`, day)
log.debug(`今天是[{weekDays}]`, weekDay)
// 返回包含星期数字和对应星期名称的对象
return {
day: day,
dayOfWeek: weekDay
}
}
const commonPath = 'assets/'
const commonMap = new Map([
['main_ui', {
path: `${commonPath}`,
name: 'paimon_menu',
type: '.png',
}],
['yue', {
path: `${commonPath}`,
name: 'yue',
type: '.png',
}],
['200', {
path: `${commonPath}`,
name: '200',
type: '.png',
}],
['add_button', {
path: `${commonPath}`,
name: 'add_button',
type: '.jpg',
}],
['out_domain', {
path: `${commonPath}`,
name: 'out_domain',
type: '.jpg',
}],
])
const genshinJson = {
width: 1920,//genshin.width,
height: 1080,//genshin.height,
}
/**
* 根据键值获取JSON路径
* @param {string} key - 要查找的键值
* @returns {any} 返回与键值对应的JSON路径值
*/
function getJsonPath(key) {
return commonMap.get(key); // 通过commonMap的get方法获取指定键对应的值
}
// 判断是否在主界面的函数
const isInMainUI = () => {
// let name = '主界面'
let main_ui = getJsonPath('main_ui');
// 定义识别对象
let paimonMenuRo = RecognitionObject.TemplateMatch(
file.ReadImageMatSync(`${main_ui.path}${main_ui.name}${main_ui.type}`),
0,
0,
genshinJson.width / 3.0,
genshinJson.width / 5.0
);
let captureRegion = captureGameRegion();
try {
let res = captureRegion.find(paimonMenuRo);
return !res.isEmpty();
} finally {
captureRegion.dispose()
}
};
async function toMainUi() {
let ms = 300
let index = 1
await sleep(ms);
while (!isInMainUI()) {
await sleep(ms);
await genshin.returnMainUi(); // 如果未启用,则返回游戏主界面
await sleep(ms);
if (index > 3) {
throwError(`多次尝试返回主界面失败`);
}
index += 1
}
}
const isInOutDomainUI = async () => {
// // let name = '主界面'
// let main_ui = getJsonPath('out_domain');
// // 定义识别对象
// let paimonMenuRo = RecognitionObject.TemplateMatch(
// file.ReadImageMatSync(`${main_ui.path}${main_ui.name}${main_ui.type}`),
// 0,
// 0,
// genshinJson.width / 3.0,
// genshinJson.width / 5.0
// );
// let captureRegion = captureGameRegion();
// try {
// let res = captureRegion.find(paimonMenuRo);
// return !res.isEmpty();
// }finally {
// captureRegion.dispose()
// }
//509, 259, 901, 563
const text = "退出秘境";
const ocrRegion = {
x: 509,
y: 259,
w: 901,
h: 563
}
const find = await findText(text, ocrRegion.x, ocrRegion.y, ocrRegion.w, ocrRegion.h)
log.debug("识别结果:{1}", find)
return find && find.includes(text)
};
/**
* 退出秘境的UI处理函数
* 该函数用于处理退出秘境界面的相关操作,包括点击确认按钮和检测界面状态
*/
async function outDomainUI() {
log.info(`{0}`,"退出秘境");
const ocrRegion = {
x: 509,
y: 259,
w: 901,
h: 563
}
let ms = 300
let index = 1
let tryMax = false
let inMainUI = false
await sleep(ms);
//点击确认按钮
await findTextAndClick('地脉异常')
await sleep(ms);
while (!await isInOutDomainUI()) {
if (isInMainUI()) {
inMainUI = true
break
}
await sleep(ms);
await keyPress("ESCAPE");
await sleep(ms * 2);
if (index > 3) {
log.error(`多次尝试匹配退出秘境界面失败 假定已经退出处理`);
tryMax = true
break
}
index += 1
}
if ((!tryMax) && (!inMainUI) && await isInOutDomainUI()) {
try {
//点击确认按钮
await findTextAndClick('确认', ocrRegion.x, ocrRegion.y, ocrRegion.w, ocrRegion.h)
} catch (e) {
// log.error(`多次尝试点击确认失败 假定已经退出处理`);
}
}
}
/**
* 在指定区域内查找文本内容
* @param {string} text - 要查找的文本内容
* @param {number} x - 查找区域的左上角x坐标默认为0
* @param {number} y - 查找区域的左上角y坐标默认为0
* @param {number} w - 查找区域的宽度默认为1920
* @param {number} h - 查找区域的高度默认为1080
* @param {number} attempts - 尝试查找的次数默认为5
* @param {number} interval - 每次尝试之间的间隔时间(毫秒)默认为50
* @returns {Promise<string>} 返回找到的文本内容,如果未找到则返回空字符串
*/
async function findText(
text,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50,
) {
const keyword = text.toLowerCase(); // 将搜索关键字转换为小写,实现不区分大小写的搜索
for (let i = 0; i < attempts; i++) { // 循环尝试查找文本最多尝试attempts次
const gameRegion = captureGameRegion(); // 捕获游戏区域图像
try {
const ro = RecognitionObject.Ocr(x, y, w, h); // 创建OCR识别对象指定识别区域
const results = gameRegion.findMulti(ro); // 在区域内查找所有匹配的文本
// 遍历查找结果
for (let j = 0; j < results.count; j++) {
const res = results[j];
// 检查结果是否存在、包含文本内容,并且文本包含搜索关键字
if (
res.isExist() &&
res.text &&
res.text.toLowerCase().includes(keyword)
) {
return res.text; // 找到匹配文本,直接返回
}
}
} finally {
gameRegion.dispose(); // 确保释放游戏区域资源
}
await sleep(interval); // 等待指定的时间后进行下一次尝试
}
return ""; // 未找到匹配文本,返回空字符串
}
/**
* 通用找文本并点击OCR
* @param {string} text 目标文本(单个文本)
* @param {number} [x=0] OCR 区域左上角 X
* @param {number} [y=0] OCR 区域左上角 Y
* @param {number} [w=1920] OCR 区域宽度
* @param {number} [h=1080] OCR 区域高度
* @param {number} [attempts=5] OCR 尝试次数
* @param {number} [interval=50] 每次 OCR 之间的等待间隔(毫秒)
* @param {number} [preClickDelay=50] 点击前等待时间(毫秒)
* @param {number} [postClickDelay=50] 点击后等待时间(毫秒)
*
* @returns
* - RecognitionResult | null
*/
async function findTextAndClick(
text,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50,
preClickDelay = 50,
postClickDelay = 50
) {
const keyword = text.toLowerCase();
for (let i = 0; i < attempts; i++) {
const gameRegion = captureGameRegion();
try {
const ro = RecognitionObject.Ocr(x, y, w, h);
const results = gameRegion.findMulti(ro);
for (let j = 0; j < results.count; j++) {
const res = results[j];
if (
res.isExist() &&
res.text &&
res.text.toLowerCase().includes(keyword)
) {
await sleep(preClickDelay);
res.click();
await sleep(postClickDelay);
return res;
}
}
} finally {
gameRegion.dispose();
}
await sleep(interval);
}
return null;
}
/**
* 抛出错误函数
* 该函数用于显示错误通知并抛出错误对象
* @param {string} msg - 错误信息,将用于通知和错误对象
*/
function throwError(msg, isNotification = false) {
// 使用notification组件显示错误通知
// notification.error(`${msg}`);
if (isNotification) {
notification.error(`${msg}`);
}
// 抛出一个包含错误信息的Error对象
throw new Error(`${msg}`);
}
export {
ocrRegion,
getDayOfWeek,
getJsonPath,
isInMainUI,
toMainUi,
isInOutDomainUI,
outDomainUI,
findTextAndClick,
throwError,
}

View File

@@ -0,0 +1,36 @@
import {ocrRegion} from './tool.js'
async function saveOnlyNumber(str) {
str = str ? str : '';
// 使用正则表达式匹配字符串中的所有数字
// \d匹配一个或多个数字
// .join('') 将匹配到的数字数组连接成一个字符串
// parseInt 将连接后的字符串转换为整数
// return parseInt(str.match(/\d+/g).join(''));
const matches = str.match(/\d+/g);
if (!matches) {
return 0; // 或抛出错误
}
return parseInt(matches.join(''), 10);
}
/**
* OCR识别UID的异步函数
* 该函数用于通过OCR技术识别屏幕上特定位置的UID文本
* @returns {Promise<number>} - 异步函数,没有明确的返回值
*/
async function ocrUid() {
// 定义OCR识别的坐标和尺寸参数
let uid_json = {
x: 1683, // OCR识别区域的左上角x坐标
y: 1051, // OCR识别区域的左上角y坐标
width: 234, // OCR识别区域的宽度
height: 28, // OCR识别区域的高度
}
let text = await ocrRegion(uid_json.x, uid_json.y, uid_json.width, uid_json.height);
return await saveOnlyNumber(text);
}
export {
ocrUid,
}