support yaml override

This commit is contained in:
pompurin404 2024-08-17 16:48:38 +08:00
parent 6564b2bc58
commit a46466600c
No known key found for this signature in database
13 changed files with 113 additions and 46 deletions

View File

@ -48,9 +48,10 @@ export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<voi
export async function removeOverrideItem(id: string): Promise<void> { export async function removeOverrideItem(id: string): Promise<void> {
const config = await getOverrideConfig() const config = await getOverrideConfig()
const item = await getOverrideItem(id)
config.items = config.items?.filter((item) => item.id !== id) config.items = config.items?.filter((item) => item.id !== id)
await setOverrideConfig(config) await setOverrideConfig(config)
await rm(overridePath(id)) await rm(overridePath(id, item?.ext || 'js'))
} }
export async function createOverride(item: Partial<IOverrideItem>): Promise<IOverrideItem> { export async function createOverride(item: Partial<IOverrideItem>): Promise<IOverrideItem> {
@ -59,6 +60,7 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
id, id,
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'), name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
type: item.type, type: item.type,
ext: item.ext || 'js',
url: item.url, url: item.url,
updated: new Date().getTime() updated: new Date().getTime()
} as IOverrideItem } as IOverrideItem
@ -74,12 +76,12 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
} }
}) })
const data = res.data const data = res.data
await setOverride(id, data) await setOverride(id, newItem.ext, data)
break break
} }
case 'local': { case 'local': {
const data = item.file || '' const data = item.file || ''
setOverride(id, data) setOverride(id, newItem.ext, data)
break break
} }
} }
@ -87,13 +89,13 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
return newItem return newItem
} }
export async function getOverride(id: string): Promise<string> { export async function getOverride(id: string, ext: 'js' | 'yaml'): Promise<string> {
if (!existsSync(overridePath(id))) { if (!existsSync(overridePath(id, ext))) {
return `function main(config){ return config }` return ''
} }
return await readFile(overridePath(id), 'utf-8') return await readFile(overridePath(id, ext), 'utf-8')
} }
export async function setOverride(id: string, content: string): Promise<void> { export async function setOverride(id: string, ext: 'js' | 'yaml', content: string): Promise<void> {
await writeFile(overridePath(id), content, 'utf-8') await writeFile(overridePath(id, ext), content, 'utf-8')
} }

View File

@ -3,7 +3,8 @@ import {
getProfileConfig, getProfileConfig,
getProfile, getProfile,
getProfileItem, getProfileItem,
getOverride getOverride,
getOverrideItem
} from '../config' } from '../config'
import { mihomoWorkConfigPath } from '../utils/dirs' import { mihomoWorkConfigPath } from '../utils/dirs'
import yaml from 'yaml' import yaml from 'yaml'
@ -24,8 +25,16 @@ async function overrideProfile(
): Promise<IMihomoConfig> { ): Promise<IMihomoConfig> {
const { override = [] } = (await getProfileItem(current)) || {} const { override = [] } = (await getProfileItem(current)) || {}
for (const ov of override) { for (const ov of override) {
const script = await getOverride(ov) const item = await getOverrideItem(ov)
profile = runOverrideScript(profile, script) const content = await getOverride(ov, item?.ext || 'js')
switch (item?.ext) {
case 'js':
profile = runOverrideScript(profile, content)
break
case 'yaml':
profile = deepMerge(profile, yaml.parse(content))
break
}
} }
return profile return profile
} }

View File

