mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-26 20:50:30 +08:00
feat: support cron in profile update interval (#964)
This commit is contained in:
parent
e20e46aebe
commit
c506d60e66
@ -33,6 +33,7 @@
|
||||
"axios": "^1.11.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"chokidar": "^4.0.3",
|
||||
"croner": "^9.1.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"express": "^5.1.0",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -38,6 +38,9 @@ importers:
|
||||
chokidar:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
croner:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@ -2650,6 +2653,10 @@ packages:
|
||||
cron-validator@1.4.0:
|
||||
resolution: {integrity: sha512-wGcJ9FCy65iaU6egSH8b5dZYJF7GU/3Jh06wzaT9lsa5dbqExjljmu+0cJ8cpKn+vUyZa/EM4WAxeLR6SypJXw==}
|
||||
|
||||
croner@9.1.0:
|
||||
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
|
||||
engines: {node: '>=18.0'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -8573,6 +8580,8 @@ snapshots:
|
||||
|
||||
cron-validator@1.4.0: {}
|
||||
|
||||
croner@9.1.0: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
@ -1,22 +1,37 @@
|
||||
import { addProfileItem, getCurrentProfileItem, getProfileConfig } from '../config'
|
||||
import { Cron } from 'croner'
|
||||
|
||||
const intervalPool: Record<string, NodeJS.Timeout> = {}
|
||||
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
||||
|
||||
export async function initProfileUpdater(): Promise<void> {
|
||||
const { items, current } = await getProfileConfig()
|
||||
const currentItem = await getCurrentProfileItem()
|
||||
|
||||
for (const item of items.filter((i) => i.id !== current)) {
|
||||
if (item.type === 'remote' && item.interval) {
|
||||
intervalPool[item.id] = setTimeout(
|
||||
async () => {
|
||||
if (typeof item.interval === 'number') {
|
||||
// 数字间隔使用setInterval
|
||||
intervalPool[item.id] = setInterval(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
} else if (typeof item.interval === 'string') {
|
||||
// 字符串间隔使用Cron
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
@ -24,17 +39,40 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentItem?.type === 'remote' && currentItem.interval) {
|
||||
intervalPool[currentItem.id] = setTimeout(
|
||||
async () => {
|
||||
if (typeof currentItem.interval === 'number') {
|
||||
intervalPool[currentItem.id] = setInterval(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000
|
||||
)
|
||||
|
||||
setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000 + 10000 // +10s
|
||||
)
|
||||
} else if (typeof currentItem.interval === 'string') {
|
||||
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000 + 10000 // +10s
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
@ -46,17 +84,32 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
if (item.type === 'remote' && item.interval) {
|
||||
if (intervalPool[item.id]) {
|
||||
clearTimeout(intervalPool[item.id])
|
||||
if (intervalPool[item.id] instanceof Cron) {
|
||||
(intervalPool[item.id] as Cron).stop()
|
||||
} else {
|
||||
clearInterval(intervalPool[item.id] as NodeJS.Timeout)
|
||||
}
|
||||
}
|
||||
intervalPool[item.id] = setTimeout(
|
||||
async () => {
|
||||
|
||||
if (typeof item.interval === 'number') {
|
||||
intervalPool[item.id] = setInterval(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
} else if (typeof item.interval === 'string') {
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ import { restartCore, addProfileUpdater } from '@renderer/utils/ipc'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
import { FaPlus } from 'react-icons/fa6'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isValidCron } from 'cron-validator';
|
||||
|
||||
interface Props {
|
||||
item: IProfileItem
|
||||
@ -100,15 +101,57 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('profiles.editInfo.interval')}>
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className={cn(inputWidth)}
|
||||
value={values.interval?.toString() ?? ''}
|
||||
onValueChange={(v) => {
|
||||
setValues({ ...values, interval: parseInt(v) })
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input
|
||||
size="sm"
|
||||
type="text"
|
||||
className={cn(
|
||||
inputWidth,
|
||||
// 不合法
|
||||
typeof values.interval === 'string' &&
|
||||
!/^\d+$/.test(values.interval) &&
|
||||
!isValidCron(values.interval, { seconds: false }) &&
|
||||
'border-red-500'
|
||||
)}
|
||||
value={values.interval?.toString() ?? ''}
|
||||
onValueChange={(v) => {
|
||||
// 输入限制
|
||||
if (/^[\d\s*\-,\/]*$/.test(v)) {
|
||||
// 纯数字
|
||||
if (/^\d+$/.test(v)) {
|
||||
setValues({ ...values, interval: parseInt(v, 10) || 0 });
|
||||
return;
|
||||
}
|
||||
// 非纯数字
|
||||
try {
|
||||
setValues({ ...values, interval: v });
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}}
|
||||
placeholder="例如:30 或 '0 * * * *'"
|
||||
/>
|
||||
|
||||
{/* 动态提示信息 */}
|
||||
<div className="text-xs" style={{
|
||||
color: typeof values.interval === 'string' &&
|
||||
!/^\d+$/.test(values.interval) &&
|
||||
!isValidCron(values.interval, { seconds: false })
|
||||
? '#ef4444'
|
||||
: '#6b7280'
|
||||
}}>
|
||||
{typeof values.interval === 'number' ? (
|
||||
'以分钟为单位的定时间隔'
|
||||
) : /^\d+$/.test(values.interval?.toString() || '') ? (
|
||||
'以分钟为单位的定时间隔'
|
||||
) : isValidCron(values.interval?.toString() || '', { seconds: false }) ? (
|
||||
'有效的Cron表达式'
|
||||
) : (
|
||||
'请输入数字或合法的Cron表达式(如:0 * * * *)'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('profiles.editInfo.fixedInterval')}>
|
||||
<Switch
|
||||
|
||||
@ -374,7 +374,7 @@
|
||||
"profiles.editInfo.name": "Name",
|
||||
"profiles.editInfo.url": "Subscription URL",
|
||||
"profiles.editInfo.useProxy": "Use Proxy to Update",
|
||||
"profiles.editInfo.interval": "Upd. Interval (min)",
|
||||
"profiles.editInfo.interval": "Upd. Interval",
|
||||
"profiles.editInfo.fixedInterval": "Fixed Update Interval",
|
||||
"profiles.editInfo.override.title": "Override",
|
||||
"profiles.editInfo.override.global": "Global",
|
||||
|
||||
@ -364,7 +364,7 @@
|
||||
"profiles.editInfo.name": "نام",
|
||||
"profiles.editInfo.url": "آدرس اشتراک",
|
||||
"profiles.editInfo.useProxy": "استفاده از پراکسی برای بهروزرسانی",
|
||||
"profiles.editInfo.interval": "فاصله بهروزرسانی (دقیقه)",
|
||||
"profiles.editInfo.interval": "فاصله بهروزرسانی",
|
||||
"profiles.editInfo.fixedInterval": "فاصله بهروزرسانی ثابت",
|
||||
"profiles.editInfo.override.title": "جایگزینی",
|
||||
"profiles.editInfo.override.global": "جهانی",
|
||||
|
||||
@ -364,7 +364,7 @@
|
||||
"profiles.editInfo.name": "Имя",
|
||||
"profiles.editInfo.url": "URL подписки",
|
||||
"profiles.editInfo.useProxy": "Использовать прокси для обновления",
|
||||
"profiles.editInfo.interval": "Интервал обн. (мин)",
|
||||
"profiles.editInfo.interval": "Интервал обн.",
|
||||
"profiles.editInfo.fixedInterval": "Фиксированный интервал обновления",
|
||||
"profiles.editInfo.override.title": "Переопределение",
|
||||
"profiles.editInfo.override.global": "Глобальный",
|
||||
|
||||
@ -379,7 +379,7 @@
|
||||
"profiles.editInfo.name": "名称",
|
||||
"profiles.editInfo.url": "订阅地址",
|
||||
"profiles.editInfo.useProxy": "使用代理更新",
|
||||
"profiles.editInfo.interval": "更新间隔(分钟)",
|
||||
"profiles.editInfo.interval": "更新间隔",
|
||||
"profiles.editInfo.fixedInterval": "固定更新间隔",
|
||||
"profiles.editInfo.override.title": "覆写",
|
||||
"profiles.editInfo.override.global": "全局",
|
||||
|
||||
2
src/shared/types.d.ts
vendored
2
src/shared/types.d.ts
vendored
@ -461,7 +461,7 @@ interface IProfileItem {
|
||||
name: string
|
||||
url?: string // remote
|
||||
file?: string // local
|
||||
interval?: number
|
||||
interval?: number | string
|
||||
home?: string
|
||||
updated?: number
|
||||
override?: string[]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user