feat(config): 添加七元素配置文件支持并优化密钥验证提示

- 新增 SevenElement.json 配置文件用于管理七元素数据
- 添加 json_path_name 常量对象统一管理配置文件路径
- 优化密钥不匹配错误提示,增加升级说明和文档查看建议
- 实现七元素配置的动态加载和合并功能
- 更新 CD 路径配置使用新的路径常量
- 改进七元素数据解析和排序逻辑

refactor(FullyAutoAndSemiAutoTools): 重构代码结构并优化配置管理

- 修改 getNextCronTimestamp 函数参数,移除默认 URL 参数
- 将全局变量重构为常量对象 auto、dev 和 cd,提高代码可维护性
- 更新团队配置属性名,从 teamFight 和 teamSevenElements 改为 team_fight 和 team_seven_elements
- 实现完整的初始化流程,包括工具模块动态加载和配置验证
- 添加 UID 设置持久化功能,支持不同账户的个性化配置
- 重构路径执行逻辑,优化黑白名单过滤和 CD 时间控制
- 移除过时的全局变量和初始化代码,精简代码结构
- 更新设置界面配置,新增 CD 算法开关和改进的半自动模式选项
This commit is contained in:
yan
2026-01-16 14:38:02 +08:00
parent 52ee973e41
commit dcc050074d
5 changed files with 532 additions and 412 deletions

View File

@@ -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": []
}
]

View File

@@ -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<void>} - 异步函数,没有返回值
*/
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<void>}
*/
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<void>} - 异步执行,没有返回值
*/
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)
}

View File

@@ -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://<ip:port>/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://<ip:port>/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": "调试快捷键(开发者)"
}
]

View File

@@ -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,