@ -89,8 +89,8 @@ export function overrideConfigPath(): string {
return path.join(dataDir(), 'override.yaml') return path.join(dataDir(), 'override.yaml')
} }
export function overridePath(id: string): string { export function overridePath(id: string, ext: 'js' | 'yaml'): string {
return path.join(overrideDir(), `${id}.js`) return path.join(overrideDir(), `${id}.${ext}`)
} }
export function mihomoWorkDir(): string { export function mihomoWorkDir(): string {

View File

@ -120,8 +120,8 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('addOverrideItem', (_e, item) => ipcErrorWrapper(addOverrideItem)(item)) ipcMain.handle('addOverrideItem', (_e, item) => ipcErrorWrapper(addOverrideItem)(item))
ipcMain.handle('removeOverrideItem', (_e, id) => ipcErrorWrapper(removeOverrideItem)(id)) ipcMain.handle('removeOverrideItem', (_e, id) => ipcErrorWrapper(removeOverrideItem)(id))
ipcMain.handle('updateOverrideItem', (_e, item) => ipcErrorWrapper(updateOverrideItem)(item)) ipcMain.handle('updateOverrideItem', (_e, item) => ipcErrorWrapper(updateOverrideItem)(item))
ipcMain.handle('getOverride', (_e, id) => ipcErrorWrapper(getOverride)(id)) ipcMain.handle('getOverride', (_e, id, ext) => ipcErrorWrapper(getOverride)(id, ext))
ipcMain.handle('setOverride', (_e, id, str) => ipcErrorWrapper(setOverride)(id, str)) ipcMain.handle('setOverride', (_e, id, ext, str) => ipcErrorWrapper(setOverride)(id, ext, str))
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore)) ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable)) ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable) ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)

View File

