diff --git a/repo/js/采集cd管理/README.md b/repo/js/采集cd管理/README.md index 55eaf2517..56126f254 100644 --- a/repo/js/采集cd管理/README.md +++ b/repo/js/采集cd管理/README.md @@ -42,6 +42,7 @@ - 根据所给表格,结合自身情况选择每个路径组的刷新cd类型 - 选择每个路径组要运行的文件夹 - 填写运行该组使用的配队名称(不填就不换队,有啥用啥) + - 其余自定义配置项参考自定义配置界面的介绍 4. 运行脚本: - 运行脚本,脚本将按照路径组的顺序依次执行任务。 @@ -53,4 +54,11 @@ - - 伪造日志:伪造日志功能仅用于日志分析,不会影响脚本的实际运行。 - 识别到满背包的物品将会被加入拾取黑名单,同时加入自定义配置中的 **禁用的路线的关键词** ,将会跳过文件路径包含该关键词的路线,其他你希望跳过的路线也可以使用该配置来跳过,如填写钟离来跳过部分需要钟离的挖矿路线(仅文件路径中包含钟离时有效,还是建议手动删除这些路线) -- 不同配置组中的本js仅共用相同账号的拾取黑名单,路线刷新cd,路径组等其他信息并不关联,settings.json仅供配置每个配置组使用,本身变化不影响其他配置组功能 \ No newline at end of file +- 不同配置组中的本js仅共用相同账号的拾取黑名单,路线刷新cd,路径组等其他信息并不关联,settings.json仅供配置每个配置组使用,本身变化不影响其他配置组功能 + +六、进阶配置 + - +1. 在自定义配置中勾选高级配置后展开即可查看 +2. 优先采集材料:按规则填写后,每天会尝试获取指定数量的材料后再进入路径组模式,注意,需要有足够多的拾取历史或文件路径包含目标材料的路径,其余路线无法被识别。 +3. 优先关键词:在路径组模式中,含有这些关键词的路线会被视为最高效率,配合效率降序排序或者最低效率配置项使用 +4. 路径组临界效率:执行路径组时,分均效率低于指定值的路线会被排除,特别的,无拾取记录或记录少于3次的路线会被视为恰好处于该临界,不会被排除 \ No newline at end of file diff --git a/repo/js/采集cd管理/assets/A00-塞洛海原(学习螃蟹技能).json b/repo/js/采集cd管理/assets/A00-塞洛海原(学习螃蟹技能).json new file mode 100644 index 000000000..196865648 --- /dev/null +++ b/repo/js/采集cd管理/assets/A00-塞洛海原(学习螃蟹技能).json @@ -0,0 +1,58 @@ +{ + "info": { + "name": "A00-塞洛海原(学习螃蟹技能)", + "type": "collect", + "authors": [ + { + "name": "芝士贝果", + "links": "https://github.com/cheese-bagel" + } + ], + "version": "1.0", + "description": "", + "map_name": "Teyvat", + "bgi_version": "0.50.0", + "tags": [], + "last_modified_time": 1755574896298, + "enable_monster_loot_split": false, + "map_match_method": "" + }, + "positions": [ + { + "id": 1, + "x": 4399.8906, + "y": 3083.4001, + "action": "", + "move_mode": "walk", + "action_params": "", + "type": "teleport" + }, + { + "id": 2, + "x": 4421.7334, + "y": 3081.2798, + "action": "combat_script", + "move_mode": "walk", + "action_params": "attack;wait(0.5);moveby(0,2285);wait(0.5);attack;click(middle)", + "type": "orientation" + }, + { + "id": 3, + "x": 4413.1172, + "y": 3106.9414, + "type": "orientation", + "move_mode": "walk", + "action": "combat_script", + "action_params": "attack;wait(0.5);moveby(0,2285);wait(0.5);attack;click(middle)" + }, + { + "id": 4, + "x": 4390.1826, + "y": 3099.9868, + "action": "combat_script", + "move_mode": "walk", + "action_params": "attack;wait(0.5);moveby(0,2285);wait(0.5);attack", + "type": "orientation" + } + ] +} \ No newline at end of file diff --git a/repo/js/采集cd管理/main.js b/repo/js/采集cd管理/main.js index 762873691..6da99a869 100644 --- a/repo/js/采集cd管理/main.js +++ b/repo/js/采集cd管理/main.js @@ -19,6 +19,9 @@ const accountName = settings.infoFileName || "默认账户"; // 定义目标文件夹路径和记录文件路径 const recordFolder = "record"; // 存储记录文件的文件夹路径 const defaultTimeStamp = "2023-10-13T00:00:00.000Z"; // 固定的时间戳 +let pickupRecordFile; +const MAX_PICKUP_DAYS = 30; + // 从 settings 中读取用户配置,并设置默认值 const userSettings = { @@ -95,6 +98,8 @@ FiconRo.InitTemplate(); const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150); +let underWater = false; + (async function () { /* ===== 零基构建 settings.json(BEGIN) ===== */ const SETTINGS_FILE = `settings.json`; @@ -205,6 +210,16 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) if (settings.enableMoreSettings) { newSettings.push( + { + "name": "priorityItems", + "type": "input-text", + "label": "优先采集材料,每天会尝试优先采集指定数量的目标物品,随后才执行路径组\n格式:材料名*数量,由加号+连接\n如萃凝晶*160+甜甜花*10" + }, + { + "name": "priorityItemsPartyName", + "type": "input-text", + "label": "优先采集材料使用的配队名称" + }, { "name": "priorityTags", "type": "input-text", @@ -221,6 +236,12 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) ], "default": "文件顺序,按在文件夹中位置顺序运行" }, + { + "name": "defaultEffPercentile", + "type": "input-text", + "label": "默认效率指数,范围0-1\n数值越大时,未知效率的路线被视作的默认效率越高", + "default": "0.5" + }, { "name": "weightedRule", "type": "input-text", @@ -267,7 +288,7 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) newSettings.push({ "name": `pathGroup${g}thresholdEfficiency`, "type": "input-text", - "label": `路径组${g}临界效率\n分均拾取个数效率低于临界效率的路线会被排除\n无历史记录或历史记录少于3次的路线会被视为恰好处于临界效率`, + "label": `路径组${g}临界效率\n分均拾取个数效率低于临界效率的路线会被排除`, "default": "0" }); } @@ -298,6 +319,7 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) // 获取子文件夹路径 const subFolderName = userSettings.infoFileName; const subFolderPath = `${recordFolder}/${subFolderName}`; + pickupRecordFile = `${recordFolder}/${subFolderName}/拾取记录.json`; // 读取子文件夹中的所有文件路径 const filesInSubFolder = file.ReadPathSync(subFolderPath); @@ -327,6 +349,14 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) /* 禁用BGI原生拾取,强制模板匹配 */ targetItems = await loadTargetItems(); + /* ===== 别名索引 ===== */ + const name2Other = new Map(); // 本名 → 别名数组 + const other2Name = new Map(); // 别名 → 本名 + for (const it of targetItems) { + name2Other.set(it.itemName, it.otherName || []); + for (const a of (it.otherName || [])) other2Name.set(a, it.itemName); + } + await loadBlacklist(true); state.running = true; @@ -408,274 +438,726 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) let cookInterval = 60 * 60 * 1000; let settimeInterval = 10 * 60 * 1000; + // ==================== 优先级材料前置采集 ==================== - // ==================== 路径组循环 ==================== - for (let i = 1; i <= groupCount; i++) { - const currentCdType = settings[`pathGroup${i}CdType`] || ""; - if (!currentCdType) continue; + if (settings.priorityItems) { + /* ---------- 1. 解析 ---------- */ + const priorityList = []; + const segments = settings.priorityItems.split('+').map(s => s.trim()); + for (const seg of segments) { + const [itemName, countStr] = seg.split('*').map(s => s.trim()); + if (itemName && countStr && !isNaN(Number(countStr))) { + priorityList.push({ itemName, count: Number(countStr) }); + } + } + log.info(`优先级材料解析完成: ${priorityList.map(e => `${e.itemName}*${e.count}`).join(', ')}`); + /* ===== 追加:扣除今日已拾取(UTC+8 0 点分界) ===== */ + const utc8 = new Date(Date.now() + 8 * 3600_000); // 手动+8小时 + const today = utc8.toISOString().slice(0, 10); // "YYYY-MM-DD" + let todayPicked = {}; // 今日已拾取数量 + try { + const txt = await file.readText(pickupRecordFile); + if (txt) { + const arr = JSON.parse(txt); + const todayItem = arr.find(it => it.date === today); + if (todayItem) todayPicked = todayItem.items || {}; + } + } catch (_) { /* 文件不存在或解析失败 */ } - const folder = folderNames[i - 1] || `路径组${i}`; - const targetFolder = `pathing/${folder}`; + /* 扣除今日已拾取:别名→本名 */ + for (let i = priorityList.length - 1; i >= 0; i--) { + const task = priorityList[i]; + let got = 0; + /* 先算本名 */ + got += todayPicked[task.itemName] || 0; + /* 再算别名 */ + const others = name2Other.get(task.itemName) || []; + for (const a of others) got += todayPicked[a] || 0; + task.count -= got; + if (task.count <= 0) priorityList.splice(i, 1); + } - /* 运行期同样用 Map 只改 cdTime */ - const rawRecord = await file.readText(recordFilePath); - let recordArray = JSON.parse(rawRecord); - const cdMap = new Map(recordArray.map(it => [it.fileName, it])); + if (priorityList.length === 0) { + log.info("今日优先材料已达标,跳过优先采集阶段"); + } + /* ================================= */ - const groupFiles = await readFolder(targetFolder, true); + /* ---------- 2. 材料→CD类型 映射表(仅列出现过的,其余默认 1次0点刷新)---------- */ + const materialCdMap = { + // 46h 特产 + "小灯草": "46小时刷新", + "嘟嘟莲": "46小时刷新", + "落落莓": "46小时刷新", + "塞西莉亚花": "46小时刷新", + "慕风蘑菇": "46小时刷新", + "蒲公英籽": "46小时刷新", + "钩钩果": "46小时刷新", + "风车菊": "46小时刷新", + "霓裳花": "46小时刷新", + "清心": "46小时刷新", + "琉璃袋": "46小时刷新", + "琉璃百合": "46小时刷新", + "夜泊石": "46小时刷新", + "绝云椒椒": "46小时刷新", + "星螺": "46小时刷新", + "石珀": "46小时刷新", + "清水玉": "46小时刷新", + "海灵芝": "46小时刷新", + "鬼兜虫": "46小时刷新", + "绯樱绣球": "46小时刷新", + "鸣草": "46小时刷新", + "珊瑚真珠": "46小时刷新", + "晶化骨髓": "46小时刷新", + "血斛": "46小时刷新", + "天云草实": "46小时刷新", + "幽灯蕈": "46小时刷新", + "沙脂蛹": "46小时刷新", + "月莲": "46小时刷新", + "帕蒂沙兰": "46小时刷新", + "树王圣体菇": "46小时刷新", + "圣金虫": "46小时刷新", + "万相石": "46小时刷新", + "悼灵花": "46小时刷新", + "劫波莲": "46小时刷新", + "赤念果": "46小时刷新", + "苍晶螺": "46小时刷新", + "海露花": "46小时刷新", + "柔灯铃": "46小时刷新", + "子探测单元": "46小时刷新", + "湖光铃兰": "46小时刷新", + "幽光星星": "46小时刷新", + "虹彩蔷薇": "46小时刷新", + "初露之源": "46小时刷新", + "浪沫羽鳃": "46小时刷新", + "灼灼彩菊": "46小时刷新", + "肉龙掌": "46小时刷新", + "青蜜莓": "46小时刷新", + "枯叶紫英": "46小时刷新", + "微光角菌": "46小时刷新", + "云岩裂叶": "46小时刷新", + "琉鳞石": "46小时刷新", + "奇异的「牙齿」": "46小时刷新", - if (userSettings.operationMode === "执行任务(若不存在索引文件则自动创建)") { - const groupNumber = i; - await genshin.returnMainUi(); + // 12h 素材 + "兽肉": "12小时刷新", + "禽肉": "12小时刷新", + "神秘的肉": "12小时刷新", + "鱼肉": "12小时刷新", + "鳗肉": "12小时刷新", + "螃蟹": "12小时刷新", + "蝴蝶翅膀": "12小时刷新", + "青蛙": "12小时刷新", + "发光髓": "12小时刷新", + "蜥蜴尾巴": "12小时刷新", + "晶核": "12小时刷新", + "鳅鳅宝玉": "12小时刷新", + // 4点 + "盐": "1次4点刷新", + "胡椒": "1次4点刷新", + "洋葱": "1次4点刷新", + "牛奶": "1次4点刷新", + "番茄": "1次4点刷新", + "卷心菜": "1次4点刷新", + "土豆": "1次4点刷新", + "小麦": "1次4点刷新", + "稻米": "1次4点刷新", + "虾仁": "1次4点刷新", + "豆腐": "1次4点刷新", + "杏仁": "1次4点刷新", + "发酵果实汁": "1次4点刷新", + "咖啡豆": "1次4点刷新", + "秃秃豆": "1次4点刷新", + + // 0点 + "甜甜花": "1次0点刷新", + "胡萝卜": "1次0点刷新", + "蘑菇": "1次0点刷新", + "松茸": "1次0点刷新", + "松果": "1次0点刷新", + "金鱼草": "1次0点刷新", + "莲蓬": "1次0点刷新", + "薄荷": "1次0点刷新", + "鸟蛋": "1次0点刷新", + "树莓": "1次0点刷新", + "白萝卜": "1次0点刷新", + "苹果": "1次0点刷新", + "日落果": "1次0点刷新", + "竹笋": "1次0点刷新", + "海草": "1次0点刷新", + "堇瓜": "1次0点刷新", + "星蕈": "1次0点刷新", + "墩墩桃": "1次0点刷新", + "须弥蔷薇": "1次0点刷新", + "香辛果": "1次0点刷新", + "枣椰": "1次0点刷新", + "泡泡桔": "1次0点刷新", + "汐藻": "1次0点刷新", + "茉洁草": "1次0点刷新", + "久雨莲": "1次0点刷新", + "沉玉仙茗": "24小时刷新", + "颗粒果": "1次0点刷新", + "烛伞蘑菇": "1次0点刷新", + "澄晶实": "1次0点刷新", + "红果果菇": "1次0点刷新", + "马尾": "1次0点刷新", + "烈焰花花蕊": "1次0点刷新", + "铁块": "1次0点刷新", + "白铁块": "2次0点刷新", + "星银矿石": "2次0点刷新", + "水晶块": "3次0点刷新", + "紫晶块": "3次0点刷新", + "萃凝晶": "3次0点刷新", + "虹滴晶": "3次0点刷新", + "苦种": "1次0点刷新", + "烬芯花": "1次0点刷新" + }; + + + + let changedParty = false; + + /* ---------- 3. 主循环 ---------- */ + while (priorityList.length > 0) { + + const priorityItemSet = new Set(priorityList.map(p => p.itemName)); + for (const a of priorityItemSet) { + const others = name2Other.get(a) || []; + for (const o of others) priorityItemSet.add(o); // 别名也加入 + } + + const pickedCounter = {}; + priorityItemSet.forEach(n => pickedCounter[n] = 0); + /* ===== 每轮开始输出剩余物品 ===== */ + log.info(`剩余目标材料 ${priorityList.map(t => `${t.itemName}*${t.count}`).join(', ')}`); + /* 4-1 扫描 + 读 record + 前置过滤(禁用/时间/材料相关)+ 计算效率 + CD后置排除 */ + const allFiles = await readFolder('pathing', true); + const rawRecord = await file.readText(`${recordFolder}/${subFolderName}/record.json`); + let recordArray = []; + try { recordArray = JSON.parse(rawRecord); } catch { /* 空记录 */ } + const cdMap = new Map(recordArray.map(it => [it.fileName, it])); + const now = new Date(); + /* 时间管制 */ + if (await isTimeRestricted(settings.timeRule, 10)) { priorityList.length = 0; break; } + + /* ---- 先算效率(不判CD)---- */ + for (const file of allFiles) { + const fullName = file.fileName; + const rec = cdMap.get(fullName); + + /* 禁用关键词 */ + let skip = false; + for (const kw of disableArray) { if (file.fullPath.includes(kw)) { skip = true; break; } } + if (skip) { file._priorityEff = -1; continue; } + + /* 材料相关 */ + const pathHit = [...priorityItemSet].some(n => file.fullPath.includes(n)); + const histHit = rec?.history?.some(log => + Object.keys(log.items).some(name => priorityItemSet.has(name)) + ) ?? false; + let descHit = false; + if (file.description) { + descHit = [...priorityItemSet].some(kw => file.description.includes(kw)); + } + if (!pathHit && !histHit && !descHit) { + file._priorityEff = -1; + continue; + } + + /* 计算仅看优先材料的分均效率 */ + let eff = -2; // 未知标记 + if (rec?.history && rec.history.length >= 3) { + const effList = rec.history.map(log => { + const total = Object.entries(log.items) + .filter(([name]) => priorityItemSet.has(name)) + .reduce((sum, [, cnt]) => sum + cnt, 0); + return (total / log.durationSec) * 60; + }); + eff = effList.reduce((a, b) => a + b, 0) / effList.length; + } + file._priorityEff = eff; + } + + /* ---- 用可运行路线算分位默认值 ---- */ + const knownEff = allFiles + .filter(f => { + const rec = cdMap.get(f.fileName); + const nextCD = rec ? new Date(rec.cdTime) : new Date(0); + return f._priorityEff >= 0 && now > nextCD; + }) + .map(f => f._priorityEff) + .sort((a, b) => a - b); + let defaultEff; + if (knownEff.length === 0) { + defaultEff = 1; + } else { + const rawPct = settings.defaultEffPercentile; + const pct = Math.max(0, Math.min(1, rawPct === "" ? 0.5 : Number(rawPct))); + const idx = Math.ceil(pct * knownEff.length) - 1; + defaultEff = knownEff[Math.max(0, idx)]; + } + /* 回填未知 + 排除CD */ + allFiles.forEach(f => { + if (f._priorityEff === -2) f._priorityEff = defaultEff; + const rec = cdMap.get(f.fileName); + const nextCD = rec ? new Date(rec.cdTime) : new Date(0); + if (now <= nextCD) f._priorityEff = -1; + }); + + if (priorityList.length === 0) break; + + /* 4-2 只跑最高效率路线 */ + const candidateRoutes = allFiles.filter(f => f._priorityEff >= 0) + .sort((a, b) => b._priorityEff - a._priorityEff); + if (candidateRoutes.length === 0) { + log.info('已无可用优先路线(可能全部在CD或已达标),退出优先采集阶段'); + break; + } + const bestRoute = candidateRoutes[0]; + const filePath = bestRoute.fullPath; + const fileName = basename(filePath).replace('.json', ''); + const fullName = fileName + '.json'; + const targetObj = cdMap.get(fullName); + const startTime = new Date(); + + if (!changedParty && settings.priorityItemsPartyName) { + await switchPartyIfNeeded(settings.priorityItemsPartyName); + changedParty = true; + } + let timeNow = new Date(); + if (Foods.length != 0 && (((timeNow - lastCookTime) > cookInterval) || firstCook)) { + firstCook = false; + await ingredientProcessing(); + lastCookTime = new Date(); + } + + if (settings.setTimeMode && settings.setTimeMode != "不调节时间" && (((timeNow - lastsettimeTime) > settimeInterval) || firstsettime)) { + firstsettime = false; + if (settings.setTimeMode === "尽量调为白天") { + await pathingScript.runFile("assets/调为白天.json"); + } else { + await pathingScript.runFile("assets/调为夜晚.json"); + } + lastsettimeTime = new Date(); + } + await fakeLog(fileName, false, true, 0); + + /* ================================= */ + log.info(`当前进度:执行路线 ${fileName}`); + state.running = true; + const pickupTask = recognizeAndInteract(); + if (!underWater && filePath.includes('枫丹水下')) { + await pathingScript.runFile("assets/A00-塞洛海原(学习螃蟹技能).json"); + underWater = true; + } + if (underWater && !filePath.includes('枫丹水下')) { + underWater = false; + } try { - const filePaths = groupFiles.map(f => f.fullPath); - let changedParty = false; + await pathingScript.runFile(filePath); + } catch (e) { + log.error(`优先采集路线执行失败: ${filePath}`); + state.running = false; await pickupTask; continue; + } + state.running = false; await pickupTask; + await fakeLog(fileName, false, false, 0); + /* 4-3 扣除进度:别名→本名 */ + state.runPickupLog.forEach(name => { + const realName = other2Name.get(name) || name; // 别名→本名 + if (priorityItemSet.has(name) || priorityItemSet.has(realName)) { + pickedCounter[realName] = (pickedCounter[realName] || 0) + 1; + } + }); - /* ================== 提前计算分均效率(所有模式通用) ================== */ - // 0) 解析优先关键词 - const priorityKeywords = settings.priorityTags - ? settings.priorityTags.split(',').map(s => s.trim()).filter(Boolean) - : []; + /* ===== 追加:立即把 pickedCounter 回写到 priorityList ===== */ + for (const task of priorityList) { + const left = task.count - (pickedCounter[task.itemName] || 0); + task.count = Math.max(0, left); // 防止负数 + } + /* 倒序删除已达标项 */ + for (let i = priorityList.length - 1; i >= 0; i--) { + if (priorityList[i].count <= 0) { + log.info(`优先材料已达标: ${priorityList[i].itemName}`); + priorityList.splice(i, 1); + } + } - // 1) 解析加权规则 - const weightMap = new Map(); - if (settings.weightedRule) { - settings.weightedRule - .split(',') - .map(s => s.trim()) - .forEach(rule => { - const [item, wStr] = rule.split('*'); - if (item && wStr) { - const w = Number(wStr); - weightMap.set(item, isNaN(w) ? 1 : w); - } - }); + /* ================================================ */ + + /* 4-4 计算CD(掉落材料决定)*/ + const timeDiff = new Date() - startTime; + if (timeDiff > 3000) { + /* 1) 如果runPickupLog中不含优先材料,则按其他材料查找,使用最晚刷新时间 */ + let hasPriority = state.runPickupLog.some(name => priorityItemSet.has(name)); + let hitMaterials; + if (hasPriority) { + hitMaterials = [...new Set(state.runPickupLog.filter(n => priorityItemSet.has(n)))]; + } else { + /* 非优先材料也按同一张表查CD */ + hitMaterials = [...new Set(state.runPickupLog)]; } - // 2) 先计算一次基础效率,并找出全局最大效率 - filePaths.forEach(p => { - const fullName = basename(p); - const obj = cdMap.get(fullName); - let avgEff = 0; + let latestCD = new Date(0); // 初始极小值 + let foundAny = false; + hitMaterials.forEach(name => { + const cdType = materialCdMap[name] || "1次0点刷新"; + let tmpDate = new Date(startTime); + switch (cdType) { + case "1次0点刷新": + tmpDate.setDate(tmpDate.getDate() + 1); + tmpDate.setHours(0, 0, 0, 0); + break; + case "2次0点刷新": + tmpDate.setDate(tmpDate.getDate() + 2); + tmpDate.setHours(0, 0, 0, 0); + break; + case "3次0点刷新": + tmpDate.setDate(tmpDate.getDate() + 3); + tmpDate.setHours(0, 0, 0, 0); + break; + case "1次4点刷新": + tmpDate.setHours(4, 0, 0, 0); + if (tmpDate <= startTime) tmpDate.setDate(tmpDate.getDate() + 1); + break; + case "12小时刷新": + tmpDate = new Date(startTime.getTime() + 12 * 60 * 60 * 1000); + break; + case "24小时刷新": + tmpDate = new Date(startTime.getTime() + 24 * 60 * 60 * 1000); + break; + case "46小时刷新": + tmpDate = new Date(startTime.getTime() + 46 * 60 * 60 * 1000); + break; + default: + tmpDate.setDate(tmpDate.getDate() + 1); + tmpDate.setHours(0, 0, 0, 0); + } + if (tmpDate > latestCD) latestCD = tmpDate; + foundAny = true; + }); - if (obj && obj.history && obj.history.length >= 3) { - const effList = obj.history.map(log => { - const total = Object.entries(log.items).reduce((sum, [name, cnt]) => { - const w = blacklistSet.has(name) ? 0 : (weightMap.get(name) ?? 1); - return sum + cnt * w; - }, 0); - return (total / log.durationSec) * 60; - }); - avgEff = effList.reduce((a, b) => a + b, 0) / effList.length; + /* 兜底:没有任何材料被识别到,按1次0点刷新 */ + if (!foundAny) { + latestCD = new Date(startTime); + latestCD.setDate(latestCD.getDate() + 1); + latestCD.setHours(0, 0, 0, 0); + } + + const durationSec = Math.round(timeDiff / 1000); + const itemCounter = {}; + state.runPickupLog.forEach(n => { itemCounter[n] = (itemCounter[n] || 0) + 1; }); + if (!targetObj.history) targetObj.history = []; + targetObj.history.push({ items: itemCounter, durationSec }); + if (targetObj.history.length > 7) targetObj.history = targetObj.history.slice(-7); + targetObj.cdTime = latestCD.toISOString(); + await file.writeText(recordFilePath, + JSON.stringify(Array.from(cdMap.values()), null, 2)); + + await appendDailyPickup(state.runPickupLog); + state.runPickupLog = []; + + } + + } + } + let runnedAnyPath = true; + // ==================== 路径组循环 ==================== + while (runnedAnyPath) { + runnedAnyPath = false; + if (await isTimeRestricted(settings.timeRule, 10)) break; + for (let i = 1; i <= groupCount; i++) { + if (await isTimeRestricted(settings.timeRule, 10)) break; + const currentCdType = settings[`pathGroup${i}CdType`] || ""; + if (!currentCdType) continue; + + const folder = folderNames[i - 1] || `路径组${i}`; + const targetFolder = `pathing/${folder}`; + + /* 运行期同样用 Map 只改 cdTime */ + const rawRecord = await file.readText(recordFilePath); + let recordArray = JSON.parse(rawRecord); + const cdMap = new Map(recordArray.map(it => [it.fileName, it])); + + const groupFiles = await readFolder(targetFolder, true); + + if (userSettings.operationMode === "执行任务(若不存在索引文件则自动创建)") { + const groupNumber = i; + await genshin.returnMainUi(); + + try { + const filePaths = groupFiles.map(f => f.fullPath); + let changedParty = false; + + /* ================== 提前计算分均效率(所有模式通用) ================== */ + // 0) 解析优先关键词 + const priorityKeywords = settings.priorityTags + ? settings.priorityTags.split(',').map(s => s.trim()).filter(Boolean) + : []; + + // 1) 解析加权规则 + const weightMap = new Map(); + if (settings.weightedRule) { + settings.weightedRule + .split(',') + .map(s => s.trim()) + .forEach(rule => { + const [item, wStr] = rule.split('*'); + if (item && wStr) { + const w = Number(wStr); + weightMap.set(item, isNaN(w) ? 1 : w); + } + }); + } + + // 2) 先计算一次基础效率(未知路线先标 -1) + filePaths.forEach(p => { + const fullName = basename(p); + const obj = cdMap.get(fullName); + let avgEff = -1; // 先标记为“未知” + + if (obj && obj.history && obj.history.length >= 3) { + const effList = obj.history.map(log => { + const total = Object.entries(log.items).reduce((sum, [name, cnt]) => { + const w = blacklistSet.has(name) ? 0 : (weightMap.get(name) ?? 1); + return sum + cnt * w; + }, 0); + return (total / log.durationSec) * 60; + }); + avgEff = effList.reduce((a, b) => a + b, 0) / effList.length; + } + p._efficiency = avgEff; // 已知路线存真实效率,未知路线存 -1 + }); + + // 3) 计算默认效率(分位值) + const knownEff = filePaths + .map(p => p._efficiency) + .filter(e => e >= 0) // 只保留已知路线 + .sort((a, b) => a - b); + + let defaultEff; + if (knownEff.length === 0) { + // 一条已知路线都没有 → 回退到老逻辑 + defaultEff = Number(settings[`pathGroup${i}thresholdEfficiency`]) || 0; } else { + // 按配置的分位取默认效率 + const rawPct = settings.defaultEffPercentile; + const pct = Math.max(0, Math.min(1, rawPct === "" ? 0.5 : Number(rawPct))); + const idx = Math.ceil(pct * knownEff.length) - 1; + defaultEff = knownEff[Math.max(0, idx)]; + } + + // 4) 把 -1 的未知路线替换成默认效率 + filePaths.forEach(p => { + if (p._efficiency === -1) p._efficiency = defaultEff; + }); + + // 5) 计算全局最大效率值(已含默认效率) + const maxEff = Math.max(...filePaths.map(p => p._efficiency), 0); + + // 6) 优先关键词加分(逻辑不变) + filePaths.forEach(p => { + const fullName = basename(p); + const obj = cdMap.get(fullName); + + const itemHit = obj?.history?.some(log => + Object.keys(log.items).some(item => + priorityKeywords.some(key => item.includes(key)) + ) + ); + const pathHit = priorityKeywords.some(key => p.includes(key)); + const descHit = priorityKeywords.some(key => (p.description || '').includes(key)); + + if (itemHit || pathHit || descHit) { + p._efficiency += maxEff + 1; + } + }); + + /* ================== 排序分支 ================== */ + switch (settings.sortMode) { + case "优先最早刷新,将优先执行最早刷新的路线": + filePaths.sort((a, b) => { + const nameA = basename(a); + const nameB = basename(b); + const timeA = cdMap.has(nameA) ? new Date(cdMap.get(nameA).cdTime) : new Date(0); + const timeB = cdMap.has(nameB) ? new Date(cdMap.get(nameB).cdTime) : new Date(0); + return timeA - timeB; // 越早刷新越靠前 + }); + break; + + case "优先最高效率,将优先执行最高分均拾取物的路线": + // 直接复用提前算好的 _efficiency + filePaths.sort((a, b) => (b._efficiency || 0) - (a._efficiency || 0)); + break; + + default: + // 保持原有顺序,不做任何排序 + break; + } + + for (const filePath of filePaths) { + const fileName = basename(filePath).replace('.json', ''); + const fullName = fileName + '.json'; + const targetObj = cdMap.get(fullName); + const nextCD = targetObj ? new Date(targetObj.cdTime) : new Date(0); + + const startTime = new Date(); + if (startTime <= nextCD) { + log.info(`当前任务 ${fileName} 未刷新,跳过任务`); + continue; // 跳过,不写回 + } + if (await isTimeRestricted(settings.timeRule, 10)) break; + + let doSkip = false; + for (const kw of disableArray) { + if (filePath.includes(kw)) { + log.info(`路径文件 ${filePath} 包含禁用关键词 "${kw}",跳过任务 ${fileName}`); + doSkip = true; break; + } + } + if (doSkip) continue; + + // ===== 临界效率过滤 ===== + const routeEff = filePath._efficiency ?? 0; // 提前算好的分均效率 const threshold = Number(settings[`pathGroup${i}thresholdEfficiency`]) || 0; - avgEff = threshold; + if (routeEff < threshold) { + log.info(`路线 ${fileName} 分均效率为 ${routeEff.toFixed(2)},低于设定的临界值 ${threshold},跳过`); + continue; + } + + let timeNow = new Date(); + if (Foods.length != 0 && (((timeNow - lastCookTime) > cookInterval) || firstCook)) { + firstCook = false; + await ingredientProcessing(); + lastCookTime = new Date(); + } + + if (settings.setTimeMode && settings.setTimeMode != "不调节时间" && (((timeNow - lastsettimeTime) > settimeInterval) || firstsettime)) { + firstsettime = false; + if (settings.setTimeMode === "尽量调为白天") { + await pathingScript.runFile("assets/调为白天.json"); + } else { + await pathingScript.runFile("assets/调为夜晚.json"); + } + lastsettimeTime = new Date(); + } + + if (!changedParty) { + await switchPartyIfNeeded(partyNames[groupNumber - 1]); + changedParty = true; + } + await fakeLog(fileName, false, true, 0); + + /* ========== 历史拾取物前置排序 ========== */ + // 0) 只有 history 里出现过的物品才需要前置 + const historyItemSet = new Set(); + const routeRec = cdMap.get(fullName); + if (routeRec?.history) { + routeRec.history.forEach(log => { + Object.keys(log.items).forEach(name => historyItemSet.add(name)); + }); + } + + // 1) 把 targetItems 拆成「历史出现」+「未出现」两部分 + const frontPart = []; + const backPart = []; + for (const it of targetItems) { + (historyItemSet.has(it.itemName) ? frontPart : backPart).push(it); + } + + // 2) 合并后重新赋值,完成前置 + targetItems = [...frontPart, ...backPart]; + /* ======================================= */ + + state.running = true; + const pickupTask = recognizeAndInteract(); + runnedAnyPath = true; + + log.info(`当前进度:路径组${i} ${folder} ${fileName} 为第 ${filePaths.indexOf(filePath) + 1}/${filePaths.length} 个`); + if (!underWater && filePath.includes('枫丹水下')) { + await pathingScript.runFile("assets/A00-塞洛海原(学习螃蟹技能).json"); + underWater = true; + } + if (underWater && !filePath.includes('枫丹水下')) { + underWater = false; + } + try { + state.runPickupLog = []; // 新路线开始前清空 + await pathingScript.runFile(filePath); + } catch (error) { + log.error(`路径文件 ${filePath} 不存在或执行失败: ${error}`); + continue; + } + + try { await sleep(1); } + catch (error) { log.error(`发生错误: ${error}`); break; } + + const endTime = new Date(); + const timeDiff = endTime.getTime() - startTime.getTime(); + + await fakeLog(fileName, false, false, timeDiff); + state.running = false; + await pickupTask; + + // >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history) <<< + if (timeDiff > 3000) { + let newTimestamp = new Date(startTime); + + switch (currentCdType) { + case "1次0点刷新": + newTimestamp.setDate(newTimestamp.getDate() + 1); + newTimestamp.setHours(0, 0, 0, 0); break; + case "2次0点刷新": + newTimestamp.setDate(newTimestamp.getDate() + 2); + newTimestamp.setHours(0, 0, 0, 0); break; + case "3次0点刷新": + newTimestamp.setDate(newTimestamp.getDate() + 3); + newTimestamp.setHours(0, 0, 0, 0); break; + case "4点刷新": + newTimestamp.setHours(4, 0, 0, 0); + if (newTimestamp <= startTime) newTimestamp.setDate(newTimestamp.getDate() + 1); break; + case "12小时刷新": + newTimestamp = new Date(startTime.getTime() + 12 * 60 * 60 * 1000); break; + case "24小时刷新": + newTimestamp = new Date(startTime.getTime() + 24 * 60 * 60 * 1000); break; + case "46小时刷新": + newTimestamp = new Date(startTime.getTime() + 46 * 60 * 60 * 1000); break; + default: + newTimestamp = startTime; break; + } + + // ===== 把本次拾取明细写进 history ===== + const durationSec = Math.round(timeDiff / 1000); + const itemCounter = {}; + for (const name of state.runPickupLog) { + itemCounter[name] = (itemCounter[name] || 0) + 1; + } + const logEntry = { + items: itemCounter, + durationSec: durationSec + }; + if (!targetObj.history) targetObj.history = []; // 兜底 + targetObj.history.push(logEntry); + // 保留最多 7 条记录,超出的旧记录丢弃 + if (targetObj.history.length > 7) { + targetObj.history = targetObj.history.slice(-7); + } + + // 只改 cdTime,其余字段(含 history)保持 + targetObj.cdTime = newTimestamp.toISOString(); + await file.writeText(recordFilePath, + JSON.stringify(Array.from(cdMap.values()), null, 2)); + await appendDailyPickup(state.runPickupLog); + + // 清空本次记录 + state.runPickupLog = []; + + log.info(`本任务执行大于3秒,cd信息已更新,下一次可用时间为 ${newTimestamp.toLocaleString()}`); + } } - p._efficiency = avgEff; // 先存基础值 - }); - - // 3) 计算全局最大效率值 - const maxEff = Math.max(...filePaths.map(p => p._efficiency), 0); - - // 4) 优先关键词加分 - filePaths.forEach(p => { - const fullName = basename(p); - const obj = cdMap.get(fullName); - - // 4-1) 历史拾取物里是否含关键词 - const itemHit = obj?.history?.some(log => - Object.keys(log.items).some(item => - priorityKeywords.some(key => item.includes(key)) - ) - ); - - // 4-2) 文件路径(含文件名)是否含关键词 - const pathHit = priorityKeywords.some(key => p.includes(key)); - - if (itemHit || pathHit) { - p._efficiency += maxEff; // 把最大效率值直接加给它 - } - }); - - - /* ================== 排序分支 ================== */ - switch (settings.sortMode) { - case "优先最早刷新,将优先执行最早刷新的路线": - filePaths.sort((a, b) => { - const nameA = basename(a); - const nameB = basename(b); - const timeA = cdMap.has(nameA) ? new Date(cdMap.get(nameA).cdTime) : new Date(0); - const timeB = cdMap.has(nameB) ? new Date(cdMap.get(nameB).cdTime) : new Date(0); - return timeA - timeB; // 越早刷新越靠前 - }); - break; - - case "优先最高效率,将优先执行最高分均拾取物的路线": - // 直接复用提前算好的 _efficiency - filePaths.sort((a, b) => (b._efficiency || 0) - (a._efficiency || 0)); - break; - - default: - // 保持原有顺序,不做任何排序 - break; + log.info(`路径组${groupNumber} 的所有任务运行完成`); + } catch (error) { + log.error(`读取路径组文件时出错: ${error}`); } - - for (const filePath of filePaths) { - const fileName = basename(filePath).replace('.json', ''); - const fullName = fileName + '.json'; - const targetObj = cdMap.get(fullName); - const nextCD = targetObj ? new Date(targetObj.cdTime) : new Date(0); - - const startTime = new Date(); - if (startTime <= nextCD) { - log.info(`当前任务 ${fileName} 未刷新,跳过任务`); - continue; // 跳过,不写回 - } - if (await isTimeRestricted(settings.timeRule, 10)) break; - - let doSkip = false; - for (const kw of disableArray) { - if (filePath.includes(kw)) { - log.info(`路径文件 ${filePath} 包含禁用关键词 "${kw}",跳过任务 ${fileName}`); - doSkip = true; break; - } - } - if (doSkip) continue; - - // ===== 临界效率过滤 ===== - const routeEff = filePath._efficiency ?? 0; // 提前算好的分均效率 - const threshold = Number(settings[`pathGroup${i}thresholdEfficiency`]) || 0; - if (routeEff < threshold) { - log.info(`路线 ${fileName} 分均效率为 ${routeEff.toFixed(2)},低于设定的临界值 ${threshold},跳过`); - continue; - } - - let timeNow = new Date(); - if (Foods.length != 0 && (((timeNow - lastCookTime) > cookInterval) || firstCook)) { - firstCook = false; - await ingredientProcessing(); - lastCookTime = new Date(); - } - - if (settings.setTimeMode && settings.setTimeMode != "不调节时间" && (((timeNow - lastsettimeTime) > settimeInterval) || firstsettime)) { - firstsettime = false; - if (settings.setTimeMode === "尽量调为白天") { - await pathingScript.runFile("assets/调为白天.json"); - } else { - await pathingScript.runFile("assets/调为夜晚.json"); - } - lastsettimeTime = new Date(); - } - - if (!changedParty) { - await switchPartyIfNeeded(partyNames[groupNumber - 1]); - changedParty = true; - } - await fakeLog(fileName, false, true, 0); - - /* ========== 历史拾取物前置排序 ========== */ - // 0) 只有 history 里出现过的物品才需要前置 - const historyItemSet = new Set(); - const routeRec = cdMap.get(fullName); - if (routeRec?.history) { - routeRec.history.forEach(log => { - Object.keys(log.items).forEach(name => historyItemSet.add(name)); - }); - } - - // 1) 把 targetItems 拆成「历史出现」+「未出现」两部分 - const frontPart = []; - const backPart = []; - for (const it of targetItems) { - (historyItemSet.has(it.itemName) ? frontPart : backPart).push(it); - } - - // 2) 合并后重新赋值,完成前置 - targetItems = [...frontPart, ...backPart]; - /* ======================================= */ - - state.running = true; - const pickupTask = recognizeAndInteract(); - - log.info(`当前进度:路径组${i} ${folder} ${fileName} 为第 ${filePaths.indexOf(filePath) + 1}/${filePaths.length} 个`); - - try { - state.runPickupLog = []; // 新路线开始前清空 - await pathingScript.runFile(filePath); - } catch (error) { - log.error(`路径文件 ${filePath} 不存在或执行失败: ${error}`); - continue; - } - - try { await sleep(1); } - catch (error) { log.error(`发生错误: ${error}`); break; } - - const endTime = new Date(); - const timeDiff = endTime.getTime() - startTime.getTime(); - - await fakeLog(fileName, false, false, timeDiff); - state.running = false; - await pickupTask; - - // >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history) <<< - if (timeDiff > 3000) { - let newTimestamp = new Date(startTime); - - switch (currentCdType) { - case "1次0点刷新": - newTimestamp.setDate(newTimestamp.getDate() + 1); - newTimestamp.setHours(0, 0, 0, 0); break; - case "2次0点刷新": - newTimestamp.setDate(newTimestamp.getDate() + 2); - newTimestamp.setHours(0, 0, 0, 0); break; - case "3次0点刷新": - newTimestamp.setDate(newTimestamp.getDate() + 3); - newTimestamp.setHours(0, 0, 0, 0); break; - case "4点刷新": - newTimestamp.setHours(4, 0, 0, 0); - if (newTimestamp <= startTime) newTimestamp.setDate(newTimestamp.getDate() + 1); break; - case "12小时刷新": - newTimestamp = new Date(startTime.getTime() + 12 * 60 * 60 * 1000); break; - case "24小时刷新": - newTimestamp = new Date(startTime.getTime() + 24 * 60 * 60 * 1000); break; - case "46小时刷新": - newTimestamp = new Date(startTime.getTime() + 46 * 60 * 60 * 1000); break; - default: - newTimestamp = startTime; break; - } - - // ===== 把本次拾取明细写进 history ===== - const durationSec = Math.round(timeDiff / 1000); - const itemCounter = {}; - for (const name of state.runPickupLog) { - itemCounter[name] = (itemCounter[name] || 0) + 1; - } - const logEntry = { - items: itemCounter, - durationSec: durationSec - }; - if (!targetObj.history) targetObj.history = []; // 兜底 - targetObj.history.push(logEntry); - // 保留最多 7 条记录,超出的旧记录丢弃 - if (targetObj.history.length > 7) { - targetObj.history = targetObj.history.slice(-7); - } - - // 只改 cdTime,其余字段(含 history)保持 - targetObj.cdTime = newTimestamp.toISOString(); - await file.writeText(recordFilePath, - JSON.stringify(Array.from(cdMap.values()), null, 2)); - - // 清空本次记录 - state.runPickupLog = []; - - log.info(`本任务执行大于3秒,cd信息已更新,下一次可用时间为 ${newTimestamp.toLocaleString()}`); - } - } - log.info(`路径组${groupNumber} 的所有任务运行完成`); - } catch (error) { - log.error(`读取路径组文件时出错: ${error}`); } } } @@ -910,14 +1392,18 @@ async function loadTargetItems() { it.roi.Threshold = itsThreshold; it.roi.InitTemplate(); - /* ---------- 2. 解析中括号内容 ---------- */ - const otherNames = []; - const bracketMatch = it.fullPath.matchAll(/\[(.*?)\]/g); - for (const m of bracketMatch) { - if (m[1].trim()) otherNames.push(m[1].trim()); + /* ---------- 2. 解析中括号内容 + 纯中文过滤 ---------- */ + const otherNames = new Set(); // 用 Set 去重 + // 2-1 中括号匹配 + for (const m of it.fullPath.matchAll(/\[(.*?)\]/g)) { + const pure = (m[1] || '').replace(/[^\u4e00-\u9fff]/g, '').trim(); + if (pure) otherNames.add(pure); } - it.otherName = otherNames; // 始终返回数组,无则为空数组 + // 2-2 若 itemName 本身含非中文,也生成纯中文别名 + const namePure = it.itemName.replace(/[^\u4e00-\u9fff]/g, '').trim(); + if (namePure && namePure !== it.itemName) otherNames.add(namePure); + it.otherName = Array.from(otherNames); // 转回数组 } catch (error) { log.error(`[loadTargetItems] ${it.fullPath}: ${error.message}`); } @@ -1052,57 +1538,55 @@ function removeJsonSuffix(fileName) { // 定义 readFolder 函数 async function readFolder(folderPath, onlyJson) { - // 新增一个堆栈,初始时包含 folderPath const folderStack = [folderPath]; - - // 新增一个数组,用于存储文件信息对象 const files = []; - // 当堆栈不为空时,继续处理 while (folderStack.length > 0) { - // 从堆栈中弹出一个路径 const currentPath = folderStack.pop(); - - // 读取当前路径下的所有文件和子文件夹路径 - const filesInSubFolder = file.ReadPathSync(currentPath); - - // 临时数组,用于存储子文件夹路径 + const filesInSubFolder = file.ReadPathSync(currentPath); // 同步读取 const subFolders = []; + for (const filePath of filesInSubFolder) { if (file.IsFolder(filePath)) { - // 如果是文件夹,先存储到临时数组中 subFolders.push(filePath); - } else { - if (filePath.endsWith(".js")) { - //跳过js结尾的文件 - continue; - } - // 如果是文件,根据 onlyJson 判断是否存储 - if (onlyJson) { - if (filePath.endsWith(".json")) { - const fileName = filePath.split('\\').pop(); // 提取文件名 - const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 - files.push({ - fullPath: filePath, - fileName: fileName, - folderPathArray: folderPathArray - }); - //log.info(`找到 JSON 文件:${filePath}`); - } - } else { - const fileName = filePath.split('\\').pop(); // 提取文件名 - const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 - files.push({ - fullPath: filePath, - fileName: fileName, - folderPathArray: folderPathArray - }); - //log.info(`找到文件:${filePath}`); - } + continue; } + + if (filePath.endsWith('.js')) continue; // 跳过 js + + // 仅 json 模式 + if (onlyJson) { + if (!filePath.endsWith('.json')) continue; + + let description = ''; + try { + // 同步读文本,避免 async 传染 + const txt = file.readTextSync(filePath); + const parsed = JSON.parse(txt); + description = parsed?.info?.description ?? ''; + } catch { + /* 读盘或解析失败就留空串 */ + } + + const fileName = filePath.split('\\').pop(); + const folderPathArray = filePath.split('\\').slice(0, -1); + + files.push({ + fullPath: filePath, + fileName, + folderPathArray, + description + }); + continue; + } + + const fileName = filePath.split('\\').pop(); + const folderPathArray = filePath.split('\\').slice(0, -1); + files.push({ fullPath: filePath, fileName, folderPathArray }); } - // 将临时数组中的子文件夹路径按原顺序压入堆栈 - folderStack.push(...subFolders.reverse()); // 反转子文件夹路径 + + // 子文件夹按原顺序入栈(深度优先) + folderStack.push(...subFolders.reverse()); } return files; @@ -1193,8 +1677,6 @@ async function isTimeRestricted(timeRule, threshold = 5) { return true; } } - - log.info("不处于限制时间"); return false; } @@ -1473,4 +1955,47 @@ async function ingredientProcessing() { } } await genshin.returnMainUi(); -} \ No newline at end of file +} + +/** + * 把本次路线的掉落合并到“拾取记录.json”中同一天条目(不含 durationSec) + * @param {string[]} pickupLog 本次路线的 state.runPickupLog + */ +async function appendDailyPickup(pickupLog) { + if (!pickupLog || !pickupLog.length) return; + + let oldArr = []; + try { + const txt = await file.readText(pickupRecordFile); + if (txt) oldArr = JSON.parse(txt); + } catch (_) { /* 文件不存在或解析失败 */ } + + // 统一按 UTC+8 的 0 点划分日期 + const utc8 = new Date(Date.now() + 8 * 3600_000); + const today = utc8.toISOString().slice(0, 10); // "YYYY-MM-DD" + + let todayItem = oldArr.find(e => e.date === today); + if (!todayItem) { + todayItem = { date: today, items: {} }; + oldArr.push(todayItem); + } + + const todayItems = todayItem.items; + pickupLog.forEach(name => { + todayItems[name] = (todayItems[name] || 0) + 1; + }); + + // 滑动窗口:只保留最近 MAX_PICKUP_DAYS 天 + if (oldArr.length > MAX_PICKUP_DAYS) oldArr = oldArr.slice(-MAX_PICKUP_DAYS); + + // 按日期倒序(最新在前) + oldArr.sort((a, b) => b.date.localeCompare(a.date)); + + // 写盘 + 异常捕获 + try { + await file.writeText(pickupRecordFile, JSON.stringify(oldArr, null, 2), false); + } catch (error) { + log.error(`appendDailyPickup 写盘失败: ${error.message}`); + } +} + diff --git a/repo/js/采集cd管理/manifest.json b/repo/js/采集cd管理/manifest.json index cede75a8a..ea78ef0ed 100644 --- a/repo/js/采集cd管理/manifest.json +++ b/repo/js/采集cd管理/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "采集cd管理", - "version": "2.4.1", + "version": "2.6.0", "bgi_version": "0.44.8", "description": "仅面对会操作文件和读readme的用户,基于文件夹操作自动管理采集路线的cd,会按照路径组的顺序依次运行,直到指定的时间,并会按照给定的cd类型,自动跳过未刷新的路线", "saved_files": [