mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-16 03:33:25 +08:00
* 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 commit7995c67419. * 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 commitcfa86be643. * 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数据加载失败时添加错误日志 - 在团队数据加载失败时添加错误日志 - 在路径排序配置加载失败时添加错误日志 - 在顺序映射加载失败时添加错误日志 - 在组限制配置加载失败时添加错误日志
2216 lines
87 KiB
JavaScript
2216 lines
87 KiB
JavaScript
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_LIST,uid={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_LIST,uid={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 是 path,value 是完整的 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
|
||
}
|
||
|