From 44230d93e31aa9a23c2884bc9c37451fd1830c61 Mon Sep 17 00:00:00 2001 From: yan Date: Sun, 11 Jan 2026 12:32:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(auto-tools):=20=E6=B7=BB=E5=8A=A0=E6=8B=A9?= =?UTF-8?q?=E4=BC=98=E6=A8=A1=E5=BC=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改路径列表处理逻辑,为每个路径对象添加选中状态字段 - 更新执行日志显示,添加模式和选中路径信息输出 - 实现择优运行函数,优先执行上次未完成的任务路径 - 添加choose_best配置选项,支持择优模式开关设置 - 优化记录检查逻辑,提升路径匹配性能 - 整理代码结构,将择优运行逻辑封装为独立函数 feat(auto-tools): 添加黑白名单配置功能 - 在设置中添加白名单和黑名单输入框配置 - 新增 config_list 对象存储黑白名单数组 - 实现从设置读取黑白名单并解析为数组 - 添加路径过滤逻辑,支持黑名单排除和白名单优先 - 实现黑白名单预处理,去除空字符串并trim - 优化路径遍历,跳过黑名单中的路径 fix(FullyAutoAndSemiAutoTools): 修复空映射时的执行逻辑并移除调试代码 - 添加了 needRunMap 非空检查避免无效执行 - 移除了调试用的日志记录和按键等待功能 - 优化了代码流程控制结构 feat(auto-tools): 添加记录管理和用户配置功能 - 定义记录文件路径并实现saveRecord函数保存记录到JSON文件 - 在initRecord函数中添加读取记录文件逻辑,支持Set数据类型的转换 - 实现同一天记录数据合并功能,确保Set属性存在 - 添加用户配置映射管理,支持不同UID的配置存储 - 实现配置模式选择功能,支持刷新和加载两种模式 - 添加错误处理机制确保文件操作的安全性 - 优化配置文件的读写流程,提升数据持久化可靠性 --- repo/js/FullyAutoAndSemiAutoTools/main.js | 234 ++++++++++++++---- .../FullyAutoAndSemiAutoTools/settings.json | 27 ++ 2 files changed, 219 insertions(+), 42 deletions(-) diff --git a/repo/js/FullyAutoAndSemiAutoTools/main.js b/repo/js/FullyAutoAndSemiAutoTools/main.js index 6ff38f868..a82904bb2 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/main.js +++ b/repo/js/FullyAutoAndSemiAutoTools/main.js @@ -10,9 +10,11 @@ const pathingName = "pathing" // 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() +let settingsNameList = new Array() const settingsNameAsList = new Array() let PATH_JSON_LIST = new Array() +// 定义记录文件的路径 +let RecordText = "Record\\record.json" let RecordList = new Array() let RecordLast = { name: "", @@ -30,6 +32,38 @@ const Record = { errorPaths: new Set(), // 记录错误路径 groupPaths: new Set(), // 记录分组路径 } +const config_list = { + black: [], + white: [], +} + +/** + * 保存当前记录到记录列表并同步到文件 + * 该函数在保存前会将Set类型的数据转换为数组格式,确保JSON序列化正常进行 + */ +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(RecordText, JSON.stringify(RecordList)) +} /** * 初始化记录函数 @@ -43,6 +77,28 @@ async function initRecord() { Record.timestamp = Date.now() // 获取并设置调整后的日期数据 Record.data = getAdjustedDate() + + try { + // 尝试读取记录文件 + // 读取后将数组转换回 Set,处理特殊的数据结构 + RecordList = JSON.parse(file.readTextSync(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; + }); + } catch (e) { + // 如果读取文件出错,则忽略错误(可能是文件不存在或格式错误) + } + // 如果记录列表不为空,则查找最新记录 if (RecordList.length > 0) { // 最优解:一次遍历找到最新的记录 @@ -61,6 +117,15 @@ async function initRecord() { 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]) @@ -214,6 +279,8 @@ async function init() { debug = (debug) ? debug : settings.debug isDebug = settings.isDebug SEMI_AUTO = settings.mode === settings.mode + 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 (SEMI_AUTO && !AUTO_STOP) { throw new Error("半自动模式下必须开启自动停止") } @@ -223,10 +290,24 @@ async function init() { log.error("{0}文件夹不存在,请在BetterGI中右键点击本脚本,选择{1}。然后双击脚本目录下的{2}文件以创建文件夹链接", `${pathingName}`, "打开所在目录", batFile); return false; } - //刷新settings - if (true) { + //记录初始化 + await initRecord(); + + // 读取现有配置并合并 + let uidSettingsMap = new Map() + const uidSettingsJson = "settings/uid.json"; + try { + const existingData = JSON.parse(file.readTextSync(uidSettingsJson)) + uidSettingsMap = new Map(existingData) + } catch (e) { + // 文件不存在时使用空Map + log.debug("配置文件不存在,将创建新的"); + } + let levelName = "treeLevel" + + async function refreshALL() { let level = 0 - let levelName = "treeLevel" + // 获取当前路径下的所有文件/文件夹 let pathSyncList = file.readPathSync(`${PATHING_ALL[level].name}`); log.debug("{0}文件夹下有{1}个文件/文件夹", `${pathingName}`, pathSyncList.length); @@ -238,7 +319,7 @@ async function init() { options: [] } for (const element of pathSyncList) { - log.warn("element={0}", element) + // log.warn("element={0}", element) parentJson.options.push(element.replace(`${pathingName}\\`, "")) } await addUniquePath({level: level, name: `${pathingName}`, parent_name: '', child_names: parentJson.options}) @@ -247,8 +328,28 @@ async function init() { await debugKey('log-treePathList.json', JSON.stringify(treePathList)) let pathJsonList = await treeToList(treePathList) PATH_JSON_LIST = pathJsonList + + // 预处理黑白名单数组,移除空字符串并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 !== ""); + for (const element of pathJsonList) { const pathRun = element.path + + // 检查路径是否被允许 + const isBlacklisted = processedBlackList.some(item => pathRun.includes(item)); + const isWhitelisted = processedWhiteList.some(item => pathRun.includes(item)); + + if (isBlacklisted && !isWhitelisted) { + continue; + } + + const level_parent_name = getChildFolderNameFromRoot(pathRun, level + 1); const level1_name = getChildFolderNameFromRoot(pathRun, level + 1 + 1); let level2_name = getChildFolderNameFromRoot(pathRun, level + 2 + 1); @@ -290,7 +391,7 @@ async function init() { const groupLevel = groupByLevel(PATHING_ALL); const initLength = settingsList.length let parentNameLast = undefined - let parentNameNow = undefined + // let parentNameNow = undefined const line = 30 const br = `${"=".repeat(line)}\n` groupLevel.filter(list => list.length > 0).forEach( @@ -298,7 +399,7 @@ async function init() { 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" + let prefix = '' if (item.parent_name !== parentNameLast) { parentNameLast = item.parent_name; let b = (line - item.parent_name.length) % 2 === 0; @@ -320,7 +421,17 @@ async function init() { settings_as_name: item.name }) settingsNameList.push(name) - settingsList.push(leveJson) + const existingIndex = settingsList.findIndex(item => item.name === leveJson.name); + if (existingIndex !== -1) { + // 替换已存在的配置项 + settingsList[existingIndex] = leveJson; + } else { + if (item.parent_name !== parentNameLast) { + settingsList.push({type: "separator"}) + } + // 添加新的配置项 + settingsList.push(leveJson); + } i++ } }) @@ -334,9 +445,32 @@ async function init() { // 刷新settings自动设置密钥 item.default = manifest.key }) + // 更新当前用户的配置 + uidSettingsMap.set(Record.uid, settingsList) + // 安全写入配置文件 + try { + file.writeTextSync(uidSettingsJson, JSON.stringify([...uidSettingsMap])) + log.debug("用户配置已保存: {uid}", Record.uid) + } catch (error) { + log.error("保存用户配置失败: {error}", error.message) + } file.writeTextSync(manifest.settings_ui, JSON.stringify(settingsList)) } +//刷新settings + if (settings.config_run === "刷新") { + await refreshALL(); + } else if (true) { + //直接从配置文件中加载对应账号的配置 + let uidSettings = uidSettingsMap.get(Record.uid); + if (uidSettings) { + file.writeTextSync(manifest.settings_ui, JSON.stringify(uidSettings)) + } + configSettings = await initSettings() + settingsNameList = settingsNameList.concat(await getMultiCheckboxMap().then(map => { + return map.keys().filter(key => key.startsWith(levelName)) + })) + } // 初始化needRunMap if (true) { // for (let key of pathAsMap.keys()) { @@ -347,10 +481,18 @@ async function init() { const multi = await getValueByMultiCheckboxName(settingsName); const settingsAsName = settingsNameAsList.find(item => item.settings_name === settingsName) + let list = PATH_JSON_LIST.filter(item => + multi.some(element => item.path.includes(element)) + ).map(item => { + // 找到匹配的元素并填充到 selected 字段 + const matchedElement = multi.find(element => item.path.includes(element)); + return {name: item.name, parent_name: item.parent_name, selected: matchedElement || "", path: item.path} + }); + if (list.length <= 0) { + continue + } needRunMap.set(settingsAsName.settings_as_name, { - paths: (PATH_JSON_LIST.filter(item => - multi.some(element => item.path.includes(element)) - ).map(item => item.path)), + paths: list, as_name: settingsAsName.settings_as_name, name: settingsAsName.settings_name }) @@ -478,7 +620,11 @@ async function runList(list = [], stopKey = AUTO_STOP) { // 遍历路径列表 for (let i = 0; i < list.length; i++) { - const path = list[i]; + const onePath = list[i]; + const path = onePath.path; + if (i === 0) { + log.info(`[{mode}] 开始执行[{1}-{2}]列表`, settings.mode, onePath.selected, onePath.parent_name); + } log.debug('正在执行第{index}/{total}个路径: {path}', i + 1, list.length, path); try { @@ -516,10 +662,14 @@ async function runMap(map = new Map(), stopKey = AUTO_STOP) { // 遍历Map中的所有键 for (const [key, one] of map.entries()) { + if (one.paths.size <= 0) { + continue + } try { // 记录开始执行任务的日志信息 log.info(`[{0}] 开始执行[{1}]...`, settings.mode, one.as_name); // 执行当前任务关联的路径列表 + await runList(one.paths, stopKey); Record.groupPaths.add({ name: one.as_name, @@ -722,52 +872,52 @@ async function treeToList(treeList = []) { } (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)) + saveRecord(); } })() async function main() { let lastRunMap = new Map() - if (RecordLast.groupPaths.size > 0 && RecordLast.paths.size !== RecordLast.groupPaths.size) { - // 由于在迭代过程中删除元素会影响迭代,先收集要删除的键 - const keysToDelete = []; - // 优先跑上次群组没跑过的 - // 使用 Set 提高性能 - const lastListSet = new Set([...RecordLast.groupPaths].map(item => item.name)); - for (const [key, one] of needRunMap.entries()) { - if (!lastListSet.has(key)) { - lastRunMap.set(key, one); - keysToDelete.push(key); + 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); } } - // 然后批量删除 - for (const key of keysToDelete) { - needRunMap.delete(key); - } } - await runMap(needRunMap) + + chooseBestRun(); + + if (needRunMap.size > 0) { + await runMap(needRunMap) + } if (lastRunMap.size > 0) { await runMap(lastRunMap) } - 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) + // 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) } \ No newline at end of file diff --git a/repo/js/FullyAutoAndSemiAutoTools/settings.json b/repo/js/FullyAutoAndSemiAutoTools/settings.json index 8d1707bd7..f32c7cb25 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/settings.json +++ b/repo/js/FullyAutoAndSemiAutoTools/settings.json @@ -1,4 +1,25 @@ [ + { + "name": "config_run", + "type": "select", + "label": "配置模式", + "options": [ + "刷新","加载" + ], + "default": "刷新" + }, + { + "name": "config_white_list", + "type": "input-text", + "label": "刷新白名单 以,分割", + "default": "晶蝶" + }, + { + "name": "config_black_list", + "type": "input-text", + "label": "刷新黑名单 以,分割", + "default": "其他,锄地专区,食材与炼金" + }, { "name": "key", "type": "input-text", @@ -25,6 +46,12 @@ ], "default": "全自动" }, + { + "name": "choose_best", + "type": "checkbox", + "label": "择优模式", + "default": false + }, { "name": "autoStop", "type": "input-text",