From dcc050074dc98ec8a8db998e146a6c77d1076eb2 Mon Sep 17 00:00:00 2001 From: yan Date: Fri, 16 Jan 2026 14:38:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0=E4=B8=83?= =?UTF-8?q?=E5=85=83=E7=B4=A0=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E4=BC=98=E5=8C=96=E5=AF=86=E9=92=A5=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SevenElement.json 配置文件用于管理七元素数据 - 添加 json_path_name 常量对象统一管理配置文件路径 - 优化密钥不匹配错误提示,增加升级说明和文档查看建议 - 实现七元素配置的动态加载和合并功能 - 更新 CD 路径配置使用新的路径常量 - 改进七元素数据解析和排序逻辑 refactor(FullyAutoAndSemiAutoTools): 重构代码结构并优化配置管理 - 修改 getNextCronTimestamp 函数参数,移除默认 URL 参数 - 将全局变量重构为常量对象 auto、dev 和 cd,提高代码可维护性 - 更新团队配置属性名,从 teamFight 和 teamSevenElements 改为 team_fight 和 team_seven_elements - 实现完整的初始化流程,包括工具模块动态加载和配置验证 - 添加 UID 设置持久化功能,支持不同账户的个性化配置 - 重构路径执行逻辑,优化黑白名单过滤和 CD 时间控制 - 移除过时的全局变量和初始化代码,精简代码结构 - 更新设置界面配置,新增 CD 算法开关和改进的半自动模式选项 --- .../config/SevenElement.json | 42 + .../{pathing.json => config/cd-pathing.json} | 0 repo/js/FullyAutoAndSemiAutoTools/main.js | 819 ++++++++++-------- .../FullyAutoAndSemiAutoTools/settings.json | 81 +- .../FullyAutoAndSemiAutoTools/utils/cron.js | 2 +- 5 files changed, 532 insertions(+), 412 deletions(-) create mode 100644 repo/js/FullyAutoAndSemiAutoTools/config/SevenElement.json rename repo/js/FullyAutoAndSemiAutoTools/{pathing.json => config/cd-pathing.json} (100%) diff --git a/repo/js/FullyAutoAndSemiAutoTools/config/SevenElement.json b/repo/js/FullyAutoAndSemiAutoTools/config/SevenElement.json new file mode 100644 index 000000000..2963e7d15 --- /dev/null +++ b/repo/js/FullyAutoAndSemiAutoTools/config/SevenElement.json @@ -0,0 +1,42 @@ +[ +{ + "name": "矿物", + "level": 0, + "value": ["夜泊石", "石珀", "清水玉", "万相石", "矿物"] +}, +{ + "name": "火", + "level": 1, + "value": [] +}, +{ + "name": "水", + "level": 2, + "value": ["海露花"] +}, +{ + "name": "风", + "level": 3, + "value": ["蒲公英籽"] +}, +{ + "name": "雷", + "level": 4, + "value": ["琉鳞石", "绯樱绣球"] +}, +{ + "name": "草", + "level": 5, + "value": [] +}, +{ + "name": "冰", + "level": 6, + "value": [] +}, +{ + "name": "岩", + "level": 7, + "value": [] +} +] \ No newline at end of file diff --git a/repo/js/FullyAutoAndSemiAutoTools/pathing.json b/repo/js/FullyAutoAndSemiAutoTools/config/cd-pathing.json similarity index 100% rename from repo/js/FullyAutoAndSemiAutoTools/pathing.json rename to repo/js/FullyAutoAndSemiAutoTools/config/cd-pathing.json diff --git a/repo/js/FullyAutoAndSemiAutoTools/main.js b/repo/js/FullyAutoAndSemiAutoTools/main.js index d862e7cf2..396d7078f 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/main.js +++ b/repo/js/FullyAutoAndSemiAutoTools/main.js @@ -1,11 +1,20 @@ let manifest_json = "manifest.json"; let manifest = undefined let configSettings = undefined -let AUTO_STOP = undefined -let AUTO_SKIP = undefined -let debug = undefined -let isDebug = false -let SEMI_AUTO = false +const auto = { + semi: false,//半自动 + run: false,//运行 + skip: false,//跳过 + key: "", +} +const dev = { + isDebug: false, + debug: undefined, +} +const cd = { + open: false, + http_api: undefined, +} const pathingName = "pathing" // const pathAsMap = new Map([]) // const pathRunMap = new Map([]) @@ -15,6 +24,13 @@ let settingsNameList = new Array() const settingsNameAsList = new Array() let PATH_JSON_LIST = new Array() const config_root = 'config' +const json_path_name = { + RecordText: `${config_root}\\record.json`, + RecordPathText: `${config_root}\\PathRecord.json`, + uidSettingsJson: `${config_root}\\uidSettings.json`, + cdPath: `${config_root}\\cd-${pathingName}.json`, + SevenElement: `${config_root}\\SevenElement.json`, +} // 定义记录文件的路径 let RecordText = `${config_root}\\record.json` let RecordPathText = `${config_root}\\PathRecord.json` @@ -63,9 +79,9 @@ const SevenElement = { const team = { current: undefined, fight: false, - fightName: settings.teamFight, + fightName: settings.team_fight, fightKeys: ['锄地专区', "敌人与魔物"], - SevenElements: settings.teamSevenElements ? settings.teamSevenElements.split(',').map(item => item.trim()) : [], + SevenElements: settings.team_seven_elements ? settings.team_seven_elements.split(',').map(item => item.trim()) : [], } const timeType = Object.freeze({ hours: 'hours',//小时 @@ -76,6 +92,405 @@ const timeType = Object.freeze({ } }); +async function init() { + let settingsConfig = await initSettings(); + 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 + // AUTO_STOP = (AUTO_STOP) ? AUTO_STOP : settings.autoStop + // AUTO_SKIP = (AUTO_SKIP) ? AUTO_SKIP : settings.autoSkip + // + // auto.run = (auto.run) ? auto.run : settings.autoStop + // auto.skip = (auto.skip) ? auto.skip : settings.autoSkip + 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 + + 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() + const uidSettingsJson = json_path_name.uidSettingsJson; + 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 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 + + // 预处理黑白名单数组,移除空字符串并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); + 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` + let idx = 0 + 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 = '' + 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 = idx === 0 ? "【地图追踪】\n" : `${prefix}[${item.parent_name}-${item.name}]\n` + const p = `${prefix}[${item.name}]\n` + idx++ + 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) + 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++ + } + }) + } + ) + level++ + + settingsList.filter( + item => item.name === 'key' + ).forEach(item => { + // 刷新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 (settings.config_run === "加载") { + //直接从配置文件中加载对应账号的配置 + let uidSettings = uidSettingsMap.get(Record.uid); + if (uidSettings) { + try { + file.writeTextSync(manifest.settings_ui, JSON.stringify(uidSettings)) + } catch (e) { + log.error("加载用户配置失败: {error}", e.message) + } + } + configSettings = await initSettings() + settingsNameList = settingsNameList.concat(await getMultiCheckboxMap().then(map => { + return map.keys().filter(key => key.startsWith(levelName)) + })) + } else + // 初始化needRunMap + if (settings.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 + )) + // for (let key of pathAsMap.keys()) { + // const multiCheckbox = await getValueByMultiCheckboxName(pathAsMap.get(key)); + // needRunMap.set(key, multiCheckbox) + // } + for (const settingsName of settingsNameList) { + // let multi = await getValueByMultiCheckboxName(settingsName); + const multiJson = await getJsonByMultiCheckboxName(settingsName) + const label = getBracketContent(multiJson.label) + let multi = multiJson.options + + + const settingsAsName = settingsNameAsList.find(item => item.settings_name === settingsName) + let list = PATH_JSON_LIST.filter(item => + multi.some(element => item.path.includes(`\\${element}\\`) && item.path.includes(`\\${label}\\`)) + ).map(item => { + // 找到匹配的元素并填充到 selected 字段 + const matchedElement = multi.find(element => item.path.includes(`\\${element}\\`) && item.path.includes(`\\${label}\\`)); + return {name: item.name, parent_name: item.parent_name, selected: matchedElement || "", path: item.path} + }); + // 1. 预处理:将 Set/Map 转为数组,避免循环内重复转换 + const recordPaths = Array.from(RecordPath.paths); + const timeConfigs = Array.from(timeJson); + + const timeFilter = list.filter(item => { + // 2. 查找匹配的配置项 (假设这是必须的条件) + // 注意:这里保留了原有的 includes 逻辑,但在实际业务中建议评估是否需要精确匹配 + const timeConfig = timeConfigs.find(e => item.path.includes(`\\${e.name}\\`)); + + if (!timeConfig) return false; + + // 3. 查找匹配的历史记录 + // 同样保留了 includes 逻辑 + const matchedRecord = recordPaths.find(element => element.path.includes(item.path)); + + // 4. 如果没有记录,或者记录中没有时间戳,则跳过 + if (!matchedRecord || !matchedRecord.timestamp) return false; + + const {timestamp, value} = matchedRecord; + const now = Date.now(); + + // 5. 根据配置的类型进行时间判断 + if (timeConfig.type) { + switch (timeType.fromValue(timeConfig.type)) { + case timeType.hours: + const timeDifference = getTimeDifference(timestamp, now); + return timeDifference.total.hours >= value; + case timeType.cron: + const nextCronTimestamp = cronUtil.getNextCronTimestamp(`${value}`, timestamp, now, cd.http_api); + // if (!nextCronTimestamp) { + // log.error(`cron表达式解析失败: {value}`, value) + // throw new Error(`cron表达式解析失败: ${value}`) + // } + if (!nextCronTimestamp) return false; + return now >= nextCronTimestamp; + default: + return false; + } + } + + return false; + }); + + + if (timeFilter?.length > 0) { + //移除CD + list = Array.from(new Set(list).difference(new Set(timeFilter))) + } + + if (list?.length > 0) { + needRunMap.set(settingsAsName.settings_as_name, { + paths: list, + as_name: settingsAsName.settings_as_name, + name: settingsAsName.settings_name + }) + } + log.debug(`[CD]{0}[CD]`, JSON.stringify([...timeFilter])) + log.debug(`[RUN]{0}[RUN]`, JSON.stringify([...list])) + } + // 启用自动拾取的实时任务,并配置成启用急速拾取模式 + dispatcher.addTrigger(new RealtimeTimer("AutoPick", {"forceInteraction": true})); + } + return true +} + +(async function () { + try { + if (await init()) { + await main() + } + } finally { + 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); + } + } + } + + chooseBestRun(); + + if (needRunMap.size > 0) { + await runMap(needRunMap) + } + if (lastRunMap.size > 0) { + 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类型数据转换为数组后保存到文件 @@ -430,311 +845,6 @@ async function getValueByMultiCheckboxName(name) { return multiCheckboxMap.get(name).options } -async function init() { - let settingsConfig = await initSettings(); - let utils = [ - "cron", - "SwitchTeam", - "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 - AUTO_SKIP = (AUTO_SKIP) ? AUTO_SKIP : settings.autoSkip - 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("半自动模式下必须开启自动停止") - } - - if (!file.IsFolder(`${pathingName}`)) { - let batFile = "SymLink.bat"; - log.error("{0}文件夹不存在,请在BetterGI中右键点击本脚本,选择{1}。然后双击脚本目录下的{2}文件以创建文件夹链接", `${pathingName}`, "打开所在目录", batFile); - return false; - } - //记录初始化 - await initRecord(); - - // 读取现有配置并合并 - let uidSettingsMap = new Map() - const uidSettingsJson = `${config_root}/uid_settings.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 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 - - // 预处理黑白名单数组,移除空字符串并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); - 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` - let idx = 0 - 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 = '' - 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 = idx === 0 ? "【地图追踪】\n" : `${prefix}[${item.parent_name}-${item.name}]\n` - const p = `${prefix}[${item.name}]\n` - idx++ - 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) - 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++ - } - }) - } - ) - level++ - - settingsList.filter( - item => item.name === 'key' - ).forEach(item => { - // 刷新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) { - try { - file.writeTextSync(manifest.settings_ui, JSON.stringify(uidSettings)) - } catch (e) { - log.error("加载用户配置失败: {error}", e.message) - } - } - configSettings = await initSettings() - settingsNameList = settingsNameList.concat(await getMultiCheckboxMap().then(map => { - return map.keys().filter(key => key.startsWith(levelName)) - })) - } - const timeJson = new Set(JSON.parse(file.readTextSync(`${pathingName}.json`)).sort( - (a, b) => b.level - a.level - )) - // 初始化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) { - // let multi = await getValueByMultiCheckboxName(settingsName); - const multiJson = await getJsonByMultiCheckboxName(settingsName) - const label = getBracketContent(multiJson.label) - let multi = multiJson.options - - - const settingsAsName = settingsNameAsList.find(item => item.settings_name === settingsName) - let list = PATH_JSON_LIST.filter(item => - multi.some(element => item.path.includes(`\\${element}\\`) && item.path.includes(`\\${label}\\`)) - ).map(item => { - // 找到匹配的元素并填充到 selected 字段 - const matchedElement = multi.find(element => item.path.includes(`\\${element}\\`) && item.path.includes(`\\${label}\\`)); - return {name: item.name, parent_name: item.parent_name, selected: matchedElement || "", path: item.path} - }); - // 1. 预处理:将 Set/Map 转为数组,避免循环内重复转换 - const recordPaths = Array.from(RecordPath.paths); - const timeConfigs = Array.from(timeJson); - - const timeFilter = list.filter(item => { - // 2. 查找匹配的配置项 (假设这是必须的条件) - // 注意:这里保留了原有的 includes 逻辑,但在实际业务中建议评估是否需要精确匹配 - const timeConfig = timeConfigs.find(e => item.path.includes(`\\${e.name}\\`)); - - if (!timeConfig) return false; - - // 3. 查找匹配的历史记录 - // 同样保留了 includes 逻辑 - const matchedRecord = recordPaths.find(element => element.path.includes(item.path)); - - // 4. 如果没有记录,或者记录中没有时间戳,则跳过 - if (!matchedRecord || !matchedRecord.timestamp) return false; - - const {timestamp, value} = matchedRecord; - const now = Date.now(); - - // 5. 根据配置的类型进行时间判断 - if (timeConfig.type) { - switch (timeType.fromValue(timeConfig.type)) { - case timeType.hours: - const timeDifference = getTimeDifference(timestamp, now); - return timeDifference.total.hours >= value; - case timeType.cron: - const nextCronTimestamp = cronUtil.getNextCronTimestamp(`${value}`, timestamp, now); - // if (!nextCronTimestamp) { - // log.error(`cron表达式解析失败: {value}`, value) - // throw new Error(`cron表达式解析失败: ${value}`) - // } - if (!nextCronTimestamp) return false; - return now >= nextCronTimestamp; - default: - return false; - } - } - - return false; - }); - - - if (timeFilter?.length > 0) { - //移除CD - list = Array.from(new Set(list).difference(new Set(timeFilter))) - } - - if (list?.length > 0) { - needRunMap.set(settingsAsName.settings_as_name, { - paths: list, - as_name: settingsAsName.settings_as_name, - name: settingsAsName.settings_name - }) - } - log.debug(`[CD]{0}[CD]`,JSON.stringify([...timeFilter])) - log.debug(`[RUN]{0}[RUN]`,JSON.stringify([...list])) - } - } - // 启用自动拾取的实时任务,并配置成启用急速拾取模式 - dispatcher.addTrigger(new RealtimeTimer("AutoPick", {"forceInteraction": true})); - return true -} /** * 获取字符串中第一个方括号内的内容 @@ -755,10 +865,10 @@ function getBracketContent(str) { * @param {string} json - 需要写入调试文件的内容,默认为空数组 * @returns {Promise} - 异步函数,没有返回值 */ -async function debugKey(path = "debug.json", json = "", key = debug) { +async function debugKey(path = "debug.json", json = "", key = dev.debug) { const p = "debug\\" // 检查是否处于调试模式 - if (isDebug) { + if (dev.isDebug) { log.warn("[{0}]正在写出{1}日志", '开发者模式', path) // 将调试信息同步写入指定文件 file.writeTextSync(`${p}${path}`, json) @@ -833,9 +943,8 @@ async function keyMousePressStart(key) { /** * 执行指定路径的脚本文件 * @param {string} path - 要执行的脚本路径 - * @param {string} [stopKey=AUTO_STOP] - 用于暂停执行的按键,默认为AUTO_STOP */ -async function runPath(path, stopKey = AUTO_STOP) { +async function runPath(path) { // 参数验证 if (!path || typeof path !== 'string') { log.warn('无效的路径参数: {path}', path) @@ -924,9 +1033,9 @@ async function runPath(path, stopKey = AUTO_STOP) { } } - if (SEMI_AUTO) { - log.warn(`[{mode}] 路径执行完成: {path}, 请按{key}继续`, settings.mode, path, stopKey) - await keyMousePressStart(stopKey) + if (auto.semi && auto.run) { + log.warn(`[{mode}] 路径执行完成: {path}, 请按{key}继续`, settings.mode, path, auto.key) + await keyMousePressStart(auto.key) } } @@ -934,10 +1043,9 @@ async function runPath(path, stopKey = AUTO_STOP) { /** * 执行给定的路径列表 * @param {Array} list - 要执行的路径列表,默认为空数组 - * @param {string} stopKey - 停止标识,默认为AUTO_STOP * @returns {Promise} */ -async function runList(list = [], stopKey = AUTO_STOP) { +async function runList(list = []) { // 参数验证 if (!Array.isArray(list)) { log.warn('无效的路径列表参数: {list}', list); @@ -957,17 +1065,17 @@ async function runList(list = [], stopKey = AUTO_STOP) { log.info(`[{mode}] 开始执行[{1}-{2}]列表`, settings.mode, onePath.selected, onePath.parent_name); } log.debug('正在执行第{index}/{total}个路径: {path}', i + 1, list.length, path); - if (SEMI_AUTO) { - log.warn(`[{mode}] 按下{key}可跳过{0}执行,如不想跳过请按空格`, settings.mode, AUTO_SKIP, path); - const skip = await keyMousePress(AUTO_SKIP, true); + 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_SKIP, path); + log.warn(`[{mode}] 按下{key}跳过{0}执行`, settings.mode, auto.key, path); continue } } try { // 执行单个路径,并传入停止标识 - await runPath(path, stopKey); + await runPath(path); } catch (error) { log.error('执行路径列表中的路径失败: {path}, 错误: {error}', path, error.message); continue; // 继续执行列表中的下一个路径 @@ -981,10 +1089,9 @@ async function runList(list = [], stopKey = AUTO_STOP) { /** * 遍历并执行Map中的任务 * @param {Map} map - 包含任务信息的Map对象,默认为新的Map实例 - * @param {string} stopKey - 自动停止的标识键,默认为AUTO_STOP * @returns {Promise} - 异步执行,没有返回值 */ -async function runMap(map = new Map(), stopKey = AUTO_STOP) { +async function runMap(map = new Map()) { // 参数验证 if (!(map instanceof Map)) { log.warn('无效的Map参数: {map}', map); @@ -1008,7 +1115,7 @@ async function runMap(map = new Map(), stopKey = AUTO_STOP) { log.info(`[{0}] 开始执行[{1}]...`, settings.mode, one.as_name); // 执行当前任务关联的路径列表 - await runList(one.paths, stopKey); + await runList(one.paths); Record.groupPaths.add({ name: one.as_name, paths: new Set(one.paths) @@ -1209,57 +1316,3 @@ async function treeToList(treeList = []) { return list } -(async function () { - try { - if (await init()) { - await main() - } - } finally { - 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); - } - } - } - - chooseBestRun(); - - if (needRunMap.size > 0) { - await runMap(needRunMap) - } - if (lastRunMap.size > 0) { - 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) -} \ No newline at end of file diff --git a/repo/js/FullyAutoAndSemiAutoTools/settings.json b/repo/js/FullyAutoAndSemiAutoTools/settings.json index ab2883b94..5c29256b2 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/settings.json +++ b/repo/js/FullyAutoAndSemiAutoTools/settings.json @@ -1,13 +1,22 @@ [ + { + "name": "key", + "type": "input-text", + "label": "密钥", + "default": "" + }, { "name": "config_run", "type": "select", "label": "配置模式", "options": [ - "刷新","加载" + "刷新","加载","执行" ], "default": "刷新" }, + { + "type": "separator" + }, { "name": "config_white_list", "type": "input-text", @@ -21,27 +30,28 @@ "default": "其他,锄地专区,食材与炼金" }, { - "name": "cron_http_url", - "type": "input-text", - "label": "cron解析Http 地址", - "default": "http:///bgi/cron/next-timestamp" + "type": "separator" }, { - "name": "key", - "type": "input-text", - "label": "密钥", - "default": "" - }, - { - "name": "isDebug", + "name": "open_cd", "type": "checkbox", - "label": "开发者模式", + "label": "启用CD算法", "default": false }, { - "name": "debug", - "type": "input-text", - "label": "调试快捷键(开发者)" + "name": "http_api", + "type": "input-text", + "label": "[默认CD算法api]\ncron解析Http地址\n[请部署bettergi-scripts-tools可支持]", + "default": "http:///bgi/cron/next-timestamp" + }, + { + "type": "separator" + }, + { + "name": "choose_best", + "type": "checkbox", + "label": "择优模式(默认关闭 优先跑之前没跑过的)", + "default": false }, { "name": "mode", @@ -53,30 +63,45 @@ "default": "全自动" }, { - "name": "teamFight", + "name": "auto_semi_key_mode", + "type": "select", + "label": "[半自动]快捷键模式", + "options": [ + "继续运行","跳过" + ], + "default": "继续运行" + }, + { + "name": "auto_key", + "type": "input-text", + "label": "<继续运行|跳过>本次路线快捷键 (独立BGI的快捷键请勿冲突)" + }, + { + "type": "separator" + }, + { + "name": "team_fight", "type": "input-text", "label": "战斗队伍配置(同时也是行走队伍配置)" }, { - "name": "teamSevenElements", + "name": "team_seven_elements", "type": "input-text", "label": "队伍配置 按 `矿物,火,水,风,雷,草,冰,岩` 该顺序填写", "default": "" }, { - "name": "choose_best", + "type": "separator" + }, + { + "name": "is_debug", "type": "checkbox", - "label": "择优模式(默认关闭 优先跑之前没跑过的)", + "label": "开发者模式", "default": false }, { - "name": "autoStop", - "type": "input-text", - "label": "自动暂停快捷键(独立BGI的快捷键请勿冲突)" - }, - { - "name": "autoSkip", - "type": "input-text", - "label": "【测试功能-无效配置】自动跳过运行快捷键(独立BGI的快捷键请勿冲突)" + "name": "debug", + "type": "input-text", + "label": "调试快捷键(开发者)" } ] \ No newline at end of file diff --git a/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js b/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js index 6c6485eee..5e153c6c9 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js +++ b/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js @@ -122,7 +122,7 @@ function parseCron(cron) { * @param {number} endTimestamp - 结束时间戳 * @returns {Promise} 返回一个Promise,解析为下一个Cron时间戳 */ -async function getNextCronTimestamp(cronExpression, startTimestamp = Date.now(), endTimestamp, url = settings.cron_http_url) { +async function getNextCronTimestamp(cronExpression, startTimestamp = Date.now(), endTimestamp, url) { const result = await http.request("POST", url, JSON.stringify({ cronExpression: `${cronExpression}`, startTimestamp: startTimestamp,