Files
bettergi-scripts-list/repo/js/FullyAutoAndSemiAutoTools/main.js
云端客 57423ede32 全自动 + 半自动路径任务工具箱 (#2819)
* refactor(FullyAutoAndSemiAutoTools): 优化记录合并逻辑并清理废弃代码

- 实现同一天数据合并功能,将重复记录的paths和groupPaths进行合并
- 移除已注释的无用代码块,包括pathRunMap相关逻辑
- 清理废弃的pathing.json配置文件内容
- 修复字符串拼接格式问题,确保正确的换行符处理
- 删除未使用的变量声明和调试代码
- 优化记录列表的数据处理流程

feat(pathing): 添加路径层级父子关系支持并优化显示结构

- 在PATHING_ALL数组对象中添加parent_name字段以支持父子关系
- 修改addUniquePath函数调用以包含parent_name参数
- 添加getChildFolderNameFromRoot函数获取父级文件夹名称
- 在路径排序逻辑中增加parent_name比较条件
- 重构路径显示逻辑添加父级名称分组显示格式
- 更新main函数中的执行流程控制逻辑

feat(auto-tools): 添加UID识别功能支持

- 在Record对象中新增uid字段用于存储用户ID
- 集成uid工具模块,实现OCR识别UID功能
- 添加uid.js工具文件,包含UID识别、验证和比较功能
- 实现在主界面检测和返回主界面的功能
- 集成UID配置验证和错误处理机制

refactor(FullyAutoAndSemiAutoTools): 重构路径执行记录管理

- 将原有的 hasRunning Set 替换为功能更完整的 Record 对象
- 添加路径、错误路径和分组路径的独立记录集合
- 更新路径执行状态检查逻辑以使用新的 Record 结构
- 在路径执行失败时记录错误路径信息
- 为任务分组添加路径记录功能
- 移除手动按键继续执行的交互流程
- 直接执行需要运行的地图任务

feat(auto-tools): 优化自动化脚本执行逻辑

- 添加hasRunning集合避免重复执行相同路径的脚本
- 修复半自动模式判断条件的逻辑错误
- 为runPath、runList、runMap函数添加详细的JSDoc注释
- 优化runPath函数确保执行完成后添加到运行记录
- 统一日志输出中的模式显示使用settings.mode替代硬编码值
- 实现路径去重检查机制防止同一路径被多次执行
- 重构任务执行流程支持Map结构的任务批量处理

refactor(FullyAutoAndSemiAutoTools): 重构路径配置逻辑并优化层级处理

- 修改了路径名称格式,将 levelName 的显示格式从 levelName_level 改为 levelName_level_level
- 移除了对同名路径的过滤判断逻辑
- 注释掉了原有的设置项更新逻辑,改用新的分组方式处理
- 添加了按层级分组的工具函数 groupByLevel
- 实现了基于层级分组的新设置项生成逻辑
- 新增了用于调试的 pathingALLSize 数组(注释状态)
- 重写了多级路径的遍历和配置生成方式

feat(auto-tools): 添加开发者调试模式功能

- 新增 debug 和 isDebug 全局变量用于调试控制
- 实现了 debugKey 函数支持开发者模式下的断点调试
- 添加了 keyMousePress 函数用于检测按键按下和释放事件
- 在 settings.json 中增加 isDebug 复选框和 debug 输入框配置
- 更新自动暂停快捷键标签提醒避免 BGI 快捷键冲突
- 修改 dispatcher.addTimer 为 dispatcher.addTrigger
- 在树形结构生成过程中添加调试日志输出功能

refactor(FullyAutoAndSemiAutoTools): 优化路径选择逻辑和标签显示

- 修改错误提示信息中的字符串格式,添加适当的空格
- 重构路径读取逻辑,将 treePathList 的赋值移到条件判断之后
- 添加 level 变量用于动态控制路径层级显示
- 更新设置选项标签,从静态文本改为动态显示当前路径级别
- 移动 level++ 操作到正确的位置以确保层级计算准确

refactor(FullyAutoAndSemiAutoTools): 优化路径配置和按键监听实现

- 将 pathing.json 的读取逻辑简化为 forEach 方式
- 移除多余的 log.warn 调试信息
- 修复 settings 配置的 JSON 解析问题
- 改进路径同步列表的处理方式
- 添加实时自动拾取任务配置
- 实现新的鼠标键盘按键监听函数 keyMousePress
- 使用新的按键监听函数替换原有 keyPress 实现
- 优化路径执行时的用户交互提示信息

refactor(FullyAutoAndSemiAutoTools): 重构路径处理逻辑并添加父级目录获取功能

- 添加getParentFolderName函数用于获取指定层级的上级文件夹名称
- 使用新的getParentFolderName函数替换原有的路径处理逻辑
- 在treeList中增加parentName字段用于存储父级目录名称
- 修复路径分隔符处理和层级计算问题
- 临时注释掉主执行循环以进行调试

feat(FullyAutoAndSemiAutoTools): 添加路径执行设置配置功能

- 遍历pathRunMap创建多选框设置项
- 为每个路径生成对应的JSON配置对象
- 将路径名称映射到设置项的name字段
- 将路径执行类型数组作为选项值
- 将生成的设置项添加到settingsList数组
- 将设置列表写入manifest.settings_ui文件

feat(auto-tools): 添加半自动工具配置和路径处理功能

- 新增 needRunMap 存储需要运行的任务映射
- 修改 init 函数返回设置配置并解析为 JSON 格式
- 添加 parentJson 配置选项用于选择执行路径
- 重构路径处理逻辑,支持 @ 符号和特殊路径识别
- 注释掉旧的路径处理代码保持兼容性
- 实现主函数中遍历 needRunMap 执行任务列表

feat(FullyAutoAndSemiAutoTools): 添加全自动半自动工具箱功能

- 实现了自动和半自动路径执行功能
- 添加了配置文件初始化和验证机制
- 实现了路径文件的递归读取和树形结构构建
- 添加了路径映射和执行控制逻辑
- 集成了按键暂停和继续执行功能
- 实现了路径列表转换和执行流程管理
- 添加了 manifest.json、settings.json 和 pathing.json 配置文件支持

* refactor(FullyAutoAndSemiAutoTools): 优化运行队列逻辑提高性能

- 使用 Set 替代数组查找操作提升性能
- 实现批量删除机制避免迭代过程中的元素删除问题
- 重构代码结构优先处理上次群组未执行的任务
- 添加 lastRunMap 存储并处理剩余任务
- 优化迭代逻辑确保任务按预期顺序执行

* feat(auto-tools): 添加择优模式功能

- 修改路径列表处理逻辑,为每个路径对象添加选中状态字段
- 更新执行日志显示,添加模式和选中路径信息输出
- 实现择优运行函数,优先执行上次未完成的任务路径
- 添加choose_best配置选项,支持择优模式开关设置
- 优化记录检查逻辑,提升路径匹配性能
- 整理代码结构,将择优运行逻辑封装为独立函数

feat(auto-tools): 添加黑白名单配置功能

- 在设置中添加白名单和黑名单输入框配置
- 新增 config_list 对象存储黑白名单数组
- 实现从设置读取黑白名单并解析为数组
- 添加路径过滤逻辑,支持黑名单排除和白名单优先
- 实现黑白名单预处理,去除空字符串并trim
- 优化路径遍历,跳过黑名单中的路径
fix(FullyAutoAndSemiAutoTools): 修复空映射时的执行逻辑并移除调试代码

- 添加了 needRunMap 非空检查避免无效执行
- 移除了调试用的日志记录和按键等待功能
- 优化了代码流程控制结构
feat(auto-tools): 添加记录管理和用户配置功能

- 定义记录文件路径并实现saveRecord函数保存记录到JSON文件
- 在initRecord函数中添加读取记录文件逻辑,支持Set数据类型的转换
- 实现同一天记录数据合并功能,确保Set属性存在
- 添加用户配置映射管理,支持不同UID的配置存储
- 实现配置模式选择功能,支持刷新和加载两种模式
- 添加错误处理机制确保文件操作的安全性
- 优化配置文件的读写流程,提升数据持久化可靠性

* feat(config): 更新配置文件路径和多复选框数据结构

- 修改配置根目录为 'config' 并更新相关路径引用
- 重构多复选框映射表存储结构,保存标签和选项对象
- 新增根据多选框名称获取JSON数据的异步函数
- 更新从复选框映射表获取值的方法以返回选项数组
- 修改用户设置文件路径和名称为 uid_settings.json
- 添加错误处理机制避免写入配置文件时崩溃
- 优化配置过滤逻辑支持路径和标签匹配
- 新增提取方括号内容的工具函数
- 调整配置界面显示格式和索引计数逻辑

* feat(game): 添加七元素队伍配置功能

- 新增 SevenElement 配置对象,包含七元素及其对应材料映射
- 添加 team 配置对象,支持当前元素和战斗状态管理
- 实现路径执行前检查战斗策略配置的功能
- 添加团队七元素设置选项到配置文件
- 优化路径执行流程,增加调试信息输出

* feat(auto-tool): 添加队伍切换功能并支持元素匹配

- 在SevenElement中新增矿物元素类型
- 实现基于路径的元素匹配和队伍切换逻辑
- 添加SwitchTeam工具模块实现队伍配置功能
- 集成OCR识别和图像匹配的队伍切换机制
- 支持自动翻页查找目标队伍功能
- 实现七天神像传送和队伍缓存清理功能

* feat(auto-tool): 更新七元素配置和按键监听功能

- 添加矿物元素对应的材料列表(夜泊石、石珀、清水玉、万相石)
- 修复teamSevenElements为空时的分割处理逻辑
- 新增fightKeys配置项用于战斗区域识别
- 重构keyMousePress函数支持跳过功能和更精确的状态返回
- 添加keyMousePressStart函数用于兼容旧调用方式
- 在半自动模式中实现路径执行跳过功能
- 优化按键监听循环逻辑提高响应准确性

* feat(auto-tools): 添加路径记录功能和时间差计算

- 新增 RecordPath 对象用于记录路径信息
- 实现 saveRecordPaths 函数保存路径记录到文件
- 添加 getTimeDifference 函数计算时间差值
- 在路径执行完成后记录时间戳和路径信息
- 从 PathRecord.json 文件读取和解析路径记录
- 将 Set 数据结构转换为数组进行 JSON 序列化
- 重构 RecordLast 对象的 name 字段为 uid 字段

refactor(FullyAutoAndSemiAutoTools): 优化战斗队伍切换逻辑

- 修复了代码缩进和空格格式问题
- 重构了队伍切换条件判断逻辑
- 添加了战斗模式下的队伍切换功能
- 统一了代码风格和命名规范

feat(team): 添加战斗策略配置和队伍切换逻辑

- 在team对象中添加fightName属性用于存储战斗策略名称
- 实现战斗模式下的队伍切换功能,当fight为true时使用fightName进行队伍切换
- 添加战斗策略配置验证,未配置战斗策略时抛出错误
- 保留原有的七元素队伍切换逻辑,在非战斗模式下继续使用
- 添加详细的日志记录以跟踪队伍切换过程

feat(auto-tool): 添加战斗需求检测和自动战斗功能

- 实现战斗需求检查逻辑,检测路径是否需要配置战斗策略
- 添加战斗状态标记和管理机制
- 集成自动战斗任务执行功能
- 优化路径执行流程,在需要时自动启动战斗模式
- 添加战斗状态重置机制确保正确清理
- 完善错误处理和日志记录功能

* feat(auto-tools): 新增cron表达式解析功能并优化路径过滤逻辑

- 添加cron.js工具模块,实现cron表达式解析和下次执行时间计算功能
- 新增timeType枚举类型,支持小时和cron表达式两种时间配置方式
- 重构路径去重逻辑,使用Map按路径字符串和时间戳进行智能去重
- 移除废弃的getTimeDifference函数实现
- 在初始化配置中注册cron工具模块
- 实现基于时间配置的路径过滤机制,支持小时间隔和cron表达式触发
- 从pathing.json加载时间配置,按等级排序后应用到路径过滤逻辑
- 添加cron表达式解析失败的错误处理和日志记录
- 更新pathing.json默认配置,包含地方特产和矿物的时间规则示例

* refactor(cron): 重构Cron时间戳获取功能

- 将getNextCronTimestamp函数改为异步函数,使用HTTP请求替代本地计算
- 移除原有的本地Cron表达式解析和时间计算逻辑
- 添加对远程服务的HTTP请求实现时间戳计算
- 注释掉原有性能较差的本地计算方法
- 移除不再使用的辅助函数parseCron、parseField、parseCronField
- 保留isValidCron函数用于验证Cron表达式的功能

* feat(config): 添加七元素配置文件支持并优化密钥验证提示

- 新增 SevenElement.json 配置文件用于管理七元素数据
- 添加 json_path_name 常量对象统一管理配置文件路径
- 优化密钥不匹配错误提示,增加升级说明和文档查看建议
- 实现七元素配置的动态加载和合并功能
- 更新 CD 路径配置使用新的路径常量
- 改进七元素数据解析和排序逻辑

refactor(FullyAutoAndSemiAutoTools): 重构代码结构并优化配置管理

- 修改 getNextCronTimestamp 函数参数,移除默认 URL 参数
- 将全局变量重构为常量对象 auto、dev 和 cd,提高代码可维护性
- 更新团队配置属性名,从 teamFight 和 teamSevenElements 改为 team_fight 和 team_seven_elements
- 实现完整的初始化流程,包括工具模块动态加载和配置验证
- 添加 UID 设置持久化功能,支持不同账户的个性化配置
- 重构路径执行逻辑,优化黑白名单过滤和 CD 时间控制
- 移除过时的全局变量和初始化代码,精简代码结构
- 更新设置界面配置,新增 CD 算法开关和改进的半自动模式选项

* feat(config): 添加路径加载层级配置选项

- 新增 loadingLevel 变量用于控制路径加载层级
- 在设置中添加 loading_level 配置项,默认值为 2
- 实现配置解析逻辑,支持自定义加载层级数值
- 添加配置错误时的异常处理和警告日志
- 更新记录文件路径引用,使用统一的 json_path_name 配置
- 注释掉原有的硬编码记录文件路径变量

* feat(core): 添加实时任务处理功能

- 实现了 realTimeMissions 函数用于处理实时任务
- 支持自动对话、自动战斗、自动拾取三种实时任务类型
- 添加了 real_time_missions 配置选项到设置界面
- 集成实时任务调度器替代原有的固定任务执行方式
- 支持通用任务和非通用任务的差异化处理
- 更新了任务执行流程以使用新的实时任务机制

* feat(FullyAutoAndSemiAutoTools): 添加全自动半自动工具箱及文档

- 更新 manifest.json 中的 bgi_version 从 0.44.8 到 0.54.3
- 新增完整的 README.md 文档,包含项目概述、核心功能、文件结构、流程图等详细说明
- 提供了详细的配置项说明和使用流程指南
- 包含了 CD 规则示例和实用建议等完整文档内容

* docs(readme): 更新项目文档添加外部依赖部署说明

- 移除原神相关描述简化为通用工具箱介绍
- 添加 bettergi-scripts-tools 部署指南支持 Cron 解析
- 提供 Windows、Java、Docker 三种部署方式
- 补充 Cron API 接口使用示例
- 修正冷却系统说明明确需要外部 HTTP API 支持

* fix(config): 修复加载路径层级配置验证逻辑

- 添加 finally 块确保 loadingLevel 最小值为 2
- 防止配置错误导致的加载层级异常问题

* refactor(FullyAutoAndSemiAutoTools): 重构文件路径同步和层级处理逻辑

- 添加 settingsRefreshList 用于管理设置项刷新
- 修改 addUniquePath 函数支持传入列表参数
- 修复 parentLevel 计算逻辑,确保层级正确对应
- 更新排序逻辑,使用 path 字段替代 parent_name 进行比较
- 重构 readPaths 函数,实现完整的树形结构构建
- 添加 getFileOrFolderName 和 getParentFolderName 辅助函数
- 优化 settingsList 的合并和去重处理
- 统一使用 children 字段替代 child 字段

* feat(config): 添加配置文件支持并优化设置初始化

- 添加 settings.json 配置文件定义各项功能参数
- 修改 initSettings 函数支持路径前缀参数
- 实现配置文件路径动态拼接功能
- 集成密钥、模式选择、队伍配置等完整配置项
- 支持实时任务、CD算法、调试模式等高级功能配置

* fix(runtime): 修复实时任务配置执行问题

- 添加了对real_time_missions的数组类型检查和转换
- 增加了配置执行日志输出便于调试
- 修复了getMultiCheckboxMap中键值过滤逻辑
- 改进了配置路径处理和前缀拼接功能
- 移除了空的PathRecord.json文件初始化
- 优化了运行映射的过滤和处理流程

* fix(FullyAutoAndSemiAutoTools): 修复设置过滤逻辑和代码格式问题

- 修正了levelName过滤条件,从排除改为包含匹配项
- 修复了recordPaths变量声明和赋值的格式问题
- 修正了needRunMap的日志输出格式,使用数组展开语法
- 添加了configSettings检查,避免重复初始化设置
- 修复了getMultiCheckboxMap函数中的语法格式问题
- 修正了multiCheckboxMap日志输出的格式问题

* feat(FullyAutoAndSemiAutoTools): 添加黑名单白名单过滤功能

- 实现黑名单检查逻辑,过滤包含黑名单关键词的文件名
- 实现白名单检查逻辑,允许白名单中的项目通过过滤
- 添加JSON文件过滤功能,排除.json结尾的文件
- 修改子名称处理逻辑,支持数组形式的子名称存储
- 更新路径添加逻辑,集成过滤后的子名称数组

* feat(config): 添加刷新设置配置并优化黑白名单过滤功能

- 添加 RefreshSettings.json 配置文件路径定义
- 预处理黑白名单数组,移除空字符串并进行 trim 处理
- 实现黑名单白名单过滤逻辑,支持动态排除特定项目
- 优化路径遍历逻辑,支持更灵活的层级关系构建
- 添加安全的可选链操作符处理可能为空的路径数据
- 更新配置项默认值设置,确保 config_run 执行状态正确
- 优化多复选框映射创建,仅处理非空选项数据
- 改进树形结构转列表算法,提升性能和数据完整性
- 添加多个调试日志输出,便于开发过程跟踪和问题排查

* feat(config): 实现基于用户ID的路径配置管理

- 添加了levelName常量定义用于树层级标识
- 新增pathJsonByUid配置路径支持按用户ID存储路径配置
- 实现loadPathJsonListByUid函数根据用户ID加载路径JSON列表
- 创建initRefresh函数重构配置刷新逻辑并支持用户隔离
- 添加initUidSettingsMap函数初始化用户ID设置映射表
- 实现loadUidSettingsMap函数加载用户特定配置
- 创建initRun函数处理执行前配置并完善路径过滤机制
- 重构needRunMap初始化逻辑支持CD时间和路径匹配
- 优化路径筛选算法增加多层次路径匹配和过滤功能
- 实现PATH_JSON_LIST按用户ID保存和加载机制

* fix(FullyAutoAndSemiAutoTools): 修复记录查找和路径设置逻辑

- 添加空值合并操作符确保 RecordList 和 RecordPath 的默认值
- 使用逻辑或赋值运算符简化 uid 和 paths 属性的设置
- 移除冗余的条件检查和赋值操作
- 优化代码可读性和执行效率

* feat(auto-tools): 优化自动化工具的路径执行逻辑

- 添加了按父名称和名称分组路径的功能
- 实现了路径分组的排序机制
- 重构了路径执行逻辑,支持更精确的分组执行
- 修复了变量命名和代码格式问题
- 优化了日志输出,提高执行过程的可追踪性
- 更新了配置初始化和加载流程

* refactor(FullyAutoAndSemiAutoTools): 移除未使用的变量声明

- 注释掉未使用的 label 变量获取逻辑
- 移除未使用的 as_name 变量声明
- 保留 groupByParentAndName 函数定义结构

* feat(settings): 添加执行顺序规则配置功能

- 支持多条规则配置,语法为"parentName->name1=1,parentName->name2=2"
- 实现规则解析和排序映射功能
- 添加order_rules配置项到settings.json
- 优化日志信息中的参数格式
- 修复代码中的变量命名间距问题

* refactor(FullyAutoAndSemiAutoTools): 更新runPath函数参数和战斗需求检查逻辑

- 为runPath函数添加current_name和parent_name参数,默认值为空字符串
- 注释掉原有的try-catch块以跳过战斗需求检查
- 在执行路径时传递当前名称和父名称参数
- 移除对团队战斗配置的验证逻辑

* Revert "refactor(FullyAutoAndSemiAutoTools): 更新runPath函数参数和战斗需求检查逻辑"

This reverts commit 7995c67419.

* refactor(FullyAutoAndSemiAutoTools): 移除战斗策略配置检查逻辑

- 注释掉JSON解析和描述检查相关代码
- 保留路径关键词匹配的战斗需求检查逻辑
- 简化战斗需求判断流程

* feat(FullyAutoAndSemiAutoTools): 添加路径执行列表的日志记录功能

- 在runList函数中增加了current_name和parent_name参数用于日志记录
- 修改了日志输出格式以显示正确的父名称和当前名称
- 更新了runList调用以传递必要的参数
- 清理了排序相关的todo注释

* refactor(FullyAutoAndSemiAutoTools): 重构路径扫描系统并优化多账号支持

- 修复 manifest.json 中的密钥格式,去除多余空格
- 重构路径扫描功能,支持任意深度目录结构的树状扫描
- 新增按 UID 缓存路径列表功能,提升多账号切换性能
- 优化黑白名单过滤逻辑,使用 Set 数据结构提升查找效率
- 更新 README 文档,详细说明新的路径扫描和缓存机制
- 修改配置文件结构,新增 RefreshSettings.json 和 path-json-by-uid.json
- 调整 CD 规则配置,更新示例中的冷却时间设置
- 完善 Cron 解析服务部署文档,提供多种部署方式说明
- 优化用户操作流程,简化首次配置和日常使用的步骤

* refactor(FullyAutoAndSemiAutoTools): 重构队伍切换逻辑

- 提取队伍切换功能到独立的 switchTeamByIndex 函数中
- 添加详细的 JSDoc 注释说明函数用途和参数
- 简化主流程中的条件判断逻辑
- 将原有的队伍切换代码替换为函数调用
- 保留原有功能的同时提高代码可读性和维护性
- 为特定路径"有草神"添加专门的处理逻辑

* docs(settings): 更新队伍配置字段标签说明

- 在队伍配置字段标签中添加草神位置填写建议
- 明确标注建议草神填写至草位的说明信息

* feat(FullyAutoAndSemiAutoTools): 添加调试日志功能并优化队伍切换逻辑

- 添加了needRunMap的调试日志输出功能
- 修改switchTeamByIndex函数增加key参数支持
- 在路线切换时传入对应的元素名称作为标识
- 为草神路线切换添加了明确的调用参数

* style(FullyAutoAndSemiAutoTools): 优化代码格式和团队切换逻辑

- 修复字符串常量中的空格问题
- 重新格式化switchTeamByIndex函数的注释和代码缩进
- 简化团队切换逻辑,移除冗余的条件判断和重复代码
- 优化runList函数参数中的空格格式
- 重构团队切换的执行顺序和条件判断结构

* fix(path): 修复路径执行异常时的记录清理问题

- 在路径执行开始时记录时间戳以确保一致性
- 路径执行失败时删除对应的路径记录
- 执行失败时同步删除错误路径并保存记录
- 任务组执行失败时删除对应的分组路径记录

* fix(core): 修复路径记录功能中的对象引用错误

- 将 RecordPath.paths 替换为 Record.paths 以修正对象引用
- 移除已注释掉的废弃代码块,清理文件结构
- 重构路径执行逻辑,确保记录和错误处理正确同步
- 更新组路径记录方式,使用统一的对象结构管理
- 修改配置文件格式从数组改为对象以适配新逻辑

* feat(auto-tools): 添加路径记录功能并优化执行流程

- 新增 RecordPathList 数组用于存储路径记录
- 修改数据结构使用 RecordPathList 替代原有的 Record.paths
- 优化路径匹配逻辑,调整条件判断返回值
- 更新文件保存机制,异步保存路径记录和普通记录
- 重构路径执行流程,改进异常处理和状态管理
- 添加日志输出增强调试信息
- 修复路径数据解析和查找逻辑
- 移除部分 continue 语句改用异常抛出机制

* fix(FullyAutoAndSemiAutoTools): 解决记录路径排序问题

- 添加时间戳降序排序确保最新记录优先显示

* feat(settings): 添加刷新记录配置选项

- 在设置中新增 refresh_record 配置项用于控制记录清空
- 实现刷新时清空 RecordPathText 和 RecordText 文件功能
- 添加日志记录显示已清空记录文件的状态信息

* docs(README): 更新配置项文档

- 添加 refresh_record 配置项说明
- 补充运行记录相关功能描述

* fix(FullyAutoAndSemiAutoTools): 修复selected属性赋值问题

- 修正了selected属性的赋值语法,确保正确传递变量值
- 避免了潜在的属性访问错误
- 保持了对象结构的一致性

* fix(auto-tools): 修复CD过滤逻辑错误

- 重命名cdFiltered为in_cd_paths以提高可读性
- 修正CD路径过滤条件,将返回值取反确保正确过滤
- 修复cron时间类型判断逻辑,添加默认返回false避免意外通过
- 更新变量引用以使用新的变量名in_cd_paths
- 修正时间差计算逻辑确保CD状态判断准确

* feat(FullyAutoAndSemiAutoTools): 添加路径排序配置功能

- 新增 PathOrder.json 配置文件用于路径排序
- 实现基于JSON配置的路径排序功能
- 添加 try-catch 错误处理机制
- 支持从 PathOrder.json 文件读取排序规则
- 移除未使用的 readPaths 函数注释代码
- 优化排序逻辑支持多条规则配置

* feat(auto-tool): 添加锄地队伍配置功能

- 新增 HoeGroundMap 用于存储锄地队伍映射关系
- 添加 team_hoe_ground 配置项用于设置锄地队伍规则
- 实现 switchTeamByName 函数按名称切换队伍
- 修改 runPath 函数支持传递父级和当前名称参数
- 在路径执行时根据锄地配置自动切换对应队伍
- 更新设置界面添加锄地队伍配置输入框
- 移除废弃的方案2注释代码

* feat(auto-tools): 添加锄地队伍配置功能

- 新增 HoeGround.json 配置文件用于存储锄地队伍映射
- 实现基于 JSON 的锄地队伍配置支持
- 添加 UID 过滤的锄地队伍映射功能
- 优化 PathOrder.json 默认配置结构
- 移除调试代码并完善配置加载日志

* docs(FullyAutoAndSemiAutoTools): 更新 README 文档内容

- 在标题后添加空行以改善格式
- 为执行时序图部分添加新的章节标题
- 调整 mermaid 图表中的样式定义格式,统一缩进和间距
- 在 sequence diagram 中添加适当的空格以提高可读性
- 将 settings.json 配置表格进行重构,添加新的配置项如 key 和 team_hoe_ground
- 为新增配置项添加对应的 JSON 结构示例说明
- 优化 CD 规则示例的格式化显示
- 在版本密钥表格中简化表头格式
- 添加必要的换行以改善文档整体布局

* docs(README): 更新额外json配置标题说明

- 将"额外json配置"修改为"(可选)额外json配置"
- 强调该配置项的可选性质以提高文档清晰度

* feat(config): 更新自动化工具配置选项

- 添加配置模式-刷新-清空运行记录复选框
- 新增执行顺序规则输入字段
- 添加锄地队伍配置选项
- 更新七元素队伍配置说明
- 重新整理配置项顺序和分隔符位置

* fix(game): 修复自动切换队伍功能中的点击延迟问题

- 在部署按钮点击后添加 100ms 延迟,确保操作正确执行
- 解决了因点击过快导致的游戏响应异常问题

* feat(config): 添加配置UID显示功能

- 在设置界面添加config_uid复选框用于显示当前配置UID
- 实现settings.json和config/settings.json中的UID显示逻辑
- 更新main.js中保存用户配置时同步更新当前配置UID显示
- 修复JSON解析中的数组结构格式化问题
- 添加分隔符优化设置界面布局结构

* feat(core): 添加按UID清空运行记录功能

- 实现了按UID过滤清空记录的功能
- 在设置中添加了清空运行记录模式选择选项
- 新增"全部"和"UID"两种清空模式
- 当选择UID模式时只清空当前UID对应的记录
- 添加了相应的日志输出显示清空的UID信息
- 更新了配置文件中的设置项定义

* docs(FullyAutoAndSemiAutoTools): 更新README文档配置项说明

- 添加config_uid配置项说明,用于显示配置uid
- 添加refresh_record_mode配置项说明,用于设置清空运行记录模式
- 优化JSON示例格式,将注释移至下一行以提高可读性
- 更新订单规则和队伍配置的JSON结构展示

* refactor(FullyAutoAndSemiAutoTools): 优化配置管理和代码结构

- 修改配置UID显示格式,在标签中添加换行符以改善可读性
- 修复变量赋值语句的格式化问题,确保代码风格一致性
- 优化日志输出中的参数格式,提升调试信息准确性
- 重构配置设置过滤逻辑,实现基于层级的设置加载机制
- 更新getBracketContent函数的实现方式,从正则表达式改为索引查找方法
- 改进注释文档的表述准确性,明确功能描述和返回值说明

* fix(FullyAutoAndSemiAutoTools): 优化路径筛选逻辑并修复配置加载问题

- 修改循环逻辑,仅遍历有子节点的路径项
- 添加父级节点的层级名称设置功能
- 增加路径配置列表加载检查,防止未初始化时出错
- 重构设置过滤逻辑,提高层级匹配准确性
- 优化路径匹配算法,确保只有含子节点的路径参与筛选
- 移除冗余代码,简化树形结构转列表逻辑

* feat(pathing): 优化路径层级管理和显示功能

- 在 PATHING_ALL 中添加 id 字段用于唯一标识
- 将 parentName 从空字符串改为 undefined,并添加 rootName 字段
- 修改过滤逻辑以支持更完整的路径层级遍历
- 添加 rootName 字段记录最父级下一级名称
- 在设置列表中增加分隔符提升界面可读性
- 优化路径显示格式,添加父子级关系标识
- 重构 addUniquePath 函数支持基于多个条件的去重判断
- 添加对 children 和 child_names 字段的双重支持
- 增加调试日志输出便于问题排查

* fix(FullyAutoAndSemiAutoTools): 修复路径过滤逻辑错误

- 修正了父级路径过滤条件,将相等判断改为不等判断
- 修复了子级选项数组的拼接方式,改用展开运算符合并
- 纠正了配置层级加载的逻辑,反转了条件判断以确保正确的层级加载
- 修正了基于levelName的过滤逻辑,确保设置项能正确匹配对应层级

* feat(auto-tools): 增强路径管理和排序功能

- 添加rootName支持,实现更精确的路径层级管理
- 新增generatedKey函数统一处理路径键值生成逻辑
- 修复路径过滤条件,正确处理parentId匹配
- 更新JSON配置结构,增加root_name字段支持
- 完善排序规则,支持多层级(rootName->parentName->name)排序
- 优化去重逻辑,完整比较所有路径属性
- 更新文档说明,添加详细的语法配置指南

* refactor(FullyAutoAndSemiAutoTools): 优化路径过滤逻辑并清理代码

- 注释掉未使用的 countMatchingElements 函数
- 使用 dir_key 变量存储拼接的路径字符串
- 修改 filterSettings 过滤条件以支持指定目录加载
- 更新注释文字从"对应级别"为"指定级别"
- 重构路径过滤逻辑提高代码可读性

* fix(core): 修复自动工具中的逻辑判断错误

- 修复路径过滤条件,将不等于改为等于判断
- 添加高级别过滤功能支持
- 修复设置加载的路径匹配逻辑
- 修复级别过滤的条件判断
- 修复冷却时间检查的异步处理
- 修复团队锄地列表的筛选条件
- 修复排序列表的筛选条件
- 使用统一的键生成函数优化代码结构

* fix(config): 修复配置文件中的标签显示问题

- 在settings.json中为配置uid标签添加换行符以改善显示
- 在config/settings.json中同步更新配置uid标签的换行格式
- 修改main.js中添加theLayer变量来控制层级加载逻辑
- 将硬编码的false条件替换为动态的theLayer变量判断

* feat(auto-tools): 添加指定层级加载功能

- 在配置中新增 the_layer 复选框选项用于控制是否只加载指定层级
- 优化路径查找逻辑,通过预建 levelName 映射提升性能
- 修改过滤条件以支持层级加载功能
- 在两个配置文件中同步添加 the_layer 设置项

* feat(auto-tools): 添加指定层级加载功能

- 在配置中新增 the_layer 复选框选项用于控制是否只加载指定层级
- 优化路径查找逻辑,通过预建 levelName 映射提升性能
- 修改过滤条件以支持层级加载功能
- 在两个配置文件中同步添加 the_layer 设置项

* docs(readme): 更新文档中的语法规则说明

- 移除了关于 name=1 和 name=队伍名 语法支持的描述
- 调整了匹配精度规则,移除了 name 级别的匹配
- 统一了锄地特化队伍配置部分的语法规则说明

* refactor(FullyAutoAndSemiAutoTools): 优化键值生成逻辑

- 修改 generatedKey 函数支持 useParent 参数控制键值生成方式
- 优先处理 rootName->parentName->name 格式的键值生成
- 添加 useParent 模式下的父级键值生成逻辑
- 在 groupByParentAndName 函数中同时生成普通键值和父级键值
- 为每个项目创建两套分组键值以支持不同层级的分组需求

* fix(team): 修复团队锄地数据映射逻辑

- 移除无效的 undefined 初始化检查
- 直接设置团队名称到锄地映射表中
- 简化了键值对的存储逻辑
- 避免了空对象的创建和访问操作

* feat(order): 添加父级键值映射功能

- 为订单项生成父级键值
- 将父级键值与订单建立映射关系
- 保留原有键值映射逻辑不变

* fix(auto-tools): 修复锄地工具中的路径分组逻辑问题

- 移除无用的 groupByParentAndName 函数定义位置
- 修改锄地队映射逻辑,区分 root_name 存在和不存在的情况
- 更新路径排序逻辑,添加映射表处理父子路径关系
- 修复 generatedKey 调用参数传递问题
- 在 README 中添加语法风格一致性说明

* feat(config): 添加模板匹配设置功能并优化配置过滤逻辑

- 新增 templateMatchSettingsJson 配置文件路径定义
- 修复路径筛选中的 find 方法使用方式
- 实现模板匹配设置的读取和配置项过滤
- 优化 UID 设置过滤逻辑并添加分隔符结构
- 调整配置层级判断条件从大于改为大于等于
- 将最终配置写入改为使用模板匹配设置而非 UID 设置

* refactor(config): 更新配置文件处理逻辑

- 添加了 templateMatchSettingsJson 配置文件路径定义
- 使用 templateMatchSettings 替代 uidSettings 进行配置筛选
- 优化了层级设置过滤逻辑,改进了分隔符插入机制
- 重构了配置项的合并与写入流程
- 修复了层级名称匹配和路径过滤的相关问题
- 调整了设置项的排序和展示逻辑

* fix(FullyAutoAndSemiAutoTools): 修复加载层级比较逻辑错误

- 修正了模板匹配设置中的加载层级判断条件
- 将比较逻辑从 loadingLevel > level 更新为 loadingLevel > level+1
- 确保级别检查逻辑与预期行为一致

* feat(config): 添加高阶过滤配置功能

- 在设置中新增 high_level_filtering 配置项用于高级过滤
- 实现高阶过滤逻辑,支持路径层级过滤功能
- 添加 levelName 属性初始化为 undefined
- 修复加载层级判断条件中的运算符间距问题
- 添加 levelNameMap 调试日志输出
- 移除重复的变量声明代码
- 添加待优化注释标记后续改进点

* feat(FullyAutoAndSemiAutoTools): 优化路径遍历和数据结构处理逻辑

- 添加了路径JSON列表的调试日志输出功能
- 重构了路径对象创建逻辑,统一使用对象变量赋值方式
- 修改了遍历列表过滤条件,只处理文件类型的元素
- 更新了UI标签文本的显示位置和格式
- 优化了层级名称映射查找逻辑,提高性能
- 添加了详细的调试日志用于跟踪数据处理过程
- 统一了levelName字段初始化为空字符串
- 重构了树转列表算法,支持递归处理所有节点包括非文件节点

* feat(config): 添加高阶过滤功能并限制连续分隔符数量

- 实现了 limitConsecutiveSeparators 函数来限制连续分隔符不超过3个
- 在模板匹配设置中应用分隔符数量限制逻辑
- 更新 README.md 添加 high_level_filtering 配置项说明
- 添加高阶过滤语法说明文档和使用示例
- 修改 settings.json 中高阶过滤标签提示查看文档
- 优化配置界面中的高阶过滤功能描述

* docs(FullyAutoAndSemiAutoTools): 更新 README 文档中的流程图和时序图

- 更新了 Mermaid 流程图,更清晰地展示刷新、加载和执行三种模式
- 重新设计了时序图,突出不同模式下的交互流程差异
- 添加了详细的模式切换说明和过滤逻辑描述
- 优化了图表样式和分类,提高可读性
- 补充了各模块间的调用关系和数据流向说明

* feat(config): 更新路径配置文件增加更多资源类型

- 添加星银矿石、白铁块等矿物资源的定时配置
- 新增「冷鲜肉」、发光髓、鳅宝玉等多种道具的小时制配置
- 添加青蛙、鳗肉、螃蟹等食物资源的定时设置
- 增加沉玉仙茗、冰雾花花朵等植物资源的时间配置
- 新增电气水晶、食材与炼金等综合类资源的定时任务
- 添加敌人与魔物、锄地专区等功能区域的时间规划

* fix(sorting): 修复分组排序逻辑

- 将默认排序值从 9999 改为 0
- 修改比较函数实现倒序数字比较
- 确保不在 JSON 中的项目正确排列到末尾

* fix(tools): 解决排序功能中的空值异常问题

- 为 localeCompare 方法调用添加可选链操作符
- 防止当 a_key 为 null 或 undefined 时引发错误
- 确保排序逻辑在所有情况下都能正常执行

* fix(FullyAutoAndSemiAutoTools): 修复路径匹配逻辑

- 添加额外条件检查 labelParentName 是否等于 pathingName
- 确保路径匹配时考虑完整的路径名称匹配
- 修复过滤器中路径匹配的逻辑错误

* fix(FullyAutoAndSemiAutoTools): 修复路径拼接逻辑问题

- 添加了对 parentName 与 rootName 相同情况的判断条件
- 避免了当 parentName 和 rootName 相同时的重复路径拼接
- 确保了路径层级关系的正确性

* Revert "fix(FullyAutoAndSemiAutoTools): 修复路径拼接逻辑问题"

This reverts commit cfa86be643.

* feat(auto-fight): 弃用自动战斗功能

- 注释掉实时任务中的自动战斗调用
- 在设置选项中标记自动战斗为已弃用状态
- 更新配置文件中的自动战斗选项显示
- 移除自动战斗功能的激活逻辑
- 保留相关代码以供后续完全移除参考

* refactor(FullyAutoAndSemiAutoTools): 重构锄地队配置和排序规则处理逻辑

- 移除原有的team_hoe_ground配置解析代码块
- 将team_hoe_ground配置解析移动到新的位置并保持功能不变
- 调整order_rules配置解析的位置以优化代码结构
- 添加注释说明输入值优先覆盖的处理逻辑
- 重新排列代码顺序以改善可读性和维护性

* ```
refactor(FullyAutoAndSemiAutoTools): 优化模板匹配和路径过滤逻辑

- 添加连续分隔符数量限制功能
- 修复路径匹配中的逻辑运算符间距问题
- 改进订单列表解析和过滤逻辑
- 添加公共排序项支持并实现自定义排序覆盖
- 优化订单列表排序算法确保公共项优先显示
```

* feat(FullyAutoAndSemiAutoTools): 优化锄地队伍配置逻辑

- 修改 teamHoeGroundList 数据结构,添加 is_common 字段用于区分公共锄地队
- 实现自定义锄地队覆盖公共锄地队的逻辑
- 添加数据过滤和排序功能,确保自定义锄地队优先于公共锄地队
- 更新锄地队映射关系的处理方式

* feat(tools): 添加执行顺序配置功能

- 在needRunMap中添加order字段用于控制执行顺序
- 使用orderMap获取对应组的执行顺序值
- 默认执行顺序为0当orderMap中不存在对应值时

* feat(config): 添加路径排序配置文件

- 新增地方特产分类的路径排序规则
- 新增矿物分类的路径排序规则
- 配置了从挪德卡莱到白铁块的各项资源排序
- 定义了各类别下子项目的显示顺序
- 设置了统一的UID和公共属性标识
- 建立了完整的路径层级结构配置

* docs(config): 更新配置规则文档说明

- 添加order值越大优先级越高的说明
- 新增is_common字段用于标识公共配置
- 补充CD规则中level值优先级说明

* chore(debug): 更新调试日志文件命名规范

- 为初始化刷新流程的调试日志添加 [init-refresh] 前缀
- 为初始化运行流程的调试日志添加 [init-run] 前缀
- 统一调试日志命名格式以便更好地区分不同执行阶段
- 在订单映射处理后添加新的调试日志记录

* fix(FullyAutoAndSemiAutoTools): 修复路径名称处理逻辑并优化调试日志

- 处理 root_name 和 parent_name 字段的兼容性情况
- 添加可选链操作符避免访问不存在的 parentName 属性
- 增加对 parent_name 字段的支持以处理不同命名格式
- 优化调试日志文件名以便更好地区分不同的日志内容
- 添加额外的调试信息记录功能用于问题排查

* refactor(FullyAutoAndSemiAutoTools): 优化 needRunMap 排序逻辑

- 添加对 needRunMap 的整体排序功能
- 使用 order 字段对映射项进行降序排序
- 替换原有的无序 needRunMap 实现
- 确保排序后按照指定顺序执行相关操作

* debug(FullyAutoAndSemiAutoTools): 添加运行时调试日志

- 在执行 chooseBestRun() 后记录 lastRunMap 的调试信息
- 在执行 chooseBestRun() 后记录 needRunMap 的调试信息
- 使用 JSON.stringify 将 Map 对象转换为字符串进行存储
- 通过 debugKey 函数将调试信息写入对应的日志文件

* feat(FullyAutoAndSemiAutoTools): 集成设置配置并添加调试日志

- 使用 settings.open_cd 和 settings.http_api 替代硬编码默认值
- 在初始化过程中添加 timeConfigs、recordPaths 和 in_cd_paths 的调试日志记录
- 添加 CD 设置的调试日志输出以方便排查问题

* feat(cron): 添加批量获取cron时间戳功能并优化CD过滤逻辑

- 新增 getNextCronTimestampAll 函数支持批量处理cron表达式
- 实现统一的HTTP请求接口减少网络请求次数
- 重构generatedKey函数位置提升代码复用性
- 将原有的单个cron时间戳获取改为批量处理模式
- 优化CD过滤流程通过Map映射提高查询效率
- 添加详细的参数验证和错误处理机制

* fix(cron): 修复cron请求参数格式问题

- 修改cron.js中HTTP POST请求的参数格式,将bodyJson包装在CronList对象中
- 添加调试日志功能,记录cdFilterMatchedPaths和nextMap信息
- 增强debugKey函数功能,支持文本模式的日志输出
- 在main.js中添加多处调试信息输出,便于问题排查

* fix(cron): 修复定时任务配置参数和逻辑处理问题

- 修复了POST请求中参数名从CronList改为cronList以匹配后端接口
- 添加了空数组检查避免对空列表执行定时任务查询
- 优化了定时任务配置的查找和更新逻辑
- 修复了代码中的格式化问题包括空格和条件判断语句
- 修正了调试函数参数的格式化错误

* fix(scheduler): 修复定时任务配置中的键值匹配问题

- 修改了时间配置中使用 item.path 替代 generatedKey(item) 作为键值
- 移除了不必要的 generatedKey 函数调用注释
- 在保存记录时实现基于 uid 的重复检查和数据合并
- 当存在相同 uid 记录时,将路径、错误路径和分组路径进行合并
- 优化了记录列表的数据更新逻辑,避免重复添加相同记录

* fix(FullyAutoAndSemiAutoTools): 解决记录路径合并逻辑问题

- 修复 RecordPathList 中重复记录的路径合并逻辑
- 注释掉 errorPaths 和 groupPaths 的合并操作
- 简化 RecordList 的处理逻辑,直接添加新记录而不进行查找合并
- 移除冗余的记录查找和条件判断代码

* fix(FullyAutoAndSemiAutoTools): 修复 RecordPathList 类型转换问题

- 将 RecordPathList 初始化从空数组改为使用 Array.from 进行类型转换
- 确保非数组类型的 RecordPathList 能够正确转换为数组格式
- 保持原有的数组查找逻辑正常工作

* feat(auto-tools): 添加任务执行顺序排序和择优模式支持

- 实现对 lastRunMap 按 order 字段进行降序排序
- 添加择优模式配置选项 choose_best
- 在任务执行前后添加择优模式状态日志
- 增加任务组执行顺序打印功能
- 添加分隔线美化任务执行日志输出

* docs(FullyAutoAndSemiAutoTools): 更新文档和版本密钥

- 更新 manifest.json 中的密钥从 PGCSBY37NJ 到 PGCSBY37NJA
- 添加运行实例部分日志截图展示
- 更新 bettergi-scripts-tools 版本要求为 v0.0.3
- 修改默认 API 地址为 /all 端点
- 更新版本密钥表格中的密钥值
- 将版本发布日期从 2026.01.18 更改为 2026.01.30

* feat(config): 更新默认CD算法API端点路径

- 将HTTP API默认路径从 /bgi/cron/next-timestamp 修改为 /bgi/cron/next-timestamp/all
- 适配新的API接口以支持更完整的功能
- 保持向后兼容性同时提供增强的API功能支持

* fix(auto-tools): 修复自动化工具中的时间判断逻辑

- 修正时间配置判断条件,将 >= 改为 < 以正确判断是否在CD时间内
- 反转cron检查结果,确保不在CD中的项目返回true
- 优化CD路径过滤逻辑,使用filter方法替代Set.difference
- 移除多余的空行以改善代码整洁性
- 修复日志输出中的空格问题
- 统一变量声明中的空格格式

* feat(task): 更新任务组执行日志信息

- 在任务组执行顺序日志中添加执行路径数统计
- 修复了日志格式参数缺失的问题
- 添加了对路径数量的安全访问处理

* docs(readme): 更新使用说明文档

- 添加快捷使用指南包含账号刷新设置步骤
- 增加全自动执行流程说明和截图演示
- 补充CD算法部署要求和择优模式说明
- 完善队伍配置语法示例和映射规则
- 添加锄地队伍配置实例和路径执行说明
- 优化文档结构提升用户体验

* docs(README): 更新项目概述描述

- 移除原神(BetterGI平台)相关具体内容
- 简化项目功能描述
- 保持核心功能介绍不变

* docs(README): 更新项目描述

- 移除 BetterGI 平台相关宣传语
- 简化项目概述部分内容

* docs(FullyAutoAndSemiAutoTools): 更新README文档添加版本密钥获取说明

- 在快捷使用步骤1中添加先获取版本密钥的说明
- 添加高级配置查看提示作为第3个使用步骤
- 保持核心亮点部分的路径扫描重构说明

* docs(FullyAutoAndSemiAutoTools): 更新README中的冷却时间控制说明

- 在冷却时间控制描述中添加了关于路径CD的具体说明

* feat(auto-tools): 添加执行组最大路径数限制功能

- 新增 LimitMax.json 配置文件用于存储路径限制数据
- 实现执行组最大执行数限制功能,支持按组别配置最大路径数量
- 添加 open_limit_max 配置项用于开启/关闭路径限制功能
- 添加 limit_max_group 配置项用于设置各执行组的最大路径数规则
- 在任务组执行时应用路径数限制,确保不超过设定的最大值
- 更新 README.md 文档,添加新的配置项说明和使用示例
- 优化任务组执行顺序的日志输出格式

* feat(config): 更新矿物采集限制配置

- 将萃凝晶的最大限制从50增加到60
- 添加多个新的矿物采集配置项
- 新增紫晶块相关配置包括大剑版和钟离版
- 添加萃凝晶特殊路线配置支持火神赶路等模式
- 增加枫丹水下及钟离版配置选项
- 添加水晶块多种配置包括忆雪晴版和火山版
- 新增白铁块富集路线配置
- 在开启限制组功能时显示相应日志信息

* refactor(FullyAutoAndSemiAutoTools): 重构generatedKey函数并优化任务执行逻辑

- 重构generatedKey函数,优化三层和二层结构的处理逻辑
- 修改generatedKey函数中的空值检查,使用可选链操作符
- 移除无用的注释代码
- 修复teamHoeGroundList中root_name的空值检查逻辑
- 修正openLimitMax变量声明中的空格问题
- 将PathOrder文件路径改为LimitMax
- 添加对limitMaxByGroup中max值的parseInt转换
- 增加调试日志输出limitMaxList的处理情况
- 优化runList函数参数,增加group_key和group_value参数
- 更新任务执行时的日志输出格式
- 修正任务组执行顺序的日志显示格式
- 优化任务组执行的索引计算和日志输出

* refactor(FullyAutoAndSemiAutoTools): 优化组执行数限制逻辑

- 提取 limitMax 变量避免重复计算
- 简化条件表达式提高代码可读性
- 更新调试日志输出格式增加 limitMax 参数
- 保持原有功能不变但改善代码结构

* ```
fix(auto-tools): 修复自动化工具中的键值映射和路径执行问题

- 修正了组键值映射逻辑,避免重复键冲突
- 更新了限制组最大执行数的判断条件,支持多键检查
- 修改了 runPath 函数参数结构,增加 root_name 参数
- 修复了队伍切换功能中的键值查找逻辑
- 改进了路径执行时的参数传递,确保正确的层级关系
```

* fix(auto-tools): 修复日志格式化参数缺失问题

- 在日志信息中添加缺失的参数以确保正确显示模式信息
- 修正了团队切换日志中的参数传递顺序
- 防止因参数不足导致的日志输出异常

* feat(auto-tools): 禁用战斗检测功能并优化队伍切换逻辑

- 移除自动战斗策略检测功能,简化路径判断逻辑
- 添加队伍配置切换至行走位的自动切换功能
- 更新配置文件标签提示,明确战斗检测已禁用
- 修正README文档中的队伍配置说明描述
- 优化团队切换流程,增强非战斗状态下的队伍管理

* fix(FullyAutoAndSemiAutoTools): 修复逻辑运算符优先级问题

- 修正了 limitMax 变量赋值中的括号位置,确保逻辑或运算符正确执行
- 修正了 order 属性赋值中的括号位置,确保逻辑或运算符正确执行
- 避免因运算符优先级导致的潜在逻辑错误

* fix(tools): 添加错误日志记录功能

- 在记录路径读取失败时添加错误日志
- 在HoeGround数据加载失败时添加错误日志
- 在团队数据加载失败时添加错误日志
- 在路径排序配置加载失败时添加错误日志
- 在顺序映射加载失败时添加错误日志
- 在组限制配置加载失败时添加错误日志
2026-02-02 18:05:25 +08:00

2216 lines
87 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let manifest_json = "manifest.json";
let manifest = undefined
let configSettings = undefined
const auto = {
semi: false,//半自动
run: false,//运行
skip: false,//跳过
key: "",
}
const dev = {
isDebug: false,
debug: undefined,
}
const cd = {
open: settings.open_cd || false,
http_api: settings.http_api || undefined,
}
const pathingName = "pathing"
let loadingLevel = 2
// const pathAsMap = new Map([])
// const pathRunMap = new Map([])
const needRunMap = new Map([])
const PATHING_ALL = new Array({
id: `${pathingName}`,
level: 0,
name: `${pathingName}`,
parent_name: "",
child_names: []
})
let settingsNameList = new Array()
const settingsNameAsList = new Array()
let PATH_JSON_LIST = new Array()
const config_root = 'config'
const levelName = "treeLevel"
const json_path_name = {
RecordText: `${config_root}\\record.json`,
RecordPathText: `${config_root}\\PathRecord.json`,
uidSettingsJson: `${config_root}\\uidSettings.json`,
templateMatchSettingsJson: `${config_root}\\settings.json`,
cdPath: `${config_root}\\cd-${pathingName}.json`,
SevenElement: `${config_root}\\SevenElement.json`,
RefreshSettings: `${config_root}\\RefreshSettings.json`,
pathJsonByUid: `${config_root}\\path-json-by-uid.json`,
PathOrder: `${config_root}\\PathOrder.json`,
HoeGround: `${config_root}\\HoeGround.json`,
LimitMax: `${config_root}\\LimitMax.json`,
}
// 定义记录文件的路径
// let RecordText = `${config_root}\\record.json`
// let RecordPathText = `${config_root}\\PathRecord.json`
let RecordList = new Array()
let RecordPathList = new Array()
let RecordLast = {
uid: "",
data: undefined,
timestamp: 0,
paths: new Set(), // 记录路径
errorPaths: new Set(),
groupPaths: new Set(),
}
const Record = {
uid: "",
data: undefined,
timestamp: 0,
paths: new Set(), // 记录路径
errorPaths: new Set(), // 记录错误路径
groupPaths: new Set(), // 记录分组路径
}
let RecordPath = {
uid: "",
paths: new Set(), // 记录路径
//{timestamp,path}
}
const config_list = {
black: [],
white: [],
}
const SevenElement = {
SevenElements: ['矿物', '火', '水', '风', '雷', '草', '冰', '岩'],
SevenElementsMap: new Map([
['矿物', ['夜泊石', '石珀', '清水玉', '万相石', '矿物']],
['火', []],
['水', ['海露花']],
['风', ['蒲公英籽']],
['雷', ['琉鳞石', '绯樱绣球']],
['草', []],
['冰', []],
['岩', []],
]),
}
const team = {
current: undefined,
fight: false,
fightName: settings.team_fight,
fightKeys: ['锄地专区', "敌人与魔物"],
SevenElements: settings.team_seven_elements ? settings.team_seven_elements.split(',').map(item => item.trim()) : [],
HoeGroundMap: new Map([]),
}
const timeType = Object.freeze({
hours: 'hours',//小时
cron: 'cron',//cron表达式
// 添加反向映射(可选)
fromValue(value) {
return Object.keys(this).find(key => this[key] === value);
}
});
/**
* 实时任务处理函数
* @param {boolean} is_common - 是否为通用任务标志
* @returns {void} 无返回值
*/
async function realTimeMissions(is_common = true) {
let real_time_missions = settings.real_time_missions // 从设置中获取实时任务列表
if (!Array.isArray(real_time_missions)) {
real_time_missions = [...real_time_missions]
}
if (!is_common) { // 处理非通用任务
if (real_time_missions.includes("自动战斗")) {
// await dispatcher.runAutoFightTask(new AutoFightParam());
await dispatcher.runTask(new SoloTask("AutoFight")); // 执行自动战斗任务
}
return // 非通用任务处理完毕后直接返回
}
// 处理通用任务
if (real_time_missions.includes("自动对话")) {
dispatcher.addTrigger(new RealtimeTimer("AutoSkip")); // 添加自动对话触发器
}
if (real_time_missions.includes("自动拾取")) {
// 启用自动拾取的实时任务
dispatcher.addTrigger(new RealtimeTimer("AutoPick")); // 添加自动拾取触发器
}
}
/**
* 根据用户ID加载路径JSON列表
* 该函数尝试从指定路径读取并解析JSON文件将解析后的数据转换为Map对象
* 然后根据当前用户ID获取对应的路径列表
*
* @returns {boolean} 加载成功返回true失败返回false
*/
function loadPathJsonListByUid() {
try {
// 读取并解析JSON文件内容
const raw = JSON.parse(file.readTextSync(json_path_name.pathJsonByUid));
// 将解析后的数组转换为Map对象
const map = new Map(raw);
// 获取当前用户ID对应的路径列表
const list = map.get(Record.uid);
// 检查获取的列表是否为有效数组且不为空
if (Array.isArray(list) && list.length > 0) {
// 更新全局PATH_JSON_LIST变量
PATH_JSON_LIST = list;
// 记录成功日志包含用户ID和路径数量
log.info(
"[PATH] 已加载 PATH_JSON_LISTuid={0}count={1}",
Record.uid,
PATH_JSON_LIST.length
);
return true;
}
} catch (e) {
// 捕获并记录异常信息
log.warn("[PATH] 加载 PATH_JSON_LIST 失败: {0}", e.message);
}
return false;
}
async function initRefresh(settingsConfig) {
let level = 0
const parent_level = level + 1
// 获取当前路径下的所有文件/文件夹
let pathSyncList = file.readPathSync(`${PATHING_ALL[level].name}`);
log.debug("{0}文件夹下有{1}个文件/文件夹", `${pathingName}`, pathSyncList.length);
// 预处理黑白名单数组移除空字符串并trim
const processedBlackList = config_list.black
.map(item => item.trim())
.filter(item => item !== "");
const processedWhiteList = config_list.white
.map(item => item.trim())
.filter(item => item !== "");
let blacklistSet = new Set(processedBlackList)
processedWhiteList.forEach(item => {
blacklistSet.delete(item)
})
const blacklist = Array.from(blacklistSet)
let settingsList = settingsConfig
let settingsRefreshList = []
let parentJson = {
name: `${levelName}_${level}_${level}`,
type: "multi-checkbox",
label: `选择要执行的${parent_level}级路径`,
options: []
}
for (const element of pathSyncList) {
// log.warn("element={0}", element)
const item = element.replace(`${pathingName}\\`, "");
if (!blacklist.find(black => item === black)) {
parentJson.options.push(item)
}
}
// settingsRefreshList.push({type: "separator"})
let treePathList = await readPaths(`${pathingName}`)
await debugKey('[init-refresh]_log-treePathList.json', JSON.stringify(treePathList))
let pathJsonList = await treeToList(treePathList)
await debugKey('[init-refresh]_log-pathJsonList.json', JSON.stringify(pathJsonList))
let obj = {
id: `${pathingName}`,
level: level,
name: `${pathingName}`,
parentId: undefined,
parentName: undefined,
rootName: undefined, // 最父级下一级名称
child_names: parentJson.options
};
await addUniquePath(obj)
PATH_JSON_LIST = pathJsonList
const forList = pathJsonList.filter(item => item.isFile)
for (const element of forList) {
const pathRun = element.path
// 检查路径是否被允许
const isBlacklisted = processedBlackList.some(item => pathRun?.includes(item));
const isWhitelisted = processedWhiteList.some(item => pathRun?.includes(item));
if (isBlacklisted && !isWhitelisted) {
continue;
}
//方案1
try {
loadingLevel = parseInt(settings.loading_level)
} catch (e) {
log.warn("配置 {0} 错误,将使用默认值{0}", "加载路径层级", loadingLevel)
} finally {
//
loadingLevel = loadingLevel < 1 ? 2 : loadingLevel
}
// 优化版本
for (let i = 0; i < loadingLevel; i++) {
const currentLevel = parent_level + 1 + i;
const parentLevel = parent_level + i;
const currentName = getChildFolderNameFromRoot(pathRun, currentLevel);
const childName = getChildFolderNameFromRoot(pathRun, currentLevel + 1);
// 检查当前层级是否存在
if (!currentName) {
break; // 没有当前层级,停止处理
}
// 过滤JSON文件
const filteredChildName = childName?.endsWith(".json") ? undefined : childName;
let child_names = Array.from(new Set(filteredChildName ? [filteredChildName] : []).difference(new Set(blacklist)))
// 获取父级名称用于建立层级关系
const parentName = getChildFolderNameFromRoot(pathRun, parentLevel);
const rootName = getChildFolderNameFromRoot(pathRun, parent_level + 1);
let obj1 = {
id: undefined,
parentId: element.parentId,
level: parentLevel, // 存储到目标层级 属于目标层级
name: currentName, // 当前层级名称
parentName: parentName, // 父级名称
rootName: rootName, // 最父级下一级名称
child_names: [...child_names]
};
await addUniquePath(obj1);
}
}
// 正确的排序方式
PATHING_ALL.sort((a, b) => {
// 首先按 level 排序
if (a.level !== b.level) {
return a.level - b.level;
}
const pathA = a?.path || '';
const pathB = b?.path || '';
// if (a.parent_name !== b.parent_name) {
// return a.parent_name.localeCompare(b.parent_name);
// }
// level 相同时按 path 排序
return pathA.localeCompare(pathB);
});
await debugKey('[init-refresh]_log-PATHING_ALL.json', JSON.stringify(PATHING_ALL))
const groupLevel = groupByLevel(PATHING_ALL);
await debugKey('[init-refresh]_log-groupLevel.json', JSON.stringify(groupLevel))
// const initLength = settingsList.length
let parentNameLast = undefined
// let parentNameNow = undefined
// const line = 30
// const br = `${"=".repeat(line)}\n`
let idx = 0
// const settingsSortMap = new Map([])
settingsRefreshList.push({type: "separator"})
settingsRefreshList.push({type: "separator"})
groupLevel.filter(list => list.length > 0).forEach(
(list) => {
let i = 0
list.filter(item => item && item.child_names && item.child_names.length > 0).forEach(item => {
const name = `${levelName}_${item.level}_${i}`
let prefix = ''
log.debug(`[{2}]Last{0},Current{1},Name{5}`, "比对", parentNameLast, item.parentName, item.name)
const isCommonLastAndCurrent = item.parentName !== parentNameLast;
if (isCommonLastAndCurrent) {
parentNameLast = item.parentName;
// let b = (line - item.parentName.length) % 2 === 0;
// const localLine = b ? ((line - item.parentName.length) / 2) : (Math.ceil((line - item.parentName.length) / 2))
// prefix = br + `${"=".repeat(localLine)}${item.parentName}${"=".repeat(localLine)}\n` + br
}
// const p = idx === 0 ? "【地图追踪】\n" : `${prefix}[${item.parent_name}-${item.name}]\n`
const p = `\n${prefix}${(item.rootName && item.name !== item.rootName) ? "《" + item.rootName + "》->" : ""}[${item.name}]`
idx++
let leveJson = {
name: `${name}`,
type: "multi-checkbox",
label: `选择要执行的${item.level + 1}级路径${p}`,
options: []
}
// todo: 待优化
const targetItem = PATH_JSON_LIST.filter(list_item => !list_item.levelName)
.find(list_item => list_item.id === item.parentId);
if (targetItem) {
targetItem.levelName = name || undefined;
}
// leveJson.options = leveJson.options.concat(item.child_names)
leveJson.options = [...item.child_names]
if (leveJson.options && leveJson.options.length > 0) {
settingsNameAsList.push({
settings_name: name,
settings_as_name: item.name
})
settingsNameList.push(name)
const existingIndex = settingsRefreshList.findIndex(item => item.name === leveJson.name);
if (existingIndex !== -1) {
// 替换已存在的配置项
settingsRefreshList[existingIndex] = leveJson;
} else {
if (isCommonLastAndCurrent) {
settingsRefreshList.push({type: "separator"})
}
parentNameLast = item.parentName;
settingsRefreshList.push({type: "separator"})
// 添加新的配置项
settingsRefreshList.push(leveJson);
}
i++
}
})
}
)
await debugKey('[init-refresh]_log-settingsRefreshList.json', JSON.stringify(settingsRefreshList))
//settingsRefreshList 二级排序 todo:
level++
settingsList = Array.from(new Set(settingsList.concat(settingsRefreshList)))
settingsList.filter(
item => item.name === 'key'
).forEach(item => {
// 刷新settings自动设置密钥
item.default = manifest.key
})
settingsList.filter(
item => item.name === 'config_run'
).forEach(item => {
// 刷新settings自动设置执行
item.default = "执行"
})
const uidSettingsMap = new Map([])
// 更新当前用户的配置
uidSettingsMap.set(Record.uid, settingsList)
// 安全写入配置文件
try {
file.writeTextSync(json_path_name.uidSettingsJson, JSON.stringify([...uidSettingsMap]))
log.debug("用户配置已保存: {uid}", Record.uid)
} catch (error) {
log.error("保存用户配置失败: {error}", error.message)
}
settingsList.filter(
item => item.name === 'config_uid'
).forEach(item => {
// 刷新settings自动设置执行
item.label = "当前配置uid:\{" + Record.uid + "\}\n(仅仅显示配置uid无其他作用)"
})
file.writeTextSync(manifest.settings_ui, JSON.stringify(settingsList))
// ===== 保存 PATH_JSON_LIST按 uid=====
try {
let pathJsonMap = new Map();
try {
const raw = JSON.parse(file.readTextSync(json_path_name.pathJsonByUid));
pathJsonMap = new Map(raw);
} catch (e) {
log.debug("PATH_JSON_LIST 映射文件不存在,将新建");
}
pathJsonMap.set(Record.uid, PATH_JSON_LIST);
file.writeTextSync(
json_path_name.pathJsonByUid,
JSON.stringify([...pathJsonMap])
);
log.info(
"[PATH] 已保存 PATH_JSON_LISTuid={0}count={1}",
Record.uid,
PATH_JSON_LIST.length
);
} catch (e) {
log.error("[PATH] 保存 PATH_JSON_LIST 失败: {0}", e.message);
}
if (settings.refresh_record) {
if (settings.refresh_record_mode === "UID") {
RecordList = RecordList.filter(item => item.uid !== Record.uid)
RecordPathList = RecordPathList.filter(item => item.uid !== Record.uid)
file.writeTextSync(json_path_name.RecordPathText, JSON.stringify(RecordPathList))
file.writeTextSync(json_path_name.RecordText, JSON.stringify(RecordList))
log.info("已清空UID:{0}记录文件", Record.uid)
return
}
file.writeTextSync(json_path_name.RecordPathText, JSON.stringify([]))
file.writeTextSync(json_path_name.RecordText, JSON.stringify([]))
log.info("已清空全部记录文件")
}
}
/**
* 初始化用户ID设置映射表
* @param {Map} uidSettingsMap - 用于存储用户ID设置的Map对象
* @returns {Map} 返回初始化后的用户ID设置映射表
*/
async function initUidSettingsMap(uidSettingsMap) {
// 获取用户设置JSON文件的路径
const uidSettingsJson = json_path_name.uidSettingsJson;
try {
// 读取并解析JSON文件内容转换为Map对象
const existingData = JSON.parse(file.readTextSync(uidSettingsJson))
uidSettingsMap = new Map(existingData)
} catch (e) {
// 文件不存在时使用空Map
log.debug("配置文件不存在,将创建新的");
}
return uidSettingsMap;
}
/**
* 加载用户ID设置映射表
* @param {Map} uidSettingsMap - 用户ID到设置项的映射表
*/
async function loadUidSettingsMap(uidSettingsMap) {
// 从映射表中获取当前用户的设置
let uidSettings = uidSettingsMap.get(Record.uid);
// 如果存在用户设置
if (uidSettings) {
if (!loadPathJsonListByUid()) {
throw new Error(
"未找到 PATH_JSON_LIST请先执行一次【刷新配置】"
);
}
try {
let templateMatchSettings = JSON.parse(file.readTextSync(json_path_name.templateMatchSettingsJson));
// 筛选出名称为'config_run'的设置项
templateMatchSettings.filter(
item => item.name === 'config_run'
).forEach(item => {
// 刷新settings自动设置执行
item.default = "执行"
})
templateMatchSettings.filter(
item => item.name === 'config_uid'
).forEach(item => {
// 刷新settings自动设置执行
item.label = "当前配置uid:\{" + Record.uid + "\}\n(仅仅显示配置uid无其他作用)"
})
let filterSettings = []
const filterUidSettings = uidSettings.filter(item => item?.name?.startsWith(levelName))
let tempFilterUidSettings = []
let last_parent_level = undefined
for (let i = 0; i < filterUidSettings.length; i++) {
let item = filterUidSettings[i]
// log.warn(`item:{0}`,item)
if (item?.name?.startsWith(levelName)) {
if (i === 0) {
tempFilterUidSettings.push({type: "separator"})
tempFilterUidSettings.push({type: "separator"})
}
tempFilterUidSettings.push({type: "separator"})
let parent_level = item?.name?.replace(levelName + "_", "")?.split("_")[0] || undefined
if (i !== 0 && parent_level && parent_level !== last_parent_level) {
tempFilterUidSettings.push({type: "separator"})
}
tempFilterUidSettings.push(item)
last_parent_level = parent_level
}
}
// log.debug("用户配置: {0}", tempFilterUidSettings)
filterSettings = tempFilterUidSettings
// filterSettings = filterUidSettings
// templateMatchSettings = Array.from(new Set(templateMatchSettings).difference(new Set(filterUidSettings)))
try {
loadingLevel = parseInt(settings.loading_level)
} catch (e) {
log.warn("配置 {0} 错误,将使用默认值{0}", "加载路径层级", loadingLevel)
} finally {
//
loadingLevel = loadingLevel < 1 ? 2 : loadingLevel
}
//todo: 高阶层级过滤
const highLevelFiltering = settings.high_level_filtering || undefined
if (highLevelFiltering && highLevelFiltering?.trim() !== "") {
/**
* 实例pathing\地方特产\
* 地方特产
* 实例pathing\地方特产\枫丹\
* 地方特产->枫丹
* 实例pathing\地方特产\枫丹\幽光星星\
* 地方特产->枫丹->幽光星星
* 实例pathing\地方特产\枫丹\幽光星星\幽光星星@jbcaaa\
* 地方特产->枫丹->幽光星星->幽光星星@jbcaaa
*/
let keys = new Set([])
if (highLevelFiltering) {
const set = new Set(highLevelFiltering.split("->"));
keys = keys.union(set)
}
// function countMatchingElements(mainSet, subset) {
// const mainSetObj = new Set(mainSet);
// return subset.filter(item => mainSetObj.has(item)).length;
// }
// const key = keys[keys.size - 1]
// PATH_JSON_LIST.filter(item => item.level > 0)
// 预先建立 levelName 到路径信息的映射
const levelNameMap = new Map();
PATH_JSON_LIST.forEach(item => {
log.debug(`item:{0}`, JSON.stringify(item))
if (item.levelName) {
levelNameMap.set(item.levelName, item);
}
});
log.warn("levelNameMap:{0}", JSON.stringify([...levelNameMap]))
//中间一段路径名称
const dir_key = Array.from(keys).join("\\")
filterSettings = filterSettings.filter(item => {
if (!item?.name?.startsWith(levelName)) {
return true
}
// const settings_level = PATH_JSON_LIST.filter(list_item => list_item.levelName === item.name).find();
const settings_level = levelNameMap.get(item.name);
if (settings_level) {
//只加载指定目录
return (settings_level.path.includes(dir_key))
}
return false
})
}
const theLayer = settings.the_layer || false
const levelSettings = filterSettings.filter(item => {
if (!item?.name?.startsWith(levelName)) {
return true
}
const level_all = item.name.replaceAll(levelName + "_", "");
// 获取级别
const level = level_all.split("_").map(parseInt)[0]
if (theLayer) {
//只加载指定级别的设置
return (loadingLevel === level + 1)
}
// 检查级别是否小于等于加载层级
return (loadingLevel > level - 1)
})
templateMatchSettings = [...templateMatchSettings, ...levelSettings]
while (templateMatchSettings.length > 0 &&
templateMatchSettings[templateMatchSettings.length - 1]?.type === "separator") {
templateMatchSettings.pop();
}
/**
* 限制连续的分隔符数量不超过3个
* @param {Array} settings - 设置项数组
* @returns {Array} 处理后的设置项数组
*/
function limitConsecutiveSeparators(settings) {
if (!Array.isArray(settings) || settings.length === 0) {
return settings;
}
const result = [];
let consecutiveSeparatorCount = 0;
for (const item of settings) {
if (item?.type === "separator") {
consecutiveSeparatorCount++;
// 只有当连续分隔符数量不超过3个时才添加
if (consecutiveSeparatorCount <= 3) {
result.push(item);
}
} else {
// 遇到非分隔符时重置计数
consecutiveSeparatorCount = 0;
result.push(item);
}
}
return result;
}
templateMatchSettings = limitConsecutiveSeparators(templateMatchSettings)
// uidSettings.push(levelSettings)
// 将更新后的设置写入配置文件
file.writeTextSync(manifest.settings_ui, JSON.stringify(templateMatchSettings))
} catch (e) {
// 记录错误日志
log.error("加载用户配置失败: {error}", e.message)
}
}
// 初始化配置设置
configSettings = await initSettings()
}
async function initRun(config_run) {
if (!loadPathJsonListByUid()) {
throw new Error(
"未找到 PATH_JSON_LIST请先执行一次【刷新配置】"
);
}
log.info(`初始{0}配置`, config_run)
const cdPath = json_path_name.cdPath;
const timeJson = (!cd.open) ? new Set() : new Set(JSON.parse(file.readTextSync(cdPath)).sort(
(a, b) => b.level - a.level
))
const multiCheckboxMap = await getMultiCheckboxMap();
if (dev.isDebug) {
await debugKey('[init-run]_log-multiCheckboxMap.json', JSON.stringify(Array.from(multiCheckboxMap)))
const keysList = Array.from(multiCheckboxMap.keys());
await debugKey('[init-run]_log-keysList.json', JSON.stringify(keysList))
}
settingsNameList = settingsNameList.concat(
Array.from(multiCheckboxMap.keys().filter(key =>
typeof key === "string" &&
key.startsWith(levelName) &&
multiCheckboxMap.get(key)?.options?.length > 0
))
)
settingsNameList = settingsNameList
.filter(key => typeof key === "string" && key.trim() !== "")
log.debug(`settingsNameList:{0}`, JSON.stringify(settingsNameList))
// todo:补齐执行前配置
// ================= 执行前配置(补齐 needRunMap =================
await debugKey(
'[init-run]_log-PATH_JSON_LIST.json',
JSON.stringify(PATH_JSON_LIST)
);
for (const settingsName of settingsNameList) {
// 1. 读取 multi-checkbox 的 JSON 描述
const multiJson = await getJsonByMultiCheckboxName(settingsName);
if (!multiJson || !multiJson.options || multiJson.options.length === 0) continue;
const labelParentName = getBracketContent(multiJson.label); // [xxx]
const selectedOptions = multiJson.options;
// 2. 从 PATH_JSON_LIST 中筛选命中的路径
const filter = PATH_JSON_LIST.filter(item => item.children.length === 0);
await debugKey(`[init-run]_log-filtermatchedPaths.json`, JSON.stringify(filter))
let matchedPaths = filter.filter(item => {
const hitParent = item.fullPathNames.includes(labelParentName) || labelParentName === `${pathingName}`;
const hitOption = selectedOptions.some(opt =>
item.fullPathNames.some(name => name.includes(opt))
);
return hitParent && hitOption;
}).map(item => {
const selected = selectedOptions.find(opt =>
item.fullPathNames.some(name => name.includes(opt))
);
return {
level: item.level,
name: item.name,
id: item.id,
parentId: item.parentId,
parentName: item.parentName,
rootName: item.rootName,
selected: selected,
path: item.path,
fullPathNames: item.fullPathNames
};
});
await debugKey(`[init-run]_log-matchedPaths.json`, JSON.stringify(matchedPaths))
function generatedKey(item, useParent = false) {
const separator = "->";
if (useParent) {
// 使用父级名称的逻辑
if (item?.parent_name && !item?.parentName) {
return `${item.parent_name}${separator}${item.name}`;
} else if (item?.parentName) {
return `${item.parentName}${separator}${item.name}`;
}
} else {
// 三层结构的逻辑
if (item?.rootName && item?.parentName && item?.rootName !== "" && item?.parentName !== item?.rootName) {
return `${item.rootName}${separator}${item.parentName}${separator}${item.name}`;
} else if (item?.root_name && item?.parent_name && item?.root_name !== "" && item?.parent_name !== item?.root_name) {
return `${item.root_name}${separator}${item.parent_name}${separator}${item.name}`;
}
// 二层结构的逻辑
if (item?.parent_name && !item?.parentName) {
return `${item.parent_name}${separator}${item.name}`;
} else if (item?.parentName) {
return `${item.parentName}${separator}${item.name}`;
}
}
// 默认返回名称
return item.name;
}
// 3. CD 过滤(可选)
if (cd.open && matchedPaths.length > 0) {
let recordPaths = [];
try {
recordPaths = Array.from(RecordPath.paths);
} catch (e) {
log.error("读取记录路径失败: {error}", e.message)
}
recordPaths.sort((a, b) => b.timestamp - a.timestamp)
const timeConfigs = Array.from(timeJson);
await debugKey(`[init-run]_log-timeConfigs.json`, JSON.stringify(timeConfigs))
await debugKey(`[init-run]_log-recordPaths.json`, JSON.stringify(recordPaths))
let bodyList = []
const now = Date.now();
//首次过滤
let cdFilterMatchedPaths = matchedPaths.filter(item => {
const timeConfig = timeConfigs.find(cfg =>
item.fullPathNames.includes(cfg.name)
);
if (!timeConfig) return false;
const record = recordPaths.find(r =>
r.path.includes(item.path)
);
if (!record || !record.timestamp) return false;
switch (timeType.fromValue(timeConfig.type)) {
case timeType.cron:
// timeConfig.name
// const key = generatedKey(item);
const item_key = bodyList.find(cfg => cfg.key === item.path)
if (!item_key) {
bodyList.push({
key: item.path,
cronExpression: timeConfig.value,
startTimestamp: record.timestamp,
endTimestamp: now
})
} else if (item_key.startTimestamp < record.timestamp) {
item_key.startTimestamp = record.timestamp
item_key.cronExpression = timeConfig.value
}
return true;
default:
return true;
}
return true
})
await debugKey(`[init-run]_log-cdFilterMatchedPaths.json`, JSON.stringify(cdFilterMatchedPaths))
//多次请求改一次请求
const nextMap = bodyList.length <= 0 ? new Map() : await cronUtil.getNextCronTimestampAll(bodyList, cd.http_api) ?? new Map();
await debugKey(``, JSON.stringify({nextMap: [...nextMap]}), true)
//还在cd中的path
const in_cd_paths = cdFilterMatchedPaths.filter(async item => {
const timeConfig = timeConfigs.find(cfg =>
item.fullPathNames.includes(cfg.name)
);
switch (timeType.fromValue(timeConfig.type)) {
case timeType.hours: {
const diff = getTimeDifference(record.timestamp, now);
return (diff.total.hours < timeConfig.value);
}
case timeType.cron: {
// const next = await cronUtil.getNextCronTimestamp(
// `${timeConfig.value}`,
// record.timestamp,
// now,
// cd.http_api
// );
// return (next && now >= next);
// const key = generatedKey(item);
const cron_ok = nextMap.get(item.path)
return !(cron_ok?.ok); // 不应该在CD中时返回true
}
default:
return false;
}
});
await debugKey(`[init-run]_log-in_cd_paths.json`, JSON.stringify(in_cd_paths))
// 移除 CD 未到的路径
if (in_cd_paths.length > 0) {
const cdPathSet = new Set(in_cd_paths.map(item => item.path));
matchedPaths = matchedPaths.filter(item => !cdPathSet.has(item.path));
}
}
// 4. 写入 needRunMap
if (matchedPaths.length > 0) {
//锄地队对应
try {
// {
// uid:"",
// parent_name:"",
// root_name: "",
// name:"",
// team_name:""
// } json支持
let teamHoeGroundList = JSON.parse(file.readTextSync(json_path_name.HoeGround)) ?? [{
uid: "",
is_common: false,
parent_name: undefined,
root_name: undefined,
name: undefined,
team_name: ""
}]
teamHoeGroundList = teamHoeGroundList.filter(item => item?.uid === Record.uid || item?.is_common)
teamHoeGroundList.sort((a, b) => {
const orderA = a?.is_common ? 1 : 0;
const orderB = b?.is_common ? 1 : 0;
return orderB - orderA; // 这样 is_common 为 true 的会排在前面
});
// 自定义锄地队对应可覆盖公共锄地队对应
teamHoeGroundList.forEach(item => {
if (item?.root_name?.trim() !== "") {
const key = generatedKey(item);
team.HoeGroundMap.set(key, item.team_name);
} else {
const key_parent = generatedKey(item, true);
team.HoeGroundMap.set(key_parent, item.team_name);
}
})
log.info(`{0}加载完成`, json_path_name.HoeGround)
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
//输入值优先覆盖
try {
const teamHoeGroundStr = settings.team_hoe_ground || "parentName->name=key"
teamHoeGroundStr.split(",").forEach(item => {
const [key, team_name] = item.split("=");
team.HoeGroundMap.set(key, team_name)
})
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
// 排序
const orderMap = new Map()
try {
// {
// uid:"",
// parent_name:"",
// root_name: "",
// name:"",
// order:0
// } json支持
let orderList = JSON.parse(file.readTextSync(json_path_name.PathOrder)) ?? [{
uid: "",
is_common: false,
parent_name: undefined,
root_name: undefined,
name: undefined,
order: 0
}]
orderList = orderList.filter(item => item?.uid === Record.uid || item?.is_common)
orderList.sort((a, b) => {
const orderA = a?.is_common ? 1 : 0;
const orderB = b?.is_common ? 1 : 0;
return orderB - orderA; // 这样 is_common 为 true 的会排在前面
});
// 自定义排序可覆盖公共排序
orderList.forEach(item => {
if (item.root_name) {
const key = generatedKey(item);
orderMap.set(key, item.order)
} else {
const key_parent = generatedKey(item, true);
orderMap.set(key_parent, item.order)
}
})
log.info(`{0}加载完成`, json_path_name.PathOrder)
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
await debugKey("[init-run]_log-orderMap-By-json.json", JSON.stringify([...orderMap]))
//输入值优先覆盖
try {
// 支持多条规则,例如: "rootName->parentName->name1=1,rootName->parentName->name2=2"
const orderStr = settings.order_rules || "rootName->parentName->name=1"
orderStr.split(",").forEach(item => {
const [key, order] = item.split("=");
orderMap.set(key, parseInt(order))
})
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
await debugKey("[init-run]_log-orderMap-All.json", JSON.stringify([...orderMap]))
//限制组最大执行数
const openLimitMax = settings.open_limit_max
let limitMaxByGroup = new Map()
if (openLimitMax) {
log.info(`{0}`, '已开启限制组最大执行数')
try {
let limitMaxList = JSON.parse(file.readTextSync(json_path_name.LimitMax)) ?? [{
uid: "",
is_common: false,
parent_name: undefined,
root_name: undefined,
name: undefined,
max: 0
}]
limitMaxList = limitMaxList.filter(item => item?.uid === Record.uid || item?.is_common)
limitMaxList.sort((a, b) => {
const orderA = a?.is_common ? 1 : 0;
const orderB = b?.is_common ? 1 : 0;
return orderB - orderA; // 这样 is_common 为 true 的会排在前面
});
// 自定义排序可覆盖公共排序
limitMaxList.forEach(item => {
if (item?.root_name?.trim() !== "") {
const key = generatedKey(item);
limitMaxByGroup.set(key, parseInt(item.max))
log.debug(`limitMaxList=>{0}->{1}`, key, item.max)
} else {
const key_parent = generatedKey(item, true);
limitMaxByGroup.set(key_parent, parseInt(item.max))
log.debug(`limitMaxList=>{0}->{1}`, key_parent, item.max)
}
})
//输入值优先覆盖
try {
// 支持多条规则,例如: "rootName->parentName->name1=1,rootName->parentName->name2=2"
const limitMaxStr = settings.limit_max_group || "rootName->parentName->name=1"
limitMaxStr.split(",").forEach(item => {
const [key, max] = item.split("=");
limitMaxByGroup.set(key, parseInt(max))
})
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
} catch (e) {
log.error(`加载失败:{0}`, e.message)
}
}
function groupByParentAndName(list) {
const map = new Map();
list.forEach(item => {
// const key = `${item.parentName}->${item.name}`;
const key = generatedKey(item);
// const key_parent = generatedKey(item, true);
if (!map.has(key)) map.set(key, []);
map.get(key).push(item);
// if (!map.has(key_parent)) map.set(key_parent, []);
// map.get(key_parent).push(item);
});
return Array.from(map.values()); // 转成二维数组 [[], []]
}
let groups = groupByParentAndName(matchedPaths);
groups.sort((a, b) => {
const a_key = generatedKey(a)
const b_key = generatedKey(b)
const orderA = orderMap.get(a_key) ?? 0; // 没在 JSON 中的排到最后
const orderB = orderMap.get(b_key) ?? 0;
if (orderA === orderB) {
return a_key?.localeCompare(b_key);
}
return orderB - orderA; // 修改为倒序数字比较
})
const asMap = new Map()
groups.forEach(group => {
const groupOne = group[0]
let groupKey_parent = generatedKey(groupOne, true);
if (orderMap.has(groupKey_parent) || team.HoeGroundMap.has(groupKey_parent)) {
let groupKey = generatedKey(groupOne);
asMap.set(groupKey, groupKey_parent)
}
})
groups.forEach(group => {
const groupOne = group[0]
let groupKey = generatedKey(groupOne);
let key = groupKey
if (asMap.has(key)) {
key = asMap.get(key)
}
let runGroup = group
//限制组最大执行数
if (openLimitMax && (limitMaxByGroup.has(key) || limitMaxByGroup.has(groupKey))) {
const limitMax = (limitMaxByGroup.get(key) || limitMaxByGroup.get(groupKey)) ?? 99999
const max = Math.min(group.length, limitMax)
runGroup = group.slice(0, max)
log.debug("[限制组最大执行数] groupKey={0},max={1},limitMax={2},group.length={3}", key, max, limitMax, group.length)
}
needRunMap.set(key, {
order: (orderMap.get(key) || orderMap.get(groupKey)) ?? 0,
paths: runGroup,
parent_name: groupOne.parentName,
key: key,
current_name: groupOne.name,
name: settingsName //多选项 名称 如 treeLevel_0_0
});
})
// todo 对 needRunMap 进行排序
// 对 needRunMap 进行整体排序
const sortedNeedRunMap = new Map(
[...needRunMap.entries()].sort((a, b) => {
// 使用值中的 order 字段进行排序
const orderA = a[1].order ?? 0;
const orderB = b[1].order ?? 0;
// 按降序排序(数值大的优先)
return orderB - orderA;
})
);
// 替换原来的 needRunMap
needRunMap.clear();
for (const [key, value] of sortedNeedRunMap) {
needRunMap.set(key, value);
}
await debugKey(
'[init-run]_log-needRunMap.json',
JSON.stringify([...needRunMap])
);
}
log.info("[执行前配置完成] needRunMap.size={0}", needRunMap.size);
}
}
async function init() {
let settingsConfig = await initSettings(`${config_root}\\`);
let utils = [
"cron",
"SwitchTeam",
"uid",
]
for (let util of utils) {
eval(file.readTextSync(`utils/${util}.js`));
}
if (manifest.key !== settings.key) {
let message = "密钥不匹配";
if (settings.key) {
message += ",脚本可能存在升级 密钥已经改变 请查看文档功能后重新获取密钥"
}
throw new Error(message)
}
auto.semi = settings.mode === "半自动"
if (auto.semi) {
auto.run = settings.auto_semi_key_mode === "继续运行"
auto.skip = settings.auto_semi_key_mode === "跳过"
auto.key = settings.auto_key
if (!auto.key) {
throw new Error(settings.mode + "模式下必须开启快捷键设置")
}
}
dev.debug = (dev.debug) ? dev.debug : settings.debug
dev.isDebug = settings.is_debug
cd.open = (cd.open) ? cd.open : settings.cd_open
cd.http_api = (cd.http_api) ? cd.http_api : settings.http_api
await debugKey("[init]_log-cd-settings.json", JSON.stringify(cd))
config_list.black = settings.config_black_list ? settings.config_black_list.split(",") : []
config_list.white = settings.config_white_list ? settings.config_white_list.split(",") : []
if (!file.IsFolder(`${pathingName}`)) {
let batFile = "SymLink.bat";
log.error("{0}文件夹不存在请在BetterGI中右键点击本脚本选择{1}。然后双击脚本目录下的{2}文件以创建文件夹链接", `${pathingName}`, "打开所在目录", batFile);
return false;
}
try {
let parse = JSON.parse(file.readTextSync(json_path_name.SevenElement));
if (parse) {
parse?.sort((a, b) => a.level - b.level)
SevenElement.SevenElements = Array.from(new Set(SevenElement.SevenElements.concat(parse.map(item => item.name))))
parse.forEach(item => {
const name = item.name
let value = item.value
if (SevenElement.SevenElementsMap.has(name)) {
value = Array.from(new Set(SevenElement.SevenElementsMap.get(name).concat(value)))
}
SevenElement.SevenElementsMap.set(name, value)
})
}
} catch (e) {
log.warn("[SevenElement]初始化失败error:{0}", e.message)
}
//记录初始化
await initRecord();
// 读取现有配置并合并
let uidSettingsMap = new Map()
uidSettingsMap = await initUidSettingsMap(uidSettingsMap);
//总控
//刷新settings
const config_run = settings.config_run;
log.info("开始执行配置: {config_run}", config_run)
if (config_run === "刷新") {
await initRefresh(settingsConfig);
log.info("配置{0}完成", config_run)
} else if (config_run === "加载") {
//直接从配置文件中加载对应账号的配置
await loadUidSettingsMap(uidSettingsMap);
log.info("配置{0}完成", config_run)
} else
// 初始化needRunMap
if (config_run === "执行") {
await initRun(config_run);
await realTimeMissions()
}
return true
}
(async function () {
try {
if (await init()) {
await main()
}
} finally {
await saveRecordPaths();
await saveRecord();
}
})()
async function main() {
let lastRunMap = new Map()
function chooseBestRun() {
if (settings.choose_best && RecordLast.paths.size > 0) {
// 由于在迭代过程中删除元素会影响迭代,先收集要删除的键
const keysToDelete = [];
// 优先跑上次没跑过的路径
// 使用 Set 提高性能
const lastListSet = new Set([...RecordLast.paths]);
for (const [key, one] of needRunMap.entries()) {
// 检查当前任务的路径是否都不在上次执行的路径中
const allPathsInLast = one.paths.every(pathObj => lastListSet.has(pathObj.path));
if (!allPathsInLast) {
lastRunMap.set(key, one);
keysToDelete.push(key);
}
}
// 然后批量删除
for (const key of keysToDelete) {
needRunMap.delete(key);
}
//保持排序
// 对 lastRunMap 进行整体排序
const sortedNeedRunMap = new Map(
[...lastRunMap.entries()].sort((a, b) => {
// 使用值中的 order 字段进行排序
const orderA = a[1].order ?? 0;
const orderB = b[1].order ?? 0;
// 按降序排序(数值大的优先)
return orderB - orderA;
})
);
// 替换原来的 lastRunMap
lastRunMap.clear();
for (const [key, value] of sortedNeedRunMap) {
lastRunMap.set(key, value);
}
}
}
chooseBestRun();
await debugKey("[run]_log-lastRunMap.json", JSON.stringify([...lastRunMap]))
await debugKey("[run]_log-needRunMap.json", JSON.stringify([...needRunMap]))
if (needRunMap.size > 0) {
if (settings.choose_best) {
log.info(`[{0}] [{1}]`, settings.mode, "择优模式-启动")
}
await runMap(needRunMap)
}
if (lastRunMap.size > 0) {
log.info(`[{0}] [{1}]`, settings.mode, "择优模式-收尾")
await runMap(lastRunMap)
}
// if (needRunMap.size <= 0 && lastRunMap.size <= 0) {
// log.info(`设置目录{0}完成`, "刷新")
// }
// log.info(`[{mode}] path==>{path},请按下{key}以继续执行[${manifest.name} JS]`, settings.mode, "path", AUTO_STOP)
// await keyMousePressStart(AUTO_STOP);
// log.info(`[{mode}] path==>{path},请按下{key}以继续执行[${manifest.name} JS]`, settings.mode, "path", AUTO_STOP)
}
/**
* 保存记录路径的函数
* 该函数将RecordPath对象中的Set类型数据转换为数组后保存到文件
*/
async function saveRecordPaths() {
// 保存前将 Set 转换为数组因为JSON不支持Set类型
// 创建一个新的记录对象,包含原始记录的所有属性
const recordToSave = {
// 使用展开运算符复制Record对象的所有属性保持其他数据不变
...RecordPath,
// 处理 paths 数组
paths: (() => {
// 1. 使用 Map 来辅助去重Map 的 key 是 pathvalue 是完整的 item 对象
const pathMap = new Map();
// 假设 RecordPath.paths 是一个 Set先转为数组进行遍历
[...RecordPath.paths].forEach(item => {
// 获取当前项的路径字符串
const currentPath = item.path;
// 检查 Map 中是否已经存在该路径
if (pathMap.has(currentPath)) {
// 如果存在,比较时间戳
const existingItem = pathMap.get(currentPath);
// 如果当前项的时间戳比已存在的大,则更新 Map 中的值
if (item.timestamp > existingItem.timestamp) {
pathMap.set(currentPath, item);
}
} else {
// 如果不存在,直接存入 Map
pathMap.set(currentPath, item);
}
});
// 2. 将 Map 中的值(去重后的对象数组)转换回我们需要的格式
return Array.from(pathMap.values()).map(item => ({
timestamp: item.timestamp,
path: item.path
}));
})()
};
// 确保 RecordPathList 是数组
if (!Array.isArray(RecordPathList)) {
RecordPathList = Array.from(RecordPathList);
}
let temp = RecordPathList.find(item => item.uid === Record.uid)
if (temp) {
// RecordList.splice(RecordList.indexOf(temp),1)
temp.paths = [...recordToSave.paths, ...temp.paths]
// temp.errorPaths = [...recordToSave.errorPaths, ...temp.errorPaths]
// temp.groupPaths = [...recordToSave.groupPaths, ...temp.groupPaths]
} else {
// 将记录对象添加到记录列表中
RecordPathList.push(recordToSave)
}
// 将记录列表转换为JSON字符串并同步写入文件
file.writeTextSync(json_path_name.RecordPathText, JSON.stringify(RecordPathList))
log.info("saveRecordPath保存记录文件成功")
}
/**
* 保存当前记录到记录列表并同步到文件
* 该函数在保存前会将Set类型的数据转换为数组格式确保JSON序列化正常进行
*/
async function saveRecord() {
// 保存前将 Set 转换为数组
// 创建一个新的记录对象,包含原始记录的所有属性
const recordToSave = {
// 使用展开运算符复制Record对象的所有属性
...Record,
// 将paths Set转换为数组
paths: [...Record.paths],
// 将errorPaths Set转换为数组
errorPaths: [...Record.errorPaths],
// 将groupPaths Set转换为数组并对每个元素进行特殊处理
groupPaths: [...Record.groupPaths].map(item => ({
// 保留name属性
name: item.name,
// 将item中的paths Set转换为数组
paths: [...item.paths]
}))
};
// 将记录对象添加到记录列表中
RecordList.push(recordToSave)
// 将记录列表转换为JSON字符串并同步写入文件
file.writeTextSync(json_path_name.RecordText, JSON.stringify(RecordList))
log.info("saveRecord保存记录文件成功")
}
/**
* 计算两个时间之间的差值并返回指定格式的JSON
* @param {number|Date} startTime - 开始时间时间戳或Date对象
* @param {number|Date} endTime - 结束时间时间戳或Date对象
* @returns {Object} diff_json - 包含info和total的对象
*/
function getTimeDifference(startTime, endTime) {
// 确保输入是时间戳
const start = typeof startTime === 'object' ? startTime.getTime() : startTime;
const end = typeof endTime === 'object' ? endTime.getTime() : endTime;
// 计算总差值(毫秒)
const diffMs = Math.abs(end - start);
// 计算总时间(小数)
const totalSeconds = diffMs / 1000;
const totalMinutes = totalSeconds / 60;
const totalHours = totalSeconds / 3600;
// 计算info部分整数
const infoHours = Math.floor(totalHours % 24);
const remainingAfterHours = (totalHours % 24) - infoHours;
const infoMinutes = Math.floor(remainingAfterHours * 60);
const remainingAfterMinutes = (remainingAfterHours * 60) - infoMinutes;
const infoSeconds = Math.floor(remainingAfterMinutes * 60);
// 输出类似:
// {
// info: { hours: 1, minutes: 0, seconds: 0 },
// total: { hours: 1, minutes: 60, seconds: 3600 }
// }
const diff_json = {
info: {
hours: infoHours,
minutes: infoMinutes,
seconds: infoSeconds
},
total: {
hours: parseFloat(totalHours.toFixed(6)),
minutes: parseFloat(totalMinutes.toFixed(6)),
seconds: parseFloat(totalSeconds.toFixed(6))
}
};
return diff_json;
}
/**
* 初始化记录函数
* 该函数用于初始化一条新的记录包括设置UID、时间戳和调整后的日期数据
* 同时会检查记录列表中是否存在相同UID的最新记录并进行更新
*/
async function initRecord() {
// 设置记录的唯一标识符通过OCR技术获取
Record.uid = await uidUtil.ocrUID()
// 设置记录的时间戳为当前时间
Record.timestamp = Date.now()
// 获取并设置调整后的日期数据
Record.data = getAdjustedDate()
try {
// 尝试读取记录文件
// 读取后将数组转换回 Set处理特殊的数据结构
RecordList = JSON.parse(file.readTextSync(json_path_name.RecordText), (key, value) => {
// 处理普通路径集合
if (key === 'paths' || key === 'errorPaths') {
return new Set(value);
}
// 处理分组路径集合保持嵌套的Set结构
if (key === 'groupPaths') {
return new Set(value.map(item => ({
name: item.name,
paths: new Set(item.paths)
})));
}
return value;
}) ?? RecordList;
} catch (e) {
// 如果读取文件出错,则忽略错误(可能是文件不存在或格式错误)
}
try {
// 尝试读取记录文件
// 读取后将数组转换回 Set处理特殊的数据结构
RecordPathList = JSON.parse(file.readTextSync(json_path_name.RecordPathText), (key, value) => {
// 处理分组路径集合保持嵌套的Set结构
if (key === 'paths') {
return new Set(value.map(item => ({
timestamp: item.timestamp,
path: item.path
})));
}
return value;
}) ?? RecordPathList
RecordPath = RecordPathList.find(item => item.uid === Record.uid) ?? RecordPath
} catch (e) {
// 如果读取文件出错,则忽略错误(可能是文件不存在或格式错误)
}
RecordPath.uid ||= Record.uid
RecordPath.paths ||= new Set()
// 如果记录列表不为空,则查找最新记录
if (RecordList.length > 0) {
// 最优解:一次遍历找到最新的记录
let latestRecord = undefined;
// 遍历记录列表查找相同UID且时间戳最大的记录
for (const item of RecordList) {
// 检查当前记录项的UID是否匹配并且是最新记录
if (item.uid === Record.uid &&
// 如果还没有找到记录,或者当前记录的时间戳比已找到的记录更新
(!latestRecord || item.timestamp > latestRecord.timestamp)) {
// 更新最新记录为当前项
latestRecord = item;
}
}
// 如果找到最新记录则更新RecordLast否则保持原有RecordLast的值
RecordLast = latestRecord ? latestRecord : RecordLast;
}
if (RecordLast.uid === Record.uid && Record.data === RecordLast.data) {
// 判断是否为同一天 合并跑过的数据
// 确保 RecordLast 的 Set 属性存在
if (!RecordLast.paths || !(RecordLast.paths instanceof Set)) {
RecordLast.paths = new Set();
}
if (!RecordLast.groupPaths || !(RecordLast.groupPaths instanceof Set)) {
RecordLast.groupPaths = new Set();
}
// 判断是否为同一天 合并跑过的数据
Record.paths = new Set([...Record.paths, ...RecordLast.paths])
Record.groupPaths = new Set([...Record.groupPaths, ...RecordLast.groupPaths])
// 删除RecordLast
const index = RecordList.indexOf(RecordLast);
if (index > -1) {
RecordList.splice(index, 1);
}
}
}
function getAdjustedDate() {
const now = new Date();
// 减去4小时4 * 60 * 60 * 1000 毫秒)
now.setHours(now.getHours() - 4);
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 解析日期字符串
function parseDate(dateString) {
const [year, month, day] = dateString.split('-').map(Number);
return new Date(year, month - 1, day); // month - 1 因为月份从0开始
}
// Record.groupPaths.add({
// name: "",
// paths:new Set()
// })
// const pathingALLSize = []
// 使用函数来添加唯一元素
// 优化后的函数
function addUniquePath(obj, list = PATHING_ALL) {
const existingIndex = list.findIndex(item =>
item.level === obj.level && item.name === obj.name && item.parentName === obj.parentName && item.rootName === obj.rootName
);
if (existingIndex === -1) {
list.push(obj);
} else {
// 合并 child_names 数组,避免重复元素
const existingItem = list[existingIndex];
if (obj.child_names) {
const newChildren = obj.child_names || [];
// 使用 Set 去重并合并数组
const combinedChildren = [...new Set([
...(existingItem.child_names || []),
...newChildren
])];
existingItem.child_names = combinedChildren;
} else {
const newChildren = obj.children || [];
// 使用 Set 去重并合并数组
const combinedChildren = [...new Set([
...(existingItem.children || []),
...newChildren
])];
existingItem.children = combinedChildren;
}
}
}
/**
* 初始化设置函数
* 从配置文件中读取设置信息并返回
* @returns {Object} 返回解析后的JSON设置对象
*/
async function initSettings(prefix = undefined) {
// 默认设置文件路径
let settings_ui = "settings.json";
if (!configSettings) {
try {
// 读取并解析manifest.json文件
manifest = manifest ? manifest : JSON.parse(file.readTextSync(manifest_json));
// 调试日志输出manifest内容
// log.debug("manifest={key}", manifest);
// 调试日志输出manifest中的settings_ui配置
log.debug("settings_ui={key}", manifest.settings_ui);
log.info(`|脚本名称:{name},版本:{version}`, manifest.name, manifest.version);
if (manifest.bgi_version) {
log.info(`|最小可执行BGI版本:{bgi_version}`, manifest.bgi_version);
}
log.info(`|脚本作者:{authors}\n`, manifest.authors.map(a => a.name).join(","));
// 更新settings_ui变量为manifest中指定的路径
settings_ui = manifest.settings_ui
settings_ui = prefix ? prefix + settings_ui : settings_ui
} catch (error) {
// 捕获并记录可能的错误
log.warn("{error}", error.message);
}
}
// 读取并解析设置文件
const settingsJson = JSON.parse(file.readTextSync(settings_ui));
// 如果configSettings未定义则将其设置为解析后的设置对象
if (!configSettings) {
configSettings = settingsJson
}
// 调试日志:输出最终解析的设置对象
log.debug("settingsJson={key}", settingsJson);
// 返回设置对象
return settingsJson
}
/**
* 获取多复选框的映射表
* 该函数会从初始化的设置中提取所有类型为"multi-checkbox"的条目,
* 并将这些条目的名称和对应的选项值存储在一个Map对象中返回
* @returns {Promise<Map>} 返回一个Promise对象解析为包含多复选框配置的Map
*/
async function getMultiCheckboxMap() {
// 如果configSettings存在则使用它否则调用initSettings()函数获取
const settingsJson = await initSettings();
// 创建一个新的Map对象用于存储多复选框的配置
// Map结构为: {名称: 选项数组}
let multiCheckboxMap = new Map([]);
// await debugKey("log-MultiCheckbox-settings.json",JSON.stringify(settingsJson))
// 遍历设置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]) : [];
if (options.length > 0) {
// 记录调试信息,包含名称、标签、选项和选项数量
log.debug("name={key1},label={key2},options={key3},length={key4}", name, label, JSON.stringify(options), options.length);
// 将名称和对应的选项数组存入Map
// multiCheckboxMap.set(name, options);
multiCheckboxMap.set(name, {label: `${label}`, options: options});
}
})
log.debug("multiCheckboxMap={key}", JSON.stringify(Array.from(multiCheckboxMap)))
// 返回包含多复选框配置的Map
return multiCheckboxMap
}
/**
* 根据多选框名称获取对应的JSON数据
* 该函数是一个异步函数,用于从复选框映射表中获取指定名称的值
* @param {string} name - 多选框的名称,用于在映射表中查找对应的值
* @returns {Promise<any>} 返回一个Promise解析后为找到的值如果未找到则返回undefined
*/
async function getJsonByMultiCheckboxName(name) {
// 获取复选框映射表,这是一个异步操作
let multiCheckboxMap = await getMultiCheckboxMap()
// 从映射表中获取并返回指定名称对应的值
return multiCheckboxMap.get(name)
}
/**
* 根据复选框组名称获取对应的值
* 这是一个异步函数,用于从复选框映射中获取指定名称的值
* @param {string} name - 复选框组的名称
* @returns {Promise<any>} 返回一个Promise解析为复选框组对应的值
*/
async function getValueByMultiCheckboxName(name) {
// 获取复选框映射表,这是一个异步操作
let multiCheckboxMap = await getMultiCheckboxMap()
// 从映射表中获取并返回指定名称对应的值
return multiCheckboxMap.get(name).options
}
/**
* 获取字符串中第一个方括号对之间的内容
* @param {string} str - 输入的字符串
* @returns {string} 返回第一个方括号对之间的内容,如果没有找到则返回空字符串
*/
function getBracketContent(str) {
// 查找第一个 [ 和最后一个 ] 的位置
const firstBracketIndex = str.indexOf('[');
const lastBracketIndex = str.lastIndexOf(']');
// 检查是否找到了有效的括号对
if (firstBracketIndex !== -1 && lastBracketIndex !== -1 && firstBracketIndex < lastBracketIndex) {
// 提取第一个 [ 和最后一个 ] 之间的内容
return str.substring(firstBracketIndex + 1, lastBracketIndex);
}
// 如果没有找到有效的括号对,返回空字符串
return '';
}
/**
* 调试按键函数,用于在开发者模式下暂停程序执行并等待特定按键
* @param {string} key - 需要按下的键
* @param {string} path - 调试信息保存的文件路径,默认为"debug.json"
* @param {string} json - 需要写入调试文件的内容,默认为空数组
* @returns {Promise<void>} - 异步函数,没有返回值
*/
async function debugKey(path = "debug.json", json = "", isText = false, key = dev.debug) {
const p = "debug\\"
// 检查是否处于调试模式
if (dev.isDebug) {
if (!isText) {
log.warn("[{0}]正在写出{1}日志", '开发者模式', path)
// 将调试信息同步写入指定文件
file.writeTextSync(`${p}${path}`, json)
log.warn("[{0}]写出完成", '开发者模式')
} else {
log.warn("[{0}]==>{1}", '开发者模式', json)
}
// 输出等待按键的提示信息
log.warn("[{0}]请按下{1}继续执行", '开发者模式', key)
// 等待用户按下指定按键
await keyMousePressStart(key)
}
}
/**
* 监听指定按键的按下和释放事件
* @param {string} key - 需要监听的按键代码
* @param {boolean} enableSkip - 是否允许跳过监听默认为false
* @returns {Promise<Object>} 返回一个Promise对象解析为包含按键状态的对象
* - ok: boolean - 按键是否被完整按下并释放
* - skip: boolean - 是否跳过监听仅在enableSkip为true时有效
*/
async function keyMousePress(key, enableSkip = false) {
let press = {ok: false, skip: false} // 初始化返回对象,记录按键状态
const keyMouseHook = new KeyMouseHook() // 创建按键鼠标钩子实例
let keyDown = false // 记录按键是否被按下
let keyUp = false // 记录按键是否被释放
let down = false // 记录按键按下事件是否触发
let up = false // 记录按键释放事件是否触发
try {
// 注册按键按下事件处理函数
keyMouseHook.OnKeyDown(function (keyCode) {
log.debug("{keyCode}被按下", keyCode)
keyDown = (key === keyCode) // 检查是否是目标按键被按下
down = true // 标记按键按下事件已触发
});
// 注册按键释放事件处理函数
keyMouseHook.OnKeyUp(function (keyCode) {
log.debug("{keyCode}被释放", keyCode)
keyUp = (key === keyCode) // 检查是否是目标按键被释放
up = true // 标记按键释放事件已触发
});
// 循环等待直到按键被按下并释放,或者跳过条件满足
while (true) {
if (enableSkip) {
if (press.ok || press.skip) {
break;
}
} else if (press.ok) {
break;
}
press.ok = keyDown && keyUp //
press.skip = down && up //
await sleep(200) // 每次循环间隔200毫秒
}
return press
} finally {
//脚本结束前,记得释放资源!
keyMouseHook.dispose()
} // 释放按键钩子资源
}
/**
* 异步函数,用于检测特定按键的按下和释放事件
* @param {string|number} key - 需要检测的按键代码
* @returns {Promise<boolean>} 返回一个Promise解析为布尔值表示是否检测到按键的完整按下和释放过程
*/
async function keyMousePressStart(key) {
return (await keyMousePress(key)).ok
}
/**
* 根据索引切换队伍
* @param {number} index - 要切换的队伍在SevenElements数组中的索引
* @returns {Promise<void>}
*/
async function switchTeamByIndex(index, key) {
// 获取指定索引的队伍名称如果索引超出范围或小于0则返回undefined
const teamName = team.SevenElements.length > index && index >= 0 ? team.SevenElements[index] : undefined;
// 检查队伍名称是否有效
if (!teamName || teamName === "") {
// 如果没有设置队伍,记录调试日志并跳过切换
log.debug(`[{mode}] 没有设置队伍: {teamName},跳过切换`, settings.mode, teamName);
} else if (team.current === teamName) {
// 如果当前已经是目标队伍,记录调试日志并跳过切换
log.debug(`[{mode}] 当前队伍为: {teamName},无需切换`, settings.mode, teamName);
} else {
// 如果需要切换队伍,记录信息日志
log.info(`[{mode}] 检测到需要: {key},切换至{val}`, settings.mode, key, teamName);
// 调用切换队伍的工具函数
const teamSwitch = await switchUtil.SwitchPartyMain(teamName);
// 如果切换成功,更新当前队伍
if (teamSwitch) {
team.current = teamSwitch;
}
}
}
async function switchTeamByName(teamName) {
// 检查队伍名称是否有效
if (!teamName || teamName === "") {
// 如果没有设置队伍,记录调试日志并跳过切换
log.debug(`[{mode}] 没有设置队伍: {teamName},跳过切换`, settings.mode, teamName);
} else if (team.current === teamName) {
// 如果当前已经是目标队伍,记录调试日志并跳过切换
log.debug(`[{mode}] 当前队伍为: {teamName},无需切换`, settings.mode, teamName);
} else {
// 如果需要切换队伍,记录信息日志
log.info(`[{mode}] 切换至{val}`, settings.mode, teamName);
// 调用切换队伍的工具函数
const teamSwitch = await switchUtil.SwitchPartyMain(teamName);
// 如果切换成功,更新当前队伍
if (teamSwitch) {
team.current = teamSwitch;
}
}
}
/**
* 执行指定路径的脚本文件
* @param {string} path - 要执行的脚本路径
*/
async function runPath(path, root_name = "", parent_name = "", current_name = "") {
// 参数验证
if (!path || typeof path !== 'string') {
log.warn('无效的路径参数: {path}', path)
return
}
// 检查该路径是否已经在执行中
if (Record.paths.has(path)) {
log.info(`[{mode}] 路径已执行: {path},跳过执行`, settings.mode, path)
return
}
//检查战斗需求
try {
if (!team.fight) {
//自动检测禁用
// if (team.fightKeys.some(item => path.includes(`\\${item}\\`))) {
// team.fight = true
// }
}
} catch (error) {
log.error("检查战斗需求失败: {error}", error.message);
}
//切换队伍
const hoeGroundKey = `${parent_name}->${current_name}`;
const hoeGroundRootKey = `${root_name}->${parent_name}->${current_name}`;
if (team.HoeGroundMap.has(hoeGroundRootKey) || team.HoeGroundMap.has(hoeGroundKey)) {
const hoeGroundName = team.HoeGroundMap.get(hoeGroundKey) || team.HoeGroundMap.get(hoeGroundKey);
await switchTeamByName(hoeGroundName);
} else {
const entry = [...SevenElement.SevenElementsMap.entries()].find(([key, val]) => {
return val.some(item => path.includes(`\\${item}\\`));
});
if (entry) {
const [key, val] = entry;
const index = SevenElement.SevenElements.indexOf(key);
await switchTeamByIndex(index, `路线需要${key}元素`);
} else {
if (path.includes("有草神")) {
const idx = SevenElement.SevenElements.indexOf('草');
await switchTeamByIndex(idx, "路线需要草神");
}else if (team.current !== team.fightName) {
log.info(`[{mode}] 未检测到队伍配置切换至行走位,切换至{teamName}`,settings.mode, team.fightName);
const teamSwitch = await switchUtil.SwitchPartyMain(team.fightName);
if (teamSwitch) {
team.current = teamSwitch;
}
}
//自动检测已禁用
else if (team.fight) {
if (!team.fightName) {
log.error(`[{mode}] 路径需要配置好战斗策略: {path}`, settings.mode, path)
throw new Error(`路径需要配置好战斗策略: ` + path)
} else if (team.current !== team.fightName) {
log.info(`[{mode}] 检测到需要战斗,切换至{teamName}`,settings.mode, team.fightName);
const teamSwitch = await switchUtil.SwitchPartyMain(team.fightName);
if (teamSwitch) {
team.current = teamSwitch;
}
}
}
}
}
//切换队伍-end
log.info("开始执行路径: {path}", path)
await pathingScript.runFile(path)
try {
await sleep(1)
} catch (e) {
throw new Error(e.message)
}
if (team.fight) {
//启用战斗
// await dispatcher.runAutoFightTask(new AutoFightParam());
// await realTimeMissions(false)
// 重置战斗状态
team.fight = false
}
log.debug("路径执行完成: {path}", path)
if (auto.semi && auto.run) {
log.warn(`[{mode}] 路径执行完成: {path}, 请按{key}继续`, settings.mode, path, auto.key)
await keyMousePressStart(auto.key)
}
}
/**
* 执行给定的路径列表
* @param {Array} list - 要执行的路径列表,默认为空数组
* @returns {Promise<void>}
*/
async function runList(list = [], key = "", current_name = "", parent_name = "", group_key = "", group_value = "") {
// 参数验证
if (!Array.isArray(list)) {
log.warn('无效的路径列表参数: {list}', list);
return;
}
if (list.length === 0) {
log.debug('路径列表为空,跳过执行');
return;
}
log.info(`[{mode}] 开始执行 [{0}]组 路径列表,共{count}个路径`, settings.mode, key, list.length);
// 遍历路径列表
for (let i = 0; i < list.length; i++) {
const onePath = list[i];
const path = onePath.path;
if (i === 0) {
log.info(`[{mode}] 开始执行[{1}-{2}]列表`, settings.mode, parent_name, current_name);
}
log.info('任务组[{0}] ' + group_key + ',正在执行第{index}/{total}个路径: {path}', key, group_value, i + 1, list.length, path);
if (auto.semi && auto.skip) {
log.warn(`[{mode}] 按下{key}可跳过{0}执行,如不想跳过请按 空格 或 其他非功能键`, settings.mode, auto.key, path);
const skip = await keyMousePress(auto.key, auto.skip);
if (skip.skip) {
log.warn(`[{mode}] 按下{key}跳过{0}执行`, settings.mode, auto.key, path);
continue
}
}
const now = Date.now();
const value = {timestamp: now, path: path};
try {
// 执行单个路径,并传入停止标识
await runPath(path, onePath.rootName, parent_name, current_name);
} catch (error) {
log.error('执行路径列表中的路径失败: {path}, 错误: {error}', path, error.message);
Record.errorPaths.add(path)
throw new Error(error.message)
// continue; // 继续执行列表中的下一个路径
}
Record.paths.add(path)
RecordPath.paths.add(value)
}
log.debug(`[{mode}] 路径列表执行完成`, settings.mode);
}
/**
* 遍历并执行Map中的任务
* @param {Map} map - 包含任务信息的Map对象默认为新的Map实例
* @returns {Promise<void>} - 异步执行,没有返回值
*/
async function runMap(map = new Map()) {
// 参数验证
if (!(map instanceof Map)) {
log.warn('无效的Map参数: {map}', map);
return;
}
if (map.size === 0) {
log.debug('任务Map为空跳过执行');
return;
}
log.info(`========================================================`)
log.info(`[{mode}] 开始执行任务,共{count}组任务`, settings.mode, map.size);
//打印组执行顺序
let index = 1
for (const [key, one] of map.entries()) {
log.info(`[{mode}] 任务组[{0}] 执行顺序[{1}] 执行路径数[{2}]`, settings.mode, key, index + "/" + map.size, one?.paths?.length || 0);
index++
}
const group_prefix = "任务组执行顺序[{group_key}]"
log.info(`========================================================`)
// 遍历Map中的所有键
index = 0
for (const [key, one] of map.entries()) {
index++
if (one.paths.size <= 0) {
continue
}
const group = {
name: key,
paths: new Set(one.paths)
};
try {
// 记录开始执行任务的日志信息
log.debug(`[{0}] {1}组 开始执行...`, settings.mode, key);
// 执行当前任务关联的路径列表
await runList(one.paths, key, one.current_name, one.parent_name, group_prefix, (index + "/" + map.size));
log.debug(`[{0}] 任务[{1}]执行完成`, settings.mode, key);
} catch (error) {
log.error(`[{0}] 任务[{1}]执行失败: {error}`, settings.mode, key, error.message);
// continue; // 继续执行下一个任务
throw new Error(error.message)
}
Record.groupPaths.add(group)
}
log.debug(`[{mode}] 任务Map执行完成`, settings.mode);
}
/**
* 获取上级文件夹名称(支持多级查找)
* @param {string} path - 完整路径
* @param {number} level - 向上查找的层级默认为1即直接上级
* @returns {string} 指定层级的文件夹名称,如果不存在则返回空字符串
*/
function getParentFolderName(path, level = 1) {
if (!path || typeof path !== 'string' || level < 1) {
return undefined;
}
// 统一处理路径分隔符
const normalizedPath = path.replace(/\\/g, '/');
// 移除末尾的斜杠
const trimmedPath = normalizedPath.replace(/\/$/, '');
// 按斜杠分割路径
const pathParts = trimmedPath.split('/').filter(part => part !== '');
// 检查是否有足够的层级
if (level >= pathParts.length) {
return undefined;
}
// 返回指定层级的上级目录名称
return pathParts[pathParts.length - level - 1];
}
/**
* 从根目录开始获取指定位置的文件夹名称(支持多级查找)
* @param {string} path - 完整路径
* @param {number} level - 从根开始向下的层级默认为1即第一个子目录
* @returns {string} 指定层级的文件夹名称如果不存在则返回undefined
*/
function getChildFolderNameFromRoot(path, level = 1) {
if (!path || typeof path !== 'string' || level < 1) {
return undefined;
}
// 统一处理路径分隔符
const normalizedPath = path.replace(/\\/g, '/');
// 移除开头的斜杠(如果有)
const trimmedPath = normalizedPath.replace(/^\/+/, '');
// 按斜杠分割路径
const pathParts = trimmedPath.split('/').filter(part => part !== '');
// 检查是否有足够的层级
if (level > pathParts.length) {
return undefined;
}
// 返回从根开始指定层级的目录名称level - 1 是因为数组索引从0开始
return pathParts[level - 1];
}
/**
* 按层级对列表项进行分组
* @param {Array} list - 包含层级信息的列表项数组
* @returns {Array} 返回一个嵌套数组,每个子数组包含对应层级的所有项
*/
function groupByLevel(list) {
// 找出最大层级数
const maxLevel = Math.max(...list.map(item => item.level));
// 创建嵌套数组结构
const result = [];
// 按层级分组
for (let level = 0; level <= maxLevel; level++) {
const levelItems = list.filter(item => item.level === level);
result.push(levelItems);
}
return result;
}
/**
* 递归读取指定路径下的文件和文件夹,构建树形结构
* @param {string} path - 要读取的初始路径
* @param {number} level - 当前层级(从 0 开始),默认为 0
* @param {string} parentId - 父节点的 id完整路径根节点为 null
* @param {string[]} fullPathNames - 从根到当前节点的名称路径数组
* @param {string} isFileKey - 目标文件类型的后缀名,默认为 ".json"
* @param {boolean} treeStructure - 是否返回树状结构true: 树 / false: 只收集文件平铺)
* @returns {Promise<Array>} 树形或平铺的文件/文件夹结构数组
*/
async function readPaths(
path,
level = 0,
parentId = null,
fullPathNames = [],
isFileKey = ".json",
treeStructure = true
) {
const treeList = [];
// 获取当前路径下所有文件/文件夹
let pathSyncList = file.readPathSync(path);
if (!Array.isArray(pathSyncList)) {
pathSyncList = [...pathSyncList]
}
// log.error(JSON.stringify(...pathSyncList))
// log.error(JSON.stringify(Array.isArray(pathSyncList)))
//
// 可选:按名称排序,保证同层顺序可预测(字母序或自定义规则)
pathSyncList.sort((a, b) => a.localeCompare(b));
for (const itemPath of pathSyncList) {
const currentName = getFileOrFolderName(itemPath); // 请确保你有这个辅助函数,取路径最后一段
const currentId = itemPath; // 使用完整路径作为全局唯一 id最可靠
const currentFullPathNames = [...fullPathNames, currentName];
if (itemPath.endsWith(isFileKey)) {
// ── 是目标文件 ───────────────────────────────
let displayName;
let parentDisplayName;
// 你原有的复杂名称处理逻辑(可继续保留或简化)
const parentFolder = getParentFolderName(itemPath);
if (!parentFolder) {
throw new Error(`${itemPath} 没有上级目录`);
}
if (itemPath.includes("@")) {
displayName = getParentFolderName(itemPath, 2);
parentDisplayName = getParentFolderName(itemPath, 3);
} else {
displayName = parentFolder;
parentDisplayName = getParentFolderName(itemPath, 2);
}
const fileNode = {
id: currentId,
name: displayName || currentName,
parentName: parentDisplayName,
parentId: parentId,
path: itemPath,
level: level + 1,
fullPathNames: currentFullPathNames,
levelName: "",
isRoot: false,
isFile: true,
children: [] // 文件没有子节点
};
treeList.push(fileNode);
} else if (file.IsFolder(itemPath)) {
// ── 是文件夹 ───────────────────────────────
const childNodes = await readPaths(
itemPath,
level + 1,
currentId, // 把当前路径作为子节点的 parentId
currentFullPathNames, // 传递路径栈
isFileKey,
treeStructure
);
if (treeStructure) {
// 树形结构:保留文件夹节点
const folderNode = {
id: currentId,
name: currentName,
parentName: fullPathNames.length > 0 ? fullPathNames[fullPathNames.length - 1] : undefined,
parentId: parentId,
path: itemPath,
level: level + 1,
fullPathNames: currentFullPathNames,
levelName: "",
isRoot: level === 0,
isFile: false,
children: childNodes
};
treeList.push(folderNode);
} else {
// 非树形:只收集文件,文件夹本身不保留
treeList.push(...childNodes);
}
}
}
return treeList;
}
// 辅助函数示例(请根据你的实际实现替换)
function getFileOrFolderName(fullPath) {
// 返回路径最后一段名称
const parts = fullPath.split(/[\\/]/);
return parts[parts.length - 1];
}
function getParentFolderName(fullPath, upLevels = 1) {
// 返回上 N 级的文件夹名称
const parts = fullPath.split(/[\\/]/).filter(Boolean);
if (parts.length <= upLevels) return undefined;
const targetIndex = parts.length - 1 - upLevels;
return parts[targetIndex] || undefined;
}
async function treeToList(treeList = []) {
let list = []
for (const element of treeList) {
// 如果是文件,添加到结果列表
// if (element.isFile) {
list.push(element);
// }
const child = element.children
if (child && child.length > 0) {
list.push(...await treeToList(child))
// list = [...list, ...await treeToList(child)]
}
}
return list
}