@ -61,7 +61,7 @@ export const defaultControledMihomoConfig: Partial<IMihomoConfig> = {
'use-hosts': false, 'use-hosts': false,
'use-system-hosts': false, 'use-system-hosts': false,
nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'], nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'],
'proxy-server-nameserver': ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'], 'proxy-server-nameserver': ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query']
}, },
sniffer: { sniffer: {
enable: true, enable: true,

View File

@ -4,14 +4,15 @@ import { BaseEditor } from '../base/base-editor'
import { getOverride, setOverride } from '@renderer/utils/ipc' import { getOverride, setOverride } from '@renderer/utils/ipc'
interface Props { interface Props {
id: string id: string
language: 'javascript' | 'yaml'
onClose: () => void onClose: () => void
} }
const EditFileModal: React.FC<Props> = (props) => { const EditFileModal: React.FC<Props> = (props) => {
const { id, onClose } = props const { id, language, onClose } = props
const [currData, setCurrData] = useState('') const [currData, setCurrData] = useState('')
const getContent = async (): Promise<void> => { const getContent = async (): Promise<void> => {
setCurrData(await getOverride(id)) setCurrData(await getOverride(id, language === 'javascript' ? 'js' : 'yaml'))
} }
useEffect(() => { useEffect(() => {
@ -28,10 +29,12 @@ const EditFileModal: React.FC<Props> = (props) => {
scrollBehavior="inside" scrollBehavior="inside"
> >
<ModalContent className="h-full w-[calc(100%-100px)]"> <ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex"></ModalHeader> <ModalHeader className="flex">
{language === 'javascript' ? '脚本' : '配置'}
</ModalHeader>
<ModalBody className="h-full"> <ModalBody className="h-full">
<BaseEditor <BaseEditor
language="javascript" language={language}
value={currData} value={currData}
onChange={(value) => setCurrData(value)} onChange={(value) => setCurrData(value)}
/> />
@ -43,7 +46,7 @@ const EditFileModal: React.FC<Props> = (props) => {
<Button <Button
color="primary" color="primary"
onPress={async () => { onPress={async () => {
await setOverride(id, currData) await setOverride(id, language === 'javascript' ? 'js' : 'yaml', currData)
onClose() onClose()
}} }}
> >

View File

@ -115,7 +115,13 @@ const OverrideItem: React.FC<Props> = (props) => {
zIndex: isDragging ? 'calc(infinity)' : undefined zIndex: isDragging ? 'calc(infinity)' : undefined
}} }}
> >
{openFile && <EditFileModal id={info.id} onClose={() => setOpenFile(false)} />} {openFile && (
<EditFileModal
id={info.id}
language={info.ext === 'yaml' ? 'yaml' : 'javascript'}
onClose={() => setOpenFile(false)}
/>
)}
{openInfo && ( {openInfo && (
<EditInfoModal <EditInfoModal
item={info} item={info}
@ -184,18 +190,21 @@ const OverrideItem: React.FC<Props> = (props) => {
</Dropdown> </Dropdown>
</div> </div>
</div> </div>
<div className="flex justify-between">
<div className={`mt-2 flex justify-start`}>
<Chip size="sm" variant="bordered">
{info.type === 'local' ? '本地' : '远程'}
</Chip>
<Chip size="sm" variant="bordered" className="ml-2">
{info.ext === 'yaml' ? 'YAML' : 'JavaScript'}
</Chip>
</div>
{info.type === 'remote' && ( {info.type === 'remote' && (
<div className={`mt-2 flex justify-end`}> <div className={`mt-2 flex justify-end`}>
<small>{dayjs(info.updated).fromNow()}</small> <small>{dayjs(info.updated).fromNow()}</small>
</div> </div>
)} )}
{info.type === 'local' && (
<div className={`mt-2 flex justify-between`}>
<Chip size="sm" variant="bordered">
</Chip>
</div> </div>
)}
</CardBody> </CardBody>
</Card> </Card>
</div> </div>

View File

@ -2,6 +2,7 @@ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { BaseEditor } from '../base/base-editor' import { BaseEditor } from '../base/base-editor'
import { getProfileStr, setProfileStr } from '@renderer/utils/ipc' import { getProfileStr, setProfileStr } from '@renderer/utils/ipc'
import { useNavigate } from 'react-router-dom'
interface Props { interface Props {
id: string id: string
onClose: () => void onClose: () => void
@ -9,6 +10,7 @@ interface Props {
const EditFileModal: React.FC<Props> = (props) => { const EditFileModal: React.FC<Props> = (props) => {
const { id, onClose } = props const { id, onClose } = props
const [currData, setCurrData] = useState('') const [currData, setCurrData] = useState('')
const navigate = useNavigate()
const getContent = async (): Promise<void> => { const getContent = async (): Promise<void> => {
setCurrData(await getProfileStr(id)) setCurrData(await getProfileStr(id))
@ -28,7 +30,25 @@ const EditFileModal: React.FC<Props> = (props) => {
scrollBehavior="inside" scrollBehavior="inside"
> >
<ModalContent className="h-full w-[calc(100%-100px)]"> <ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex"></ModalHeader> <ModalHeader className="flex">
<div className="flex justify-start">
<div className="flex items-center"></div>
<small className="ml-2 text-default-500">
使
<Button
size="sm"
color="primary"
variant="light"
onPress={() => {
navigate('/override')
}}
>
</Button>
</small>
</div>
</ModalHeader>
<ModalBody className="h-full"> <ModalBody className="h-full">
<BaseEditor language="yaml" value={currData} onChange={(value) => setCurrData(value)} /> <BaseEditor language="yaml" value={currData} onChange={(value) => setCurrData(value)} />
</ModalBody> </ModalBody>

View File

@ -92,14 +92,19 @@ const EditInfoModal: React.FC<Props> = (props) => {
</SettingItem> </SettingItem>
</> </>
)} )}
<SettingItem title="覆写脚本"> <SettingItem title="覆写">
<Select <Select
className="w-[200px]" className="w-[200px]"
size="sm" size="sm"
selectionMode="multiple" selectionMode="multiple"
selectedKeys={new Set(values.override || [])} selectedKeys={new Set(values.override || [])}
onSelectionChange={(v) => { onSelectionChange={(v) => {
setValues({ ...values, override: Array.from(v).map((i) => i.toString()) }) setValues({
...values,
override: Array.from(v)
.map((i) => i.toString())
.filter((i) => overrideItems.find((t) => t.id === i))
})
}} }}
> >
{overrideItems.map((i) => ( {overrideItems.map((i) => (

View File

@ -29,7 +29,10 @@ const DNS: React.FC = () => {
'use-system-hosts': useSystemHosts = false, 'use-system-hosts': useSystemHosts = false,
'respect-rules': respectRules = false, 'respect-rules': respectRules = false,
nameserver = ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'], nameserver = ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'],
'proxy-server-nameserver': proxyServerNameserver = ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'], 'proxy-server-nameserver': proxyServerNameserver = [
'https://doh.pub/dns-query',
'https://dns.alidns.com/dns-query'
]
} = dns || {} } = dns || {}
const [values, setValues] = useState({ const [values, setValues] = useState({

View File

@ -33,7 +33,12 @@ const Override: React.FC = () => {
const handleImport = async (): Promise<void> => { const handleImport = async (): Promise<void> => {
setImporting(true) setImporting(true)
try { try {
await addOverrideItem({ name: '', type: 'remote', url }) await addOverrideItem({
name: '',
type: 'remote',
url,
ext: url.endsWith('.js') ? 'js' : 'yaml'
})
} finally { } finally {
setImporting(false) setImporting(false)
} }
@ -71,10 +76,15 @@ const Override: React.FC = () => {
event.stopPropagation() event.stopPropagation()
if (event.dataTransfer?.files) { if (event.dataTransfer?.files) {
const file = event.dataTransfer.files[0] const file = event.dataTransfer.files[0]
if (file.name.endsWith('.js')) { if (file.name.endsWith('.js') || file.name.endsWith('.yaml')) {
const content = await readTextFile(file.path) const content = await readTextFile(file.path)
try { try {
await addOverrideItem({ name: file.name, type: 'local', file: content }) await addOverrideItem({
name: file.name,
type: 'local',
file: content,
ext: file.name.endsWith('.js') ? 'js' : 'yaml'
})
} finally { } finally {
setFileOver(false) setFileOver(false)
} }
@ -96,7 +106,7 @@ const Override: React.FC = () => {
}, [items]) }, [items])
return ( return (
<BasePage ref={pageRef} title="覆写脚本"> <BasePage ref={pageRef} title="覆写">
<div className="sticky top-[48px] z-40 backdrop-blur bg-background/40 flex p-2"> <div className="sticky top-[48px] z-40 backdrop-blur bg-background/40 flex p-2">
<Input <Input
variant="bordered" variant="bordered"
@ -133,11 +143,16 @@ const Override: React.FC = () => {
color="primary" color="primary"
className="ml-2" className="ml-2"
onPress={() => { onPress={() => {
getFilePath(['js']).then(async (files) => { getFilePath(['js', 'yaml']).then(async (files) => {
if (files?.length) { if (files?.length) {
const content = await readTextFile(files[0]) const content = await readTextFile(files[0])
const fileName = files[0].split('/').pop()?.split('\\').pop() const fileName = files[0].split('/').pop()?.split('\\').pop()
await addOverrideItem({ name: fileName, type: 'local', file: content }) await addOverrideItem({
name: fileName,
type: 'local',
file: content,
ext: fileName?.endsWith('.js') ? 'js' : 'yaml'
})
} }
}) })
}} }}

View File

@ -181,12 +181,12 @@ 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): Promise<string> { export async function getOverride(id: string, ext: 'js' | 'yaml'): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id, ext))
} }
export async function setOverride(id: string, str: string): Promise<void> { export async function setOverride(id: string, ext: 'js' | 'yaml', str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverride', id, str)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverride', id, ext, str))
} }
export async function restartCore(): Promise<void> { export async function restartCore(): Promise<void> {

View File

@ -331,6 +331,7 @@ interface IProfileConfig {
interface IOverrideItem { interface IOverrideItem {
id: string id: string
type: 'remote' | 'local' type: 'remote' | 'local'
ext: 'js' | 'yaml'
name: string name: string
updated: number updated: number
url?: string url?: string