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 配置文件支持
This commit is contained in:
yan
2026-01-10 05:08:20 +08:00
parent f173f7ac55
commit de41ac53ea
7 changed files with 963 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
@REM @echo off
set "target1=..\..\AutoPathing"
set "target2=..\..\pathing"
if exist "%target1%" (
mklink /j pathing "%target1%"
) else if exist "%target2%" (
mklink /j pathing "%target2%"
) else (
echo ERROR: Can't find folder "%target1%" or "%target2%"
pause
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,757 @@
let manifest_json = "manifest.json";
let manifest = undefined
let configSettings = undefined
let AUTO_STOP = undefined
let debug = undefined
let isDebug = false
let SEMI_AUTO = false
const pathingName = "pathing"
// const pathAsMap = new Map([])
// const pathRunMap = new Map([])
const needRunMap = new Map([])
const PATHING_ALL = new Array({level: 0, name: `${pathingName}`, parent_name: "", child_names: []})
const settingsNameList = new Array()
const settingsNameAsList = new Array()
let PATH_JSON_LIST = new Array()
let RecordList = new Array()
let RecordLast = {
name: "",
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(), // 记录分组路径
}
/**
* 初始化记录函数
* 该函数用于初始化一条新的记录包括设置UID、时间戳和调整后的日期数据
* 同时会检查记录列表中是否存在相同UID的最新记录并进行更新
*/
async function initRecord() {
// 设置记录的唯一标识符通过OCR技术获取
Record.uid = await uidUtil.ocrUID()
// 设置记录的时间戳为当前时间
Record.timestamp = Date.now()
// 获取并设置调整后的日期数据
Record.data = getAdjustedDate()
// 如果记录列表不为空,则查找最新记录
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) {
// 判断是否为同一天 合并跑过的数据
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) {
const existingIndex = PATHING_ALL.findIndex(item =>
item.level === obj.level && item.name === obj.name
);
if (existingIndex === -1) {
PATHING_ALL.push(obj);
} else {
// 合并 child_names 数组,避免重复元素
const existingItem = PATHING_ALL[existingIndex];
const newChildren = obj.child_names || [];
// 使用 Set 去重并合并数组
const combinedChildren = [...new Set([
...(existingItem.child_names || []),
...newChildren
])];
existingItem.child_names = combinedChildren;
}
}
/**
* 初始化设置函数
* 从配置文件中读取设置信息并返回
* @returns {Object} 返回解析后的JSON设置对象
*/
async function initSettings() {
// 默认设置文件路径
let settings_ui = "settings.json";
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
} 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 = configSettings ? configSettings : await initSettings();
// 创建一个新的Map对象用于存储多复选框的配置
// Map结构为: {名称: 选项数组}
let multiCheckboxMap = new Map();
// 遍历设置JSON中的每个条目
settingsJson.forEach((entry) => {
// 如果条目没有name属性或者类型不是"multi-checkbox",则跳过该条目
if (!entry.name || entry.type !== "multi-checkbox") return;
// 解构条目中的name和label属性便于后续使用
const {name, label} = entry;
// 获取当前name对应的设置值如果存在则转换为数组否则使用空数组
const options = settings[name] ? Array.from(settings[name]) : [];
// 记录调试信息,包含名称、标签、选项和选项数量
log.debug("name={key1},label={key2},options={key3},length={key4}", name, label, JSON.stringify(options), options.length);
// 将名称和对应的选项数组存入Map
multiCheckboxMap.set(name, options);
})
// 返回包含多复选框配置的Map
return multiCheckboxMap
}
/**
* 根据复选框组名称获取对应的值
* 这是一个异步函数,用于从复选框映射中获取指定名称的值
* @param {string} name - 复选框组的名称
* @returns {Promise<any>} 返回一个Promise解析为复选框组对应的值
*/
async function getValueByMultiCheckboxName(name) {
// 获取复选框映射表,这是一个异步操作
let multiCheckboxMap = await getMultiCheckboxMap()
// 从映射表中获取并返回指定名称对应的值
return multiCheckboxMap.get(name)
}
async function init() {
let settingsConfig = await initSettings();
let utils = [
"uid",
]
for (let util of utils) {
eval(file.readTextSync(`utils/${util}.js`));
}
if (manifest.key !== settings.key) {
throw new Error("密钥不匹配")
}
AUTO_STOP = (AUTO_STOP) ? AUTO_STOP : settings.autoStop
debug = (debug) ? debug : settings.debug
isDebug = settings.isDebug
SEMI_AUTO = settings.mode === settings.mode
if (SEMI_AUTO && !AUTO_STOP) {
throw new Error("半自动模式下必须开启自动停止")
}
if (!file.IsFolder(`${pathingName}`)) {
let batFile = "SymLink.bat";
log.error("{0}文件夹不存在请在BetterGI中右键点击本脚本选择{1}。然后双击脚本目录下的{2}文件以创建文件夹链接", `${pathingName}`, "打开所在目录", batFile);
return false;
}
//刷新settings
if (true) {
let level = 0
let levelName = "treeLevel"
// 获取当前路径下的所有文件/文件夹
let pathSyncList = file.readPathSync(`${PATHING_ALL[level].name}`);
log.debug("{0}文件夹下有{1}个文件/文件夹", `${pathingName}`, pathSyncList.length);
let settingsList = settingsConfig
let parentJson = {
name: `${levelName}_${level}_${level}`,
type: "multi-checkbox",
label: `选择要执行的${level + 1}级路径`,
options: []
}
for (const element of pathSyncList) {
log.warn("element={0}", element)
parentJson.options.push(element.replace(`${pathingName}\\`, ""))
}
await addUniquePath({level: level, name: `${pathingName}`, parent_name: '', child_names: parentJson.options})
let treePathList = await readPaths(`${pathingName}`)
await debugKey('log-treePathList.json', JSON.stringify(treePathList))
let pathJsonList = await treeToList(treePathList)
PATH_JSON_LIST = pathJsonList
for (const element of pathJsonList) {
const pathRun = element.path
const level_parent_name = getChildFolderNameFromRoot(pathRun, level + 1);
const level1_name = getChildFolderNameFromRoot(pathRun, level + 1 + 1);
let level2_name = getChildFolderNameFromRoot(pathRun, level + 2 + 1);
let level3_name = getChildFolderNameFromRoot(pathRun, level + 3 + 1);
if (level2_name.endsWith(".json")) {
level2_name = undefined
}
if (level3_name.endsWith(".json")) {
level3_name = undefined
}
//存储 2 级
await addUniquePath({
level: level + 1,
name: level1_name,
parent_name: level_parent_name,
child_names: level2_name ? [level2_name] : []
})
await addUniquePath({
level: level + 2,
name: level2_name,
parent_name: level1_name,
child_names: level3_name ? [level3_name] : []
})
}
// 正确的排序方式
PATHING_ALL.sort((a, b) => {
// 首先按 level 排序
if (a.level !== b.level) {
return a.level - b.level;
}
if (a.parent_name !== b.parent_name) {
return a.parent_name.localeCompare(b.parent_name);
}
// level 相同时按 name 排序
return a.name.localeCompare(b.name);
});
await debugKey('log-PATHING_ALL.json', JSON.stringify(PATHING_ALL))
const groupLevel = groupByLevel(PATHING_ALL);
const initLength = settingsList.length
let parentNameLast = undefined
let parentNameNow = undefined
const line = 30
const br = `${"=".repeat(line)}\n`
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 = "\n"
if (item.parent_name !== parentNameLast) {
parentNameLast = item.parent_name;
let b = (line - item.parent_name.length) % 2 === 0;
const localLine = b ? ((line - item.parent_name.length) / 2) : (Math.ceil((line - item.parent_name.length) / 2))
prefix = br + `${"=".repeat(localLine)}${item.parent_name}${"=".repeat(localLine)}\n` + br
}
const p = initLength === settingsList.length ? "【地图追踪】\n" : `${prefix}[${item.name}]\n`
let leveJson = {
name: `${name}`,
type: "multi-checkbox",
label: `${p}选择要执行的${item.level + 1}级路径`,
options: []
}
// 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)
settingsList.push(leveJson)
i++
}
})
}
)
level++
settingsList.filter(
item => item.name === 'key'
).forEach(item => {
// 刷新settings自动设置密钥
item.default = manifest.key
})
file.writeTextSync(manifest.settings_ui, JSON.stringify(settingsList))
}
// 初始化needRunMap
if (true) {
// for (let key of pathAsMap.keys()) {
// const multiCheckbox = await getValueByMultiCheckboxName(pathAsMap.get(key));
// needRunMap.set(key, multiCheckbox)
// }
for (const settingsName of settingsNameList) {
const multi = await getValueByMultiCheckboxName(settingsName);
const settingsAsName = settingsNameAsList.find(item => item.settings_name === settingsName)
needRunMap.set(settingsAsName.settings_as_name, {
paths: (PATH_JSON_LIST.filter(item =>
multi.some(element => item.path.includes(element))
).map(item => item.path)),
as_name: settingsAsName.settings_as_name,
name: settingsAsName.settings_name
})
}
}
// 启用自动拾取的实时任务,并配置成启用急速拾取模式
dispatcher.addTrigger(new RealtimeTimer("AutoPick", {"forceInteraction": true}));
return true
}
/**
* 调试按键函数,用于在开发者模式下暂停程序执行并等待特定按键
* @param {string} key - 需要按下的键
* @param {string} path - 调试信息保存的文件路径,默认为"debug.json"
* @param {string} json - 需要写入调试文件的内容,默认为空数组
* @returns {Promise<void>} - 异步函数,没有返回值
*/
async function debugKey(path = "debug.json", json = "", key = debug) {
const p = "debug\\"
// 检查是否处于调试模式
if (isDebug) {
log.warn("[{0}]正在写出{1}日志", '开发者模式', path)
// 将调试信息同步写入指定文件
file.writeTextSync(`${p}${path}`, json)
log.warn("[{0}]写出完成", '开发者模式')
// 输出等待按键的提示信息
log.warn("[{0}]请按下{1}继续执行", '开发者模式', key)
// 等待用户按下指定按键
await keyMousePress(key)
}
}
/**
* 异步函数,用于检测特定按键的按下和释放事件
* @param {string|number} key - 需要检测的按键代码
* @returns {Promise<boolean>} 返回一个Promise解析为布尔值表示是否检测到按键的完整按下和释放过程
*/
async function keyMousePress(key) {
let press = false
// 需手动初始化 keyMouseHook
const keyMouseHook = new KeyMouseHook()
let keyDown = false // 标记按键是否被按下
let keyUp = false // 标记按键是否被释放
try {
// 注册按键按下事件处理函数
keyMouseHook.OnKeyDown(function (keyCode) {
log.debug("{keyCode}被按下", keyCode)
keyDown = (key === keyCode) // 检查按下的键是否与目标键匹配
});
// 注册按键释放事件处理函数
keyMouseHook.OnKeyUp(function (keyCode) {
log.debug("{keyCode}被释放", keyCode)
keyUp = (key === keyCode) // 检查释放的键是否与目标键匹配
});
// 循环检测按键状态,直到按键被按下并释放
while (!press) {
press = keyDown && keyUp // 只有当按键既被按下又被释放时press才为true
await sleep(200) // 每次循环间隔200毫秒
}
return press
} finally {
//脚本结束前,记得释放资源!
keyMouseHook.dispose()
} // 释放按键钩子资源
}
/**
* 执行指定路径的脚本文件
* @param {string} path - 要执行的脚本路径
* @param {string} [stopKey=AUTO_STOP] - 用于暂停执行的按键默认为AUTO_STOP
*/
async function runPath(path, stopKey = AUTO_STOP) {
// 参数验证
if (!path || typeof path !== 'string') {
log.warn('无效的路径参数: {path}', path)
return
}
// 检查该路径是否已经在执行中
if (Record.paths.has(path)) {
log.info(`[{mode}] 路径已执行: {path},跳过执行`, settings.mode, path)
return
}
try {
log.debug("开始执行路径: {path}", path)
await pathingScript.runFile(path)
log.debug("路径执行完成: {path}", path)
Record.paths.add(path)
Record.errorPaths.delete(path)
} catch (error) {
Record.errorPaths.add(path)
log.error("路径执行失败: {path}, 错误: {error}", path, error.message)
}
if (SEMI_AUTO) {
log.warn(`[{mode}] 路径执行完成: {path}, 请按{key}继续`, settings.mode, path, stopKey)
await keyMousePress(stopKey)
}
}
/**
* 执行给定的路径列表
* @param {Array} list - 要执行的路径列表,默认为空数组
* @param {string} stopKey - 停止标识默认为AUTO_STOP
* @returns {Promise<void>}
*/
async function runList(list = [], stopKey = AUTO_STOP) {
// 参数验证
if (!Array.isArray(list)) {
log.warn('无效的路径列表参数: {list}', list);
return;
}
if (list.length === 0) {
log.debug('路径列表为空,跳过执行');
return;
}
log.debug(`[{mode}] 开始执行路径列表,共{count}个路径`, settings.mode, list.length);
// 遍历路径列表
for (let i = 0; i < list.length; i++) {
const path = list[i];
log.debug('正在执行第{index}/{total}个路径: {path}', i + 1, list.length, path);
try {
// 执行单个路径,并传入停止标识
await runPath(path, stopKey);
} catch (error) {
log.error('执行路径列表中的路径失败: {path}, 错误: {error}', path, error.message);
continue; // 继续执行列表中的下一个路径
}
}
log.debug(`[{mode}] 路径列表执行完成`, settings.mode);
}
/**
* 遍历并执行Map中的任务
* @param {Map} map - 包含任务信息的Map对象默认为新的Map实例
* @param {string} stopKey - 自动停止的标识键默认为AUTO_STOP
* @returns {Promise<void>} - 异步执行,没有返回值
*/
async function runMap(map = new Map(), stopKey = AUTO_STOP) {
// 参数验证
if (!(map instanceof Map)) {
log.warn('无效的Map参数: {map}', map);
return;
}
if (map.size === 0) {
log.debug('任务Map为空跳过执行');
return;
}
log.info(`[{mode}] 开始执行任务Map共{count}个任务`, settings.mode, map.size);
// 遍历Map中的所有键
for (const [key, one] of map.entries()) {
try {
// 记录开始执行任务的日志信息
log.info(`[{0}] 开始执行[{1}]...`, settings.mode, one.as_name);
// 执行当前任务关联的路径列表
await runList(one.paths, stopKey);
Record.groupPaths.add({
name: one.as_name,
paths: new Set(one.paths)
})
log.debug(`[{0}] 任务[{1}]执行完成`, settings.mode, one.as_name);
} catch (error) {
log.error(`[{0}] 任务[{1}]执行失败: {error}`, settings.mode, one.as_name, error.message);
continue; // 继续执行下一个任务
}
}
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} index - 当前层级的索引默认为0
* @param {string} isFileKey - 目标文件类型的后缀名,默认为".json"
* @param {boolean} treeStructure - 是否使用树状结构返回默认为true
* @returns {Promise<Array>} 返回包含文件和文件夹结构的数组
*/
async function readPaths(path, index = 0, isFileKey = ".json", treeStructure = true) {
let treeList = []; // 用于存储当前层级的文件和文件夹结构
// 获取当前路径下的所有文件/文件夹
let pathSyncList = file.readPathSync(path);
// 遍历当前路径下的所有文件和文件夹
for (const pathSync of pathSyncList) {
// 如果是目标文件类型(默认为.json
if (pathSync.endsWith(isFileKey)) {
// 如果是目标文件类型,添加到列表
let name = undefined;
let parentName = undefined;
// let path_let = pathSync;
let parentFolder = getParentFolderName(pathSync)
if (!parentFolder) {
throw new Error(`${pathSync}没有上级目录`)
}
// 获取父级目录路径(去除文件名)
if (parentFolder.includes("@")) {
// 包含@符号的情况:取@符号前的上级目录名
// let first = path_let.split("@")[0];
// first = first.substring(0, first.lastIndexOf("\\"));
name = getParentFolderName(pathSync, 2);
parentName = getParentFolderName(pathSync, 3);
}
// else if (pathSync.includes("挪德卡莱锄地小怪")) {
// // 特殊处理
// let first_te = path_let.split("挪德卡莱锄地小怪")[0];
// first_te = first_te.substring(0, first_te.lastIndexOf("\\"));
// name = first_te.substring(first_te.lastIndexOf("\\"), first_te.length);
// }
else {
name = parentFolder;
parentName = getParentFolderName(pathSync, 2);
}
// 根据 treeStructure 参数决定是否创建完整对象
if (treeStructure) {
treeList.push({
name: name,
parentName: parentName,
path: pathSync,
index: index + 1,
isRoot: false,
isFile: true,
child: []
});
} else {
// 如果不需要树状结构,只添加基本文件信息
treeList.push({
name: name,
path: pathSync,
isFile: true
});
}
} else if (file.IsFolder(pathSync)) {
// 如果是文件夹,根据 treeStructure 参数决定如何处理
if (treeStructure) {
// 如果需要树状结构,递归处理并保留文件夹信息
const childTreeList = await readPaths(pathSync, index + 1, isFileKey, treeStructure);
treeList.push({
name: undefined,
parentName: undefined,
path: pathSync,
index: index + 1,
isRoot: false,
isFile: false,
child: childTreeList
});
} else {
// 如果不需要树状结构,直接递归遍历子文件夹,只收集文件
const childTreeList = await readPaths(pathSync, index + 1, isFileKey, treeStructure);
treeList = treeList.concat(childTreeList); // 将子文件夹中的文件直接合并到当前列表
}
}
}
return treeList;
}
async function treeToList(treeList = []) {
let list = []
for (const element of treeList) {
const child = element.child
if (child && child.length > 0) {
list = list.concat(await treeToList(child))
}
// 如果是文件,添加到结果列表
if (element.isFile) {
list.push(element);
}
}
return list
}
(async function () {
let RecordText = "Record\\record.json"
try {
RecordList = JSON.parse(file.readTextSync(RecordText))
} catch (e) {
}
try {
if (await init()) {
//记录初始化
await initRecord();
await main()
}
} finally {
RecordList.push(Record)
file.writeTextSync(RecordText, JSON.stringify(RecordList))
}
})()
async function main() {
// await runMap(needRunMap)
// for (let key of needRunMap.keys()) {
// await runList(needRunMap.get(key))
// }
if (RecordLast.groupPaths.size > 0 && RecordLast.paths.size !== RecordLast.groupPaths.size) {
//优先跑上次群组没跑过的
}
log.info(`[{mode}] path==>{path},请按下{key}以继续执行[${manifest.name} JS]`, settings.mode, "path", AUTO_STOP)
await keyMousePress(AUTO_STOP);
log.info(`[{mode}] path==>{path},请按下{key}以继续执行[${manifest.name} JS]`, settings.mode, "path", AUTO_STOP)
}

View File

@@ -0,0 +1,17 @@
{
"manifest_version": 1,
"name": "全自动或半自动工具箱",
"version": "0.0.1",
"bgi_version": "0.44.8",
"key": " PGCSBY37NJ ",
"description": "",
"saved_files": [],
"authors": [
{
"name": "云端客",
"links": "https://github.com/Kirito520Asuna"
}
],
"settings_ui": "settings.json",
"main": "main.js"
}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,34 @@
[
{
"name": "key",
"type": "input-text",
"label": "密钥",
"default": ""
},
{
"name": "isDebug",
"type": "checkbox",
"label": "开发者模式",
"default": false
},
{
"name": "debug",
"type": "input-text",
"label": "调试快捷键(开发者)"
},
{
"name": "mode",
"type": "select",
"label": "模式",
"options": [
"全自动","半自动"
],
"default": "全自动"
},
{
"name": "autoStop",
"type": "input-text",
"label": "自动暂停快捷键(独立BGI的快捷键请勿冲突)"
}
]

View File

@@ -0,0 +1,142 @@
const commonPath = 'Assets/RecognitionObject/'
const commonMap = new Map([
['main_ui', {
path: `${commonPath}`,
name: 'paimon_menu',
type: '.png',
}],
])
const genshinJson = {
width: 1920,//genshin.width,
height: 1080,//genshin.height,
}
/**
* 根据键值获取JSON路径
* @param {string} key - 要查找的键值
* @returns {any} 返回与键值对应的JSON路径值
*/
function getJsonPath(key) {
return commonMap.get(key); // 通过commonMap的get方法获取指定键对应的值
}
function saveOnlyNumber(str) {
str = str ? str : '';
// 使用正则表达式匹配字符串中的所有数字
// \d+ 匹配一个或多个数字
// .join('') 将匹配到的数字数组连接成一个字符串
// parseInt 将连接后的字符串转换为整数
return parseInt(str.match(/\d+/g).join(''));
}
async function ocrUID() {
let uid_json = {
x: 1683,
y: 1051,
width: 234,
height: 28,
}
let recognitionObjectOcr = RecognitionObject.Ocr(uid_json.x, uid_json.y, uid_json.width, uid_json.height);
let region3 = captureGameRegion()
let res = region3.find(recognitionObjectOcr);
log.debug(`[OCR识别UID]识别结果: ${res.text}, 原始坐标: x=${res.x}, y=${res.y},width:${res.width},height:${res.height}`);
//只保留数字
let uid
try {
uid = saveOnlyNumber(res.text)
} catch (e) {
log.warn(`UID未设置`)
uid = 0
} finally {
region3.dispose()
}
log.debug(`[OCR识别UID]识别结果: {uid}`, uid);
return uid
}
// 判断是否在主界面的函数
const isInMainUI = () => {
// let name = '主界面'
let main_ui = getJsonPath('main_ui');
// 定义识别对象
let paimonMenuRo = RecognitionObject.TemplateMatch(
file.ReadImageMatSync(`${main_ui.path}${main_ui.name}${main_ui.type}`),
0,
0,
genshinJson.width / 3.0,
genshinJson.width / 5.0
);
let captureRegion = captureGameRegion();
let res = captureRegion.find(paimonMenuRo);
captureRegion.Dispose()
return !res.isEmpty();
};
async function toMainUi() {
let ms = 1000
let index = 1
await sleep(ms);
while (!isInMainUI()) {
await sleep(ms);
await genshin.returnMainUi(); // 如果未启用,则返回游戏主界面
await sleep(ms);
if (index > 3) {
throw new Error(`多次尝试返回主界面失败`);
}
index += 1
}
}
async function compareUid(UID = settings.uid) {
let uid = await ocrUID()
let setUid = 0
try {
setUid = saveOnlyNumber(UID)
} catch (e) {
// log.warn(`UID未设置`)
}
let compare = uid === setUid
if (compare) {
log.info(`[OCR识别UID]识别结果: {uid} 与设置UID相同`, uid);
}
return compare
}
async function checkUid() {
let reJson = {
inMainUI: false,
isUid: false
}
if (isInMainUI()) {
reJson.isUid = await compareUid()
}
return reJson
}
async function check() {
let check = false
if (settings.uid) {
try {
await toMainUi();
} catch (e) {
log.warn("多次尝试返回主界面失败")
}
let checkJson = await checkUid()
if ((!checkJson.inMainUI) && (!checkJson.isUid)) {
//尝试直接识别
checkJson.isUid = await compareUid()
}
check = checkJson.isUid
}
return check
}
this.uidUtil = {
toMainUi,
isInMainUI,
checkUid,
ocrUID,
check,
compareUid,
}