diff --git a/src/main/config/override.ts b/src/main/config/override.ts index a1b528d..cd493ec 100644 --- a/src/main/config/override.ts +++ b/src/main/config/override.ts @@ -89,7 +89,7 @@ export async function createOverride(item: Partial): Promise { +export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise { if (!existsSync(overridePath(id, ext))) { return '' } diff --git a/src/main/core/factory.ts b/src/main/core/factory.ts index 7a2b3ec..a60e2e3 100644 --- a/src/main/core/factory.ts +++ b/src/main/core/factory.ts @@ -6,10 +6,12 @@ import { getOverride, getOverrideItem } from '../config' -import { mihomoWorkConfigPath } from '../utils/dirs' +import { mihomoWorkConfigPath, overridePath } from '../utils/dirs' import yaml from 'yaml' import { readFile, writeFile } from 'fs/promises' import { deepMerge } from '../utils/merge' +import vm from 'vm' +import { writeFileSync } from 'fs' export async function generateProfile(): Promise { const { current } = await getProfileConfig() @@ -29,7 +31,7 @@ async function overrideProfile( const content = await getOverride(ov, item?.ext || 'js') switch (item?.ext) { case 'js': - profile = runOverrideScript(profile, content) + profile = runOverrideScript(profile, content, item) break case 'yaml': { const patch = yaml.parse(content) @@ -47,13 +49,45 @@ async function overrideProfile( return profile } -function runOverrideScript(profile: IMihomoConfig, script: string): IMihomoConfig { +function runOverrideScript( + profile: IMihomoConfig, + script: string, + item: IOverrideItem +): IMihomoConfig { + const log = (type: string, data: string, flag = 'a'): void => { + writeFileSync(overridePath(item.id, 'log'), `[${type}] ${data}\n`, { + encoding: 'utf-8', + flag + }) + } try { - const func = eval(`${script} main`) - const newProfile = func(profile) - if (typeof newProfile !== 'object') return profile + const ctx = { + console: Object.freeze({ + log(data: never) { + log('log', JSON.stringify(data)) + }, + info(data: never) { + log('info', JSON.stringify(data)) + }, + error(data: never) { + log('error', JSON.stringify(data)) + }, + debug(data: never) { + log('debug', JSON.stringify(data)) + } + }) + } + vm.createContext(ctx) + const code = `${script} main(${JSON.stringify(profile)})` + log('info', '开始执行脚本', 'w') + const newProfile = vm.runInContext(code, ctx) + if (typeof newProfile !== 'object') { + throw new Error('脚本返回值必须是对象') + } + log('info', '脚本执行成功') return newProfile } catch (e) { + log('exception', `脚本执行失败: ${e}`) return profile } } diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts index 477635b..97ff255 100644 --- a/src/main/utils/dirs.ts +++ b/src/main/utils/dirs.ts @@ -89,7 +89,7 @@ export function overrideConfigPath(): string { return path.join(dataDir(), 'override.yaml') } -export function overridePath(id: string, ext: 'js' | 'yaml'): string { +export function overridePath(id: string, ext: 'js' | 'yaml' | 'log'): string { return path.join(overrideDir(), `${id}.${ext}`) } diff --git a/src/renderer/src/components/override/exec-log-modal.tsx b/src/renderer/src/components/override/exec-log-modal.tsx new file mode 100644 index 0000000..61e7a26 --- /dev/null +++ b/src/renderer/src/components/override/exec-log-modal.tsx @@ -0,0 +1,58 @@ +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + Divider +} from '@nextui-org/react' +import React, { useEffect, useState } from 'react' +import { getOverride } from '@renderer/utils/ipc' +interface Props { + id: string + onClose: () => void +} +const ExecLogModal: React.FC = (props) => { + const { id, onClose } = props + const [logs, setLogs] = useState([]) + + const getLog = async (): Promise => { + setLogs((await getOverride(id, 'log')).split('\n').filter(Boolean)) + } + + useEffect(() => { + getLog() + }, []) + + return ( + + + 执行日志 + + {logs.map((log) => { + return ( + <> + {log} + + + ) + })} + + + + + + + ) +} + +export default ExecLogModal diff --git a/src/renderer/src/components/override/override-item.tsx b/src/renderer/src/components/override/override-item.tsx index 15f3dac..c430c79 100644 --- a/src/renderer/src/components/override/override-item.tsx +++ b/src/renderer/src/components/override/override-item.tsx @@ -15,6 +15,7 @@ import EditFileModal from './edit-file-modal' import EditInfoModal from './edit-info-modal' import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' +import ExecLogModal from './exec-log-modal' interface Props { info: IOverrideItem @@ -43,6 +44,13 @@ const menuItems: MenuItem[] = [ { key: 'edit-file', label: '编辑文件', + showDivider: false, + color: 'default', + className: '' + } as MenuItem, + { + key: 'exec-log', + label: '执行日志', showDivider: true, color: 'default', className: '' @@ -62,6 +70,7 @@ const OverrideItem: React.FC = (props) => { const [updating, setUpdating] = useState(false) const [openInfo, setOpenInfo] = useState(false) const [openFile, setOpenFile] = useState(false) + const [openLog, setOpenLog] = useState(false) const { attributes, listeners, @@ -85,6 +94,10 @@ const OverrideItem: React.FC = (props) => { setOpenFile(true) break } + case 'exec-log': { + setOpenLog(true) + break + } case 'delete': { removeOverrideItem(info.id) mutateOverrideConfig() @@ -129,6 +142,7 @@ const OverrideItem: React.FC = (props) => { updateOverrideItem={updateOverrideItem} /> )} + {openLog && setOpenLog(false)} />} { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateOverrideItem', item)) } -export async function getOverride(id: string, ext: 'js' | 'yaml'): Promise { +export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id, ext)) }