mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-25 04:59:52 +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 配置文件支持
This commit is contained in:
12
repo/js/FullyAutoAndSemiAutoTools/SymLink.bat
Normal file
12
repo/js/FullyAutoAndSemiAutoTools/SymLink.bat
Normal 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
|
||||
)
|
||||
BIN
repo/js/FullyAutoAndSemiAutoTools/assets/paimon_menu.png
Normal file
BIN
repo/js/FullyAutoAndSemiAutoTools/assets/paimon_menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
757
repo/js/FullyAutoAndSemiAutoTools/main.js
Normal file
757
repo/js/FullyAutoAndSemiAutoTools/main.js
Normal 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)
|
||||
}
|
||||
17
repo/js/FullyAutoAndSemiAutoTools/manifest.json
Normal file
17
repo/js/FullyAutoAndSemiAutoTools/manifest.json
Normal 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"
|
||||
}
|
||||
1
repo/js/FullyAutoAndSemiAutoTools/pathing.json
Normal file
1
repo/js/FullyAutoAndSemiAutoTools/pathing.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
34
repo/js/FullyAutoAndSemiAutoTools/settings.json
Normal file
34
repo/js/FullyAutoAndSemiAutoTools/settings.json
Normal 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的快捷键请勿冲突)"
|
||||
}
|
||||
|
||||
]
|
||||
142
repo/js/FullyAutoAndSemiAutoTools/utils/uid.js
Normal file
142
repo/js/FullyAutoAndSemiAutoTools/utils/uid.js
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user