use nodejs vm and get script log

This commit is contained in:
pompurin404 2024-08-18 21:50:04 +08:00
parent 82d42be8d5
commit 1e2b0b92cc
No known key found for this signature in database
6 changed files with 115 additions and 9 deletions

View File

@ -89,7 +89,7 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
return newItem return newItem
} }
export async function getOverride(id: string, ext: 'js' | 'yaml'): Promise<string> { export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise<string> {
if (!existsSync(overridePath(id, ext))) { if (!existsSync(overridePath(id, ext))) {
return '' return ''
} }

View File

@ -6,10 +6,12 @@ import {
getOverride, getOverride,
getOverrideItem getOverrideItem
} from '../config' } from '../config'
import { mihomoWorkConfigPath } from '../utils/dirs' import { mihomoWorkConfigPath, overridePath } from '../utils/dirs'
import yaml from 'yaml' import yaml from 'yaml'
import { readFile, writeFile } from 'fs/promises' import { readFile, writeFile } from 'fs/promises'
import { deepMerge } from '../utils/merge' import { deepMerge } from '../utils/merge'
import vm from 'vm'
import { writeFileSync } from 'fs'
export async function generateProfile(): Promise<void> { export async function generateProfile(): Promise<void> {
const { current } = await getProfileConfig() const { current } = await getProfileConfig()
@ -29,7 +31,7 @@ async function overrideProfile(
const content = await getOverride(ov, item?.ext || 'js') const content = await getOverride(ov, item?.ext || 'js')
switch (item?.ext) { switch (item?.ext) {
case 'js': case 'js':
profile = runOverrideScript(profile, content) profile = runOverrideScript(profile, content, item)
break break
case 'yaml': { case 'yaml': {
const patch = yaml.parse(content) const patch = yaml.parse(content)
@ -47,13 +49,45 @@ async function overrideProfile(
return profile 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 { try {
const func = eval(`${script} main`) const ctx = {
const newProfile = func(profile) console: Object.freeze({
if (typeof newProfile !== 'object') return profile 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 return newProfile
} catch (e) { } catch (e) {
log('exception', `脚本执行失败: ${e}`)
return profile return profile
} }
} }

View File

@ -89,7 +89,7 @@ export function overrideConfigPath(): string {
return path.join(dataDir(), 'override.yaml') 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}`) return path.join(overrideDir(), `${id}.${ext}`)
} }

View File

@ -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> = (props) => {
const { id, onClose } = props
const [logs, setLogs] = useState<string[]>([])
const getLog = async (): Promise<void> => {
setLogs((await getOverride(id, 'log')).split('\n').filter(Boolean))
}
useEffect(() => {
getLog()
}, [])
return (
<Modal
backdrop="blur"
hideCloseButton
isOpen={true}
onOpenChange={onClose}
scrollBehavior="inside"
>
<ModalContent>
<ModalHeader className="flex"></ModalHeader>
<ModalBody>
{logs.map((log) => {
return (
<>
<small className="break-all">{log}</small>
<Divider />
</>
)
})}
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onClose}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
export default ExecLogModal

View File

@ -15,6 +15,7 @@ import EditFileModal from './edit-file-modal'
import EditInfoModal from './edit-info-modal' import EditInfoModal from './edit-info-modal'
import { useSortable } from '@dnd-kit/sortable' import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
import ExecLogModal from './exec-log-modal'
interface Props { interface Props {
info: IOverrideItem info: IOverrideItem
@ -43,6 +44,13 @@ const menuItems: MenuItem[] = [
{ {
key: 'edit-file', key: 'edit-file',
label: '编辑文件', label: '编辑文件',
showDivider: false,
color: 'default',
className: ''
} as MenuItem,
{
key: 'exec-log',
label: '执行日志',
showDivider: true, showDivider: true,
color: 'default', color: 'default',
className: '' className: ''
@ -62,6 +70,7 @@ const OverrideItem: React.FC<Props> = (props) => {
const [updating, setUpdating] = useState(false) const [updating, setUpdating] = useState(false)
const [openInfo, setOpenInfo] = useState(false) const [openInfo, setOpenInfo] = useState(false)
const [openFile, setOpenFile] = useState(false) const [openFile, setOpenFile] = useState(false)
const [openLog, setOpenLog] = useState(false)
const { const {
attributes, attributes,
listeners, listeners,
@ -85,6 +94,10 @@ const OverrideItem: React.FC<Props> = (props) => {
setOpenFile(true) setOpenFile(true)
break break
} }
case 'exec-log': {
setOpenLog(true)
break
}
case 'delete': { case 'delete': {
removeOverrideItem(info.id) removeOverrideItem(info.id)
mutateOverrideConfig() mutateOverrideConfig()
@ -129,6 +142,7 @@ const OverrideItem: React.FC<Props> = (props) => {
updateOverrideItem={updateOverrideItem} updateOverrideItem={updateOverrideItem}
/> />
)} )}
{openLog && <ExecLogModal id={info.id} onClose={() => setOpenLog(false)} />}
<Card <Card
fullWidth fullWidth
isPressable isPressable

View File

@ -181,7 +181,7 @@ export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateOverrideItem', item)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateOverrideItem', item))
} }
export async function getOverride(id: string, ext: 'js' | 'yaml'): Promise<string> { export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id, ext)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id, ext))
} }