From de8d5ff9753d567b4ca8c1e2548c78cdc1ce067e Mon Sep 17 00:00:00 2001 From: mno <718135749@qq.com> Date: Wed, 7 May 2025 11:45:52 +0800 Subject: [PATCH] =?UTF-8?q?js=EF=BC=9A=E9=87=87=E9=9B=86cd=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20(#727)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repo/js/采集cd管理/README.md | 19 ++ repo/js/采集cd管理/main.js | 310 ++++++++++++++++++ repo/js/采集cd管理/manifest.json | 14 + .../pathing/需要更多路径组请自行新建文件夹.txt | 0 repo/js/采集cd管理/settings.json | 76 +++++ .../将地图追踪文件放到pathing文件夹.txt | 0 .../采集cd管理/采集物cd类型(仅供参考).xlsx | Bin 0 -> 12980 bytes 7 files changed, 419 insertions(+) create mode 100644 repo/js/采集cd管理/README.md create mode 100644 repo/js/采集cd管理/main.js create mode 100644 repo/js/采集cd管理/manifest.json create mode 100644 repo/js/采集cd管理/pathing/需要更多路径组请自行新建文件夹.txt create mode 100644 repo/js/采集cd管理/settings.json create mode 100644 repo/js/采集cd管理/将地图追踪文件放到pathing文件夹.txt create mode 100644 repo/js/采集cd管理/采集物cd类型(仅供参考).xlsx diff --git a/repo/js/采集cd管理/README.md b/repo/js/采集cd管理/README.md new file mode 100644 index 00000000..a995dca4 --- /dev/null +++ b/repo/js/采集cd管理/README.md @@ -0,0 +1,19 @@ +# 采集CD管理脚本 + +## 简介 +仅供了解文件操作的用户使用,这是一个基于文件夹操作的自动化脚本,用于管理采集路线的冷却时间(CD)。它会按照路径组的顺序依次运行任务,直到指定的时间,并根据给定的CD类型自动跳过未刷新的路线。 + +## 文件结构 +- `main.js`:主要的脚本文件,负责执行任务和管理CD。 +- `manifest.json`:脚本的配置文件,包含脚本的基本信息和设置。 +- `settings.json`:用户配置文件,用于设置操作模式、路径组CD类型、配队名称等。 + +## 功能 +- **操作模式选择**:提供两种操作模式,包括“执行任务(若不存在索引文件则自动创建)”和“重新生成索引文件(用于强制刷新CD或更新文件)”。 +- **路径组CD类型**:支持多种CD类型,包括“1次0点刷新”、“2次0点刷新”、“3次0点刷新”、“4点刷新”、“12小时刷新”、“24小时刷新”和“46小时刷新”。 +- **任务管理**:自动读取路径组文件,跳过未刷新的任务,并根据CD类型更新任务的时间戳。同时支持跳过指定时间段的任务。 +- **配队管理**:支持为每个路径组指定配队名称,自动切换配队。 + +## 使用方法 +1. 将要运行的地图追踪文件放在pathing文件夹下的路径组中,将会按照路径组的顺序执行,并跳过未刷新的路线 +2. 根据需要修改自定义配置,设置操作模式、路径组CD类型、配队名称等参数。 \ No newline at end of file diff --git a/repo/js/采集cd管理/main.js b/repo/js/采集cd管理/main.js new file mode 100644 index 00000000..1cd91df9 --- /dev/null +++ b/repo/js/采集cd管理/main.js @@ -0,0 +1,310 @@ +// 定义目标文件夹路径和记录文件路径 +const recordFolder = "record"; // 存储记录文件的文件夹路径 +const timestamp = "::2000-01-01T00:00:00.000Z"; // 固定的时间戳 + +// 从 settings 中读取用户配置,并设置默认值 +const userSettings = { + operationMode: settings.operationMode || "执行任务(若不存在索引文件则自动创建)", + pathGroup1CdType: settings.pathGroup1CdType || "", + pathGroup2CdType: settings.pathGroup2CdType || "", + pathGroup3CdType: settings.pathGroup3CdType || "", + otherPathGroupsCdTypes: settings.otherPathGroupsCdTypes || "", + partyNames: settings.partyNames || "", + skipTimeRanges: settings.skipTimeRanges || "", + infoFileName: settings.infoFileName || "" +}; + +// 将 partyNames 分割并存储到一个数组中 +const partyNamesArray = userSettings.partyNames.split(";").map(name => name.trim()); + +// 新增一个数组 pathGroupCdType,存储每个路径组的 cdtype 信息 +const pathGroupCdType = [ + userSettings.pathGroup1CdType, + userSettings.pathGroup2CdType, + userSettings.pathGroup3CdType +]; + +// 如果 otherPathGroupsCdTypes 不为空,将其分割为数组并添加到 pathGroupCdType 中 +if (userSettings.otherPathGroupsCdTypes) { + pathGroupCdType.push(...userSettings.otherPathGroupsCdTypes.split(";")); +} + +// 当infoFileName为空时,将其改为由其他自定义配置决定的一个字符串 +if (!userSettings.infoFileName) { + userSettings.infoFileName = [ + userSettings.pathGroup1CdType, + userSettings.pathGroup2CdType, + userSettings.pathGroup3CdType, + userSettings.otherPathGroupsCdTypes, + userSettings.partyNames, + userSettings.skipTimeRanges + ].join("."); +} + +// 定义自定义函数 basename,用于获取文件名 +function basename(filePath) { + const lastSlashIndex = filePath.lastIndexOf('\\'); // 或者使用 '/',取决于你的路径分隔符 + return filePath.substring(lastSlashIndex + 1); +} + +// 定义自定义函数 removeJsonSuffix,用于移除文件名中的 .json 后缀 +function removeJsonSuffix(fileName) { + if (fileName.endsWith('.json')) { + return fileName.substring(0, fileName.length - 5); // 移除 .json 后缀 + } + return fileName; +} + +(async function () { + try { + // 获取子文件夹路径 + const subFolderName = userSettings.infoFileName; // 使用设置后的 infoFileName + const subFolderPath = `${recordFolder}/${subFolderName}`; + + // 读取子文件夹中的所有文件路径 + const filesInSubFolder = file.ReadPathSync(subFolderPath); + + // 检查路径组1.txt文件是否存在 + let indexDoExist = false; + for (const filePath of filesInSubFolder) { + const fileName = basename(filePath); // 提取文件名 + if (fileName === "路径组1.txt") { + indexDoExist = true; + break; + } + } + log.debug(`路径组1.txt 是否存在: ${indexDoExist}`); + + // 根据用户配置选择操作模式 + if (userSettings.operationMode === "重新生成索引文件(用于强制刷新CD或更新文件)" || !indexDoExist) { + if (userSettings.operationMode === "重新生成索引文件(用于强制刷新CD或更新文件)") { + log.info("重新生成索引文件模式,将覆盖现有索引文件"); + } else { + log.info("路径组1.txt 文件不存在,将生成文件"); + } + + // 循环处理多个路径组 + for (let i = 1; ; i++) { + const targetFolder = `pathing/路径组${i}`; // 动态生成目标文件夹路径 + const filePaths = file.ReadPathSync(targetFolder); + + // 检查当前路径组的 cdtype 是否为空 + const currentCdType = pathGroupCdType[i - 1] || ""; + if (!currentCdType) { + log.info(`路径组${i} 的 cdtype 为空,停止处理`); + break; + } + + // 如果文件夹为空,退出循环 + if (filePaths.length === 0) { + log.info(`路径组${i} 文件夹为空,停止处理`); + break; + } + + // 用于存储符合条件的文件名的数组 + const jsonFileNames = []; + + // 遍历文件路径数组并提取文件名 + for (const filePath of filePaths) { + const fileName = basename(filePath); // 提取文件名 + if (fileName.endsWith('.json')) { // 检查文件名是否以 .json 结尾 + const fileNameWithoutSuffix = removeJsonSuffix(fileName); // 移除 .json 后缀 + jsonFileNames.push(`${fileNameWithoutSuffix}${timestamp}`); // 添加时间戳并存储 + } + } + + // 如果没有找到符合条件的文件,跳过当前路径组 + if (jsonFileNames.length === 0) { + //log.info(`路径组${i} 中未找到符合条件的 .json 文件,跳过`); + continue; + } + + // 将文件名数组转换为字符串,每个文件名占一行 + const fileNamesContent = jsonFileNames.join("\n"); + + // 定义记录文件的完整路径 + const recordFilePath = `${subFolderPath}/路径组${i}.txt`; + + // 将文件名写入记录文件 + const writeResult = file.writeTextSync(recordFilePath, fileNamesContent); + + if (writeResult) { + log.info(`文件名已成功写入: ${recordFilePath}`); + } else { + log.error(`写入文件失败: ${recordFilePath}`); + } + } + } else { + // 如果用户选择的不是“重新生成索引文件”且文件已存在,则输出特定日志信息 + log.debug("奶龙"); + } + + // 新增逻辑:当选择“执行任务(若不存在索引文件则自动创建)”时,执行类似路径执行的逻辑 +if (userSettings.operationMode === "执行任务(若不存在索引文件则自动创建)") { + log.info("启用自动拾取的实时任务"); + dispatcher.addTimer(new RealtimeTimer("AutoPick")); + // 获取子文件夹内的所有文件路径 + const recordFiles = file.ReadPathSync(subFolderPath); + // 直接获取文件数量作为路径组数量 + const totalPathGroups = recordFiles.length; + + // 外层循环:依次处理每个路径组 + for (let groupNumber = 1; groupNumber <= totalPathGroups; groupNumber++) { + const pathGroupFilePath = `${subFolderPath}/路径组${groupNumber}.txt`; // 动态生成路径组文件路径 + +genshin.returnMainUi(); + +//切换到指定配队 + if (partyNamesArray[groupNumber - 1] !== "") { +await genshin.switchParty(partyNamesArray[groupNumber - 1]) +} + +genshin.returnMainUi(); + +try { + const pathGroupContent = await file.readText(pathGroupFilePath); + const pathGroupEntries = pathGroupContent.trim().split('\n'); + for (let i = 0; i < pathGroupEntries.length; i++) { + const entryWithTimestamp = pathGroupEntries[i].trim(); + const [entryName, entryTimestamp] = entryWithTimestamp.split('::'); + + // 构造路径文件路径 + const pathingFilePath = `pathing/路径组${groupNumber}/${entryName}.json`; + + // 获取开始时间 + const startTime = new Date(); + + // 比较当前时间戳与任务的时间戳 + const entryDate = new Date(entryTimestamp); + if (startTime <= entryDate) { + log.info(`当前任务 ${entryName} 未刷新,跳过任务 ${i + 1}/${pathGroupEntries.length} 个`); + continue; // 跳过当前任务 + } + + // 新增校验:若当前时间的小时数和 skipTimeRanges 一致,则跳过任务 + const currentHour = startTime.getHours(); // 获取当前时间的小时数 + const skipHours = userSettings.skipTimeRanges.split(';').map(Number); // 将 skipTimeRanges 转换为数字数组 + if (skipHours.includes(currentHour)) { + log.info(`当前时间的小时数为 ${currentHour},在跳过时间范围内,跳过任务 ${entryName}`); + continue; // 跳过当前任务 + } + + // 日志输出当前任务信息 + log.info(`当前任务 ${entryName} 为第 ${i + 1}/${pathGroupEntries.length} 个`); + + // 执行路径文件 + try { + await pathingScript.runFile(pathingFilePath); + log.info(`执行任务: ${entryName}`); + } catch (error) { + log.error(`路径文件 ${pathingFilePath} 不存在或执行失败: ${error}`); + continue; // 跳过当前任务 + } + + // 获取结束时间 + const endTime = new Date(); + + // 比较开始时间与结束时间 + const timeDiff = endTime.getTime() - startTime.getTime(); // 时间差(毫秒) + if (timeDiff > 10000) { // 时间差大于10秒 + // 获取当前路径组的 cdtype +const currentCdType = pathGroupCdType[groupNumber - 1] || "未知类型"; + +// 初始化 newTimestamp 和 nextAvailableTime +let newTimestamp; +let nextAvailableTime; + +// 根据 cdtype 执行不同的操作 +switch (currentCdType) { + case "1次0点刷新": + // 将任务文件中对应的时间戳改为下一个0点 + const tomorrow = new Date(startTime.getTime() + 24 * 60 * 60 * 1000); + tomorrow.setHours(0, 0, 0, 0); // 设置为下一个0点 + newTimestamp = tomorrow.toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "2次0点刷新": + // 将任务文件中对应的时间戳改为下下个0点 + const dayAfterTomorrow = new Date(startTime.getTime() + 48 * 60 * 60 * 1000); + dayAfterTomorrow.setHours(0, 0, 0, 0); // 设置为下下个0点 + newTimestamp = dayAfterTomorrow.toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "3次0点刷新": + // 将任务文件中对应的时间戳改为下下下个0点 + const twoDaysAfterTomorrow = new Date(startTime.getTime() + 72 * 60 * 60 * 1000); + twoDaysAfterTomorrow.setHours(0, 0, 0, 0); // 设置为下下下个0点 + newTimestamp = twoDaysAfterTomorrow.toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "4点刷新": + // 将任务文件中对应的时间戳改为下一个4点 + const next4AM = new Date(startTime.getTime()); + next4AM.setHours(4, 0, 0, 0); // 设置为当天的4点 + if (next4AM <= startTime) { + next4AM.setDate(next4AM.getDate() + 1); // 如果当前时间已过4点,则设置为下一天的4点 + } + newTimestamp = next4AM.toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "12小时刷新": + // 将任务文件中对应的时间戳改为开始时间后12小时0分0秒 + newTimestamp = new Date(startTime.getTime() + 12 * 60 * 60 * 1000).toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "24小时刷新": + // 将任务文件中对应的时间戳改为开始时间后24小时0分0秒 + newTimestamp = new Date(startTime.getTime() + 24 * 60 * 60 * 1000).toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + case "46小时刷新": + // 将任务文件中对应的时间戳改为开始时间后46小时0分0秒 + newTimestamp = new Date(startTime.getTime() + 46 * 60 * 60 * 1000).toISOString(); + nextAvailableTime = new Date(newTimestamp).toLocaleString(); // 转换为本地时间格式 + //log.info(`下一次可用时间为 ${nextAvailableTime}`); + break; + + default: + log.warn(`路径组${groupNumber} 的 cdtype 是 ${currentCdType},执行默认操作`); + // 默认操作:将下一个可用时间设置为开始时间 + newTimestamp = startTime.toISOString(); + nextAvailableTime = startTime.toLocaleString(); // 转换为本地时间格式 + break; +} + + // 更新任务文件中的时间戳 + const updatedEntries = pathGroupEntries.map(entry => { + const [name, timestamp] = entry.split('::'); + if (name === entryName) { + return `${name}::${newTimestamp}`; + } + return entry; + }).join('\n'); + + await file.writeText(pathGroupFilePath, updatedEntries); + log.info(`本任务执行大于10秒,cd信息已更新,下一次可用时间为 ${nextAvailableTime}`); + } + } + log.info(`路径组${groupNumber} 的所有任务运行完成`); + } catch (error) { + log.error(`读取路径组文件 ${pathGroupFilePath} 时出错: ${error}`); + } + } + log.info('所有路径组的任务运行完成'); +} + } catch (error) { + log.error(`操作失败: ${error}`); + } +})(); diff --git a/repo/js/采集cd管理/manifest.json b/repo/js/采集cd管理/manifest.json new file mode 100644 index 00000000..658bd414 --- /dev/null +++ b/repo/js/采集cd管理/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "name": "采集cd管理", + "version": "1.0", + "bgi_version": "0.44.8", + "description": "仅限了解文件操作的用户使用,基于文件夹操作自动管理采集路线的cd,会按照路径组的顺序依次运行,直到指定的时间,并会按照给定的cd类型,自动跳过未刷新的路线", + "authors": [ + { + "name": "mno" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} diff --git a/repo/js/采集cd管理/pathing/需要更多路径组请自行新建文件夹.txt b/repo/js/采集cd管理/pathing/需要更多路径组请自行新建文件夹.txt new file mode 100644 index 00000000..e69de29b diff --git a/repo/js/采集cd管理/settings.json b/repo/js/采集cd管理/settings.json new file mode 100644 index 00000000..7c744474 --- /dev/null +++ b/repo/js/采集cd管理/settings.json @@ -0,0 +1,76 @@ +[ + { + "name": "operationMode", + "type": "select", + "label": "选择操作模式", + "options": [ + "执行任务(若不存在索引文件则自动创建)", + "重新生成索引文件(用于强制刷新CD或更新文件)" + ] + }, + { + "name": "pathGroup1CdType", + "type": "select", + "label": "选择路径组1 CD类型", + "options": [ + "", // 增加一个空选项 + "1次0点刷新", + "2次0点刷新", + "3次0点刷新", + "4点刷新", + "12小时刷新", + "24小时刷新", + "46小时刷新" + ] + }, + { + "name": "pathGroup2CdType", + "type": "select", + "label": "选择路径组2 CD类型", + "options": [ + "", // 增加一个空选项 + "1次0点刷新", + "2次0点刷新", + "3次0点刷新", + "4点刷新", + "12小时刷新", + "24小时刷新", + "46小时刷新" + ] + }, + { + "name": "pathGroup3CdType", + "type": "select", + "label": "选择路径组3 CD类型", + "options": [ + "", // 增加一个空选项 + "1次0点刷新", + "2次0点刷新", + "3次0点刷新", + "4点刷新", + "12小时刷新", + "24小时刷新", + "46小时刷新" + ] + }, + { + "name": "otherPathGroupsCdTypes", + "type": "input-text", + "label": "依次输入更多路径组的CD类型(存在时才输入,英文分号;分隔)" + }, + { + "name": "partyNames", + "type": "input-text", + "label": "依次输入各个路径组使用的配队名称(英文分号;分隔)" + }, + { + "name": "skipTimeRanges", + "type": "input-text", + "label": "输入不在某时执行(整数)" + }, + { + "name": "infoFileName", + "type": "input-text", + "label": "输入用于存储信息的文件名,只在不同账号分别管理CD时填写" + } +] diff --git a/repo/js/采集cd管理/将地图追踪文件放到pathing文件夹.txt b/repo/js/采集cd管理/将地图追踪文件放到pathing文件夹.txt new file mode 100644 index 00000000..e69de29b diff --git a/repo/js/采集cd管理/采集物cd类型(仅供参考).xlsx b/repo/js/采集cd管理/采集物cd类型(仅供参考).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..08fa99bd3a22123b972e4ba2c60be6a1b4bedbc4 GIT binary patch literal 12980 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 literal 0 HcmV?d00001