Files
bettergi-scripts-list/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js
yan dcc050074d 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 算法开关和改进的半自动模式选项
2026-01-16 23:07:43 +08:00

294 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 分钟 小时 日期 月份 星期 [年份可选]
const cronRegex = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)(?:\s+(\S+))?$/;
/**
* 解析单个 cron 字段,返回匹配的数值数组
* @param {string} field - 字段值 如 "1,3-5,* /10"
* @param {number} min - 最小值
* @param {number} max - 最大值
* @param {number} [stepBase=0] - 步进基准星期从0或1开始都支持
* @returns {Set<number>} 所有匹配的数值集合
*/
function parseCronField(field, min, max, stepBase = 0) {
const result = new Set();
const parts = field.split(',');
for (const part of parts) {
// 处理 */n 格式
if (part.startsWith('*/')) {
const step = parseInt(part.slice(2), 10);
if (isNaN(step) || step <= 0) continue;
for (let i = min; i <= max; i += step) {
result.add(i);
}
continue;
}
// 处理 n-n/n 格式
if (part.includes('/')) {
const [range, stepStr] = part.split('/');
const step = parseInt(stepStr, 10);
if (isNaN(step) || step <= 0) continue;
if (range === '*') {
for (let i = min; i <= max; i += step) {
result.add(i);
}
} else if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
if (isNaN(start) || isNaN(end)) continue;
for (let i = Math.max(min, start); i <= Math.min(max, end); i += step) {
result.add(i);
}
}
continue;
}
// 处理范围 n-n
if (part.includes('-')) {
const [start, end] = part.split('-').map(Number);
if (!isNaN(start) && !isNaN(end)) {
for (let i = Math.max(min, start); i <= Math.min(max, end); i++) {
result.add(i);
}
}
continue;
}
// 处理单个数字 / 列表
const num = parseInt(part, 10);
if (!isNaN(num) && num >= min && num <= max) {
result.add(num);
}
// 处理 L最后一天 - 只对日期字段有意义,这里简单处理
if (part === 'L' && max === 31) {
// 实际使用时需要知道当月天数,这里先占位
result.add(99); // 特殊标记,后续需处理
}
// ? 在日期/星期中代表「无特定值」 - 这里简单忽略具体值
if (part === '?') {
// 实际使用时需配合另一字段判断
}
}
return result;
}
/**
* 简单版 cron 解析器(只解析出每个字段允许的时间值)
* @param {string} cron - cron表达式 "0 9 * * 1-5"
* @returns {object|null} 解析结果 或 null格式错误
*/
function parseCron(cron) {
const match = cron.trim().match(cronRegex);
if (!match) return null;
const [, min, hour, day, month, dow] = match;
try {
const minutes = parseCronField(min, 0, 59);
const hours = parseCronField(hour, 0, 23);
const days = parseCronField(day, 1, 31);
const months = parseCronField(month, 1, 12);
const dows = parseCronField(dow, 0, 7); // 0和7都代表周日
// 星期字段 7 → 0
if (dows.has(7)) dows.add(0);
return {
minutes: [...minutes].sort((a, b) => a - b),
hours: [...hours].sort((a, b) => a - b),
days: [...days].sort((a, b) => a - b),
months: [...months].sort((a, b) => a - b),
dows: [...dows].sort((a, b) => a - b),
// 注days和dows同时有特定值时实际cron是「或」关系
// 这里仅简单分开列出,真实调度需复杂判断
original: cron.trim(),
isValid: true
};
} catch (e) {
return {isValid: false, error: e.message, original: cron};
}
}
/**
* 获取下一个Cron时间戳
* @param {string} cronExpression - Cron表达式
* @param {number} [startTimestamp=Date.now()] - 开始时间戳,默认为当前时间
* @param {number} endTimestamp - 结束时间戳
* @returns {Promise} 返回一个Promise解析为下一个Cron时间戳
*/
async function getNextCronTimestamp(cronExpression, startTimestamp = Date.now(), endTimestamp, url) {
const result = await http.request("POST", url, JSON.stringify({
cronExpression: `${cronExpression}`,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp
}), JSON.stringify({
"Content-Type": "application/json"
})).then(res => {
log.debug(`[{0}]res=>{1}`, 'next', JSON.stringify(res))
if (res.status_code === 200 && res.body) {
let result_json = JSON.parse(res.body);
if (result_json?.code === 200) {
return result_json?.data
}
throw new Error("请求失败,error:" + result_json?.message)
}
return undefined
})
return result === null || !result ? undefined : result
}
//影响到性能 改http 第三方
// function getNextCronTimestamp(cron, fromTime = Date.now(),endTime) {
// const parts = cron.trim().split(/\s+/);
// if (parts.length < 5 || parts.length > 6) {
// throw new Error("不支持的 cron 格式,应为 5~6 段");
// }
//
// const [minStr, hourStr, dayStr, monthStr, dowStr] = parts;
//
// // 解析每个字段
// const minutes = parseField(minStr, 0, 59);
// const hours = parseField(hourStr, 0, 23);
// const days = parseField(dayStr, 1, 31);
// const months = parseField(monthStr, 1, 12);
// const dows = parseField(dowStr, 0, 7);
//
// // 星期 7 → 0 (周日)
// if (dows.has(7)) dows.add(0);
//
// let current = new Date(fromTime);
// // 如果没有指定 endTime默认设置为明天 00:00:00
// if (endTime === undefined) {
// const tomorrow = new Date(current);
// tomorrow.setDate(tomorrow.getDate() + 1);
// tomorrow.setHours(0, 0, 0, 0);
// endTime = tomorrow.getTime();
// }
//
// // 动态计算最大迭代次数
// // 将时间差(毫秒)转换为分钟,向上取整,确保覆盖所有可能的分钟点
// const timeDiffMinutes = Math.ceil((endTime - fromTime) / 60000);
//
// // 设置最大迭代次数,防止意外情况(如 endTime 极大)导致内存溢出或死循环
// // 即使 endTime 是 10 年后,也限制在约 2 年内(避免极端情况)
// // 2年 ≈ 365 * 2 * 24 * 60 = 1,051,200
// const MAX_ITERATIONS_LIMIT = 1051200;
// const MAX_ITERATIONS = Math.min(timeDiffMinutes, MAX_ITERATIONS_LIMIT);
//
// let iteration = 0;
// while (iteration++ < MAX_ITERATIONS) {
// // 先推进到下一分钟,避免死循环在同一分钟
// current.setMinutes(current.getMinutes() + 1);
// current.setSeconds(0);
// current.setMilliseconds(0);
//
// const m = current.getMinutes();
// const h = current.getHours();
// const d = current.getDate();
// const mon = current.getMonth() + 1; // JS 月份 0~11
// const dow = current.getDay(); // 0=周日, 1=周一, ..., 6=周六
//
// // 核心匹配条件(日期和星期是 OR 关系)
// const minuteMatch = minutes.has(m) || minutes.size === 0;
// const hourMatch = hours.has(h) || hours.size === 0;
// const monthMatch = months.has(mon) || months.size === 0;
// const dayMatch = days.has(d) || days.size === 0;
// const dowMatch = dows.has(dow) || dows.size === 0;
//
// const dateOrDowMatch = (days.size === 0 && dows.size === 0) || // 两者都是 *
// (days.size > 0 && dows.size === 0) || // 只指定了日期
// (days.size === 0 && dows.size > 0) || // 只指定了星期
// (dayMatch && dowMatch); // 两者都满足才算(最严格)
//
// if (minuteMatch && hourMatch && monthMatch && dateOrDowMatch) {
// return current.getTime();
// }
// }
//
// // 如果是因为超过 MAX_ITERATIONS_LIMIT 而退出,说明时间跨度太大
// if (timeDiffMinutes > MAX_ITERATIONS_LIMIT) {
// log.warn("查找范围过大,已达到最大迭代次数限制");
// } else {
// log.warn("未找到合理下一次执行时间");
// }
// return null;
// }
/**
* 解析单个 cron 字段,返回匹配的数值 Set
* 支持: * , - / 数值列表 * /n
*/
function parseField(field, min, max) {
const result = new Set();
if (field === '*' || field === '?') {
return result; // 空 set 代表任意
}
const parts = field.split(',');
for (let part of parts) {
part = part.trim();
// */n
if (part.startsWith('*/')) {
const step = Number(part.slice(2));
if (!isNaN(step) && step > 0) {
for (let i = min; i <= max; i += step) {
result.add(i);
}
}
continue;
}
// n-n
if (part.includes('-') && !part.includes('/')) {
const [start, end] = part.split('-').map(Number);
if (!isNaN(start) && !isNaN(end)) {
for (let i = Math.max(min, start); i <= Math.min(max, end); i++) {
result.add(i);
}
}
continue;
}
// n/n 或 */n 已处理,剩下是普通数字或范围/步进
if (part.includes('/')) {
const [range, stepStr] = part.split('/');
const step = Number(stepStr);
if (isNaN(step) || step <= 0) continue;
if (range === '*') {
for (let i = min; i <= max; i += step) result.add(i);
} else {
const [start, end] = range.split('-').map(Number);
if (!isNaN(start) && !isNaN(end)) {
for (let i = Math.max(min, start); i <= Math.min(max, end); i += step) {
result.add(i);
}
}
}
continue;
}
// 单个数字
const num = Number(part);
if (!isNaN(num) && num >= min && num <= max) {
result.add(num);
}
}
return result;
}
this.cronUtil = {
getNextCronTimestamp,
}