From 7785c2237e2b258bd697735137d31e17068fcbdf Mon Sep 17 00:00:00 2001 From: pompurin404 Date: Sun, 11 Aug 2024 19:53:49 +0800 Subject: [PATCH] support profile override script --- src/main/config/index.ts | 11 + src/main/config/override.ts | 108 ++++++++++ src/main/resolve/factory.ts | 32 ++- src/main/resolve/init.ts | 9 + src/main/utils/dirs.ts | 12 ++ src/main/utils/ipc.ts | 24 ++- src/main/utils/template.ts | 4 + .../components/override/edit-file-modal.tsx | 58 ++++++ .../components/override/edit-info-modal.tsx | 73 +++++++ .../src/components/override/override-item.tsx | 197 ++++++++++++++++++ .../components/profiles/edit-info-modal.tsx | 22 +- .../src/components/sider/override-card.tsx | 9 +- .../src/hooks/use-override-config.tsx | 52 +++++ src/renderer/src/pages/override.tsx | 169 ++++++++++++++- src/renderer/src/pages/profiles.tsx | 6 +- src/renderer/src/utils/ipc.ts | 36 +++- src/shared/types.d.ts | 14 ++ 17 files changed, 817 insertions(+), 19 deletions(-) create mode 100644 src/main/config/override.ts create mode 100644 src/renderer/src/components/override/edit-file-modal.tsx create mode 100644 src/renderer/src/components/override/edit-info-modal.tsx create mode 100644 src/renderer/src/components/override/override-item.tsx create mode 100644 src/renderer/src/hooks/use-override-config.tsx diff --git a/src/main/config/index.ts b/src/main/config/index.ts index fd5d173..cd68d37 100644 --- a/src/main/config/index.ts +++ b/src/main/config/index.ts @@ -14,3 +14,14 @@ export { changeCurrentProfile, updateProfileItem } from './profile' +export { + getOverrideConfig, + setOverrideConfig, + getOverrideItem, + addOverrideItem, + removeOverrideItem, + createOverride, + getOverride, + setOverride, + updateOverrideItem +} from './override' diff --git a/src/main/config/override.ts b/src/main/config/override.ts new file mode 100644 index 0000000..41fec4e --- /dev/null +++ b/src/main/config/override.ts @@ -0,0 +1,108 @@ +import { overrideConfigPath, overridePath } from '../utils/dirs' +import yaml from 'yaml' +import fs from 'fs' +import { dialog } from 'electron' +import axios from 'axios' +import { getControledMihomoConfig } from './controledMihomo' + +let overrideConfig: IOverrideConfig // override.yaml + +export function getOverrideConfig(force = false): IOverrideConfig { + if (force || !overrideConfig) { + overrideConfig = yaml.parse(fs.readFileSync(overrideConfigPath(), 'utf-8')) + } + return overrideConfig +} + +export function setOverrideConfig(config: IOverrideConfig): void { + overrideConfig = config + fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig)) +} + +export function getOverrideItem(id: string): IOverrideItem | undefined { + return overrideConfig.items.find((item) => item.id === id) +} +export function updateOverrideItem(item: IOverrideItem): void { + const index = overrideConfig.items.findIndex((i) => i.id === item.id) + overrideConfig.items[index] = item + fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig)) +} + +export async function addOverrideItem(item: Partial): Promise { + const newItem = await createOverride(item) + if (overrideConfig.items.find((i) => i.id === newItem.id)) { + updateOverrideItem(newItem) + } else { + overrideConfig.items.push(newItem) + } + fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig)) +} + +export function removeOverrideItem(id: string): void { + overrideConfig.items = overrideConfig.items?.filter((item) => item.id !== id) + fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig)) + fs.rmSync(overridePath(id)) +} + +export async function createOverride(item: Partial): Promise { + const id = item.id || new Date().getTime().toString(16) + const newItem = { + id, + name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'), + type: item.type, + url: item.url, + updated: new Date().getTime() + } as IOverrideItem + switch (newItem.type) { + case 'remote': { + if (!item.url) { + dialog.showErrorBox( + 'URL is required for remote script', + 'URL is required for remote script' + ) + throw new Error('URL is required for remote script') + } + try { + const res = await axios.get(item.url, { + proxy: { + protocol: 'http', + host: '127.0.0.1', + port: getControledMihomoConfig()['mixed-port'] || 7890 + }, + responseType: 'text' + }) + const data = res.data + setOverride(id, data) + } catch (e) { + dialog.showErrorBox('Failed to fetch remote script', `${e}\nurl: ${item.url}`) + throw new Error(`Failed to fetch remote script ${e}`) + } + break + } + case 'local': { + if (!item.file) { + dialog.showErrorBox( + 'File is required for local script', + 'File is required for local script' + ) + throw new Error('File is required for local script') + } + const data = item.file + setOverride(id, data) + break + } + } + + return newItem +} + +export function getOverride(id: string): string { + if (!fs.existsSync(overridePath(id))) { + return `function main(config){ return config }` + } + return fs.readFileSync(overridePath(id), 'utf-8') +} + +export function setOverride(id: string, content: string): void { + fs.writeFileSync(overridePath(id), content, 'utf-8') +} diff --git a/src/main/resolve/factory.ts b/src/main/resolve/factory.ts index 97f1b7b..a2af2fc 100644 --- a/src/main/resolve/factory.ts +++ b/src/main/resolve/factory.ts @@ -1,11 +1,17 @@ -import { getControledMihomoConfig, getProfileConfig, getProfile } from '../config' +import { + getControledMihomoConfig, + getProfileConfig, + getProfile, + getProfileItem, + getOverride +} from '../config' import { mihomoWorkConfigPath } from '../utils/dirs' import yaml from 'yaml' import fs from 'fs' export function generateProfile(): void { const current = getProfileConfig().current - const currentProfile = getProfile(current) + const currentProfile = overrideProfile(current, getProfile(current)) const controledMihomoConfig = getControledMihomoConfig() const { tun: profileTun = {} } = currentProfile const { tun: controledTun } = controledMihomoConfig @@ -22,3 +28,25 @@ export function generateProfile(): void { profile.sniffer = sniffer fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile)) } + +function overrideProfile(current: string | undefined, profile: IMihomoConfig): IMihomoConfig { + const overrideScriptList = getProfileItem(current).override || [] + for (const override of overrideScriptList) { + const script = getOverride(override) + profile = runOverrideScript(profile, script) + } + return profile +} + +function runOverrideScript(profile: IMihomoConfig, script: string): IMihomoConfig { + try { + const func = eval(`${script} main`) + const newProfile = func(profile) + if (typeof newProfile !== 'object') { + throw new Error('Override script must return an object') + } + return newProfile + } catch (e) { + return profile + } +} diff --git a/src/main/resolve/init.ts b/src/main/resolve/init.ts index 4eff0c5..dd82e85 100644 --- a/src/main/resolve/init.ts +++ b/src/main/resolve/init.ts @@ -5,6 +5,8 @@ import { logDir, mihomoTestDir, mihomoWorkDir, + overrideConfigPath, + overrideDir, profileConfigPath, profilePath, profilesDir, @@ -13,6 +15,7 @@ import { import { defaultConfig, defaultControledMihomoConfig, + defaultOverrideConfig, defaultProfile, defaultProfileConfig } from '../utils/template' @@ -31,6 +34,9 @@ function initDirs(): void { if (!fs.existsSync(profilesDir())) { fs.mkdirSync(profilesDir()) } + if (!fs.existsSync(overrideDir())) { + fs.mkdirSync(overrideDir()) + } if (!fs.existsSync(mihomoWorkDir())) { fs.mkdirSync(mihomoWorkDir()) } @@ -49,6 +55,9 @@ function initConfig(): void { if (!fs.existsSync(profileConfigPath())) { fs.writeFileSync(profileConfigPath(), yaml.stringify(defaultProfileConfig)) } + if (!fs.existsSync(overrideConfigPath())) { + fs.writeFileSync(overrideConfigPath(), yaml.stringify(defaultOverrideConfig)) + } if (!fs.existsSync(profilePath('default'))) { fs.writeFileSync(profilePath('default'), yaml.stringify(defaultProfile)) } diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts index 7e3a01c..9e51e32 100644 --- a/src/main/utils/dirs.ts +++ b/src/main/utils/dirs.ts @@ -53,6 +53,18 @@ export function profilePath(id: string): string { return path.join(profilesDir(), `${id}.yaml`) } +export function overrideDir(): string { + return path.join(dataDir, 'override') +} + +export function overrideConfigPath(): string { + return path.join(dataDir, 'override.yaml') +} + +export function overridePath(id: string): string { + return path.join(overrideDir(), `${id}.js`) +} + export function mihomoWorkDir(): string { return path.join(dataDir, 'work') } diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 974ec19..817f4c7 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -34,7 +34,15 @@ import { getProfileStr, setProfileStr, updateProfileItem, - setProfileConfig + setProfileConfig, + getOverrideConfig, + setOverrideConfig, + getOverrideItem, + addOverrideItem, + removeOverrideItem, + getOverride, + setOverride, + updateOverrideItem } from '../config' import { isEncryptionAvailable, restartCore } from '../core/manager' import { triggerSysProxy } from '../resolve/sysproxy' @@ -81,11 +89,19 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('changeCurrentProfile', (_e, id) => changeCurrentProfile(id)) ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item)) ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id)) + ipcMain.handle('getOverrideConfig', (_e, force) => getOverrideConfig(force)) + ipcMain.handle('setOverrideConfig', (_e, config) => setOverrideConfig(config)) + ipcMain.handle('getOverrideItem', (_e, id) => getOverrideItem(id)) + ipcMain.handle('addOverrideItem', (_e, item) => addOverrideItem(item)) + ipcMain.handle('removeOverrideItem', (_e, id) => removeOverrideItem(id)) + ipcMain.handle('updateOverrideItem', (_e, item) => updateOverrideItem(item)) + ipcMain.handle('getOverride', (_e, id) => getOverride(id)) + ipcMain.handle('setOverride', (_e, id, str) => setOverride(id, str)) ipcMain.handle('restartCore', restartCore) ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable)) ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable) ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str)) - ipcMain.handle('getFilePath', getFilePath) + ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext)) ipcMain.handle('readTextFile', (_e, filePath) => readTextFile(filePath)) ipcMain.handle('getRuntimeConfigStr', getRuntimeConfigStr) ipcMain.handle('getRuntimeConfig', getRuntimeConfig) @@ -97,10 +113,10 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('quitApp', () => app.quit()) } -function getFilePath(): string[] | undefined { +function getFilePath(ext: string[]): string[] | undefined { return dialog.showOpenDialogSync({ title: '选择订阅文件', - filters: [{ name: 'Yaml Files', extensions: ['yml', 'yaml'] }], + filters: [{ name: 'Yaml Files', extensions: ext }], properties: ['openFile'] }) } diff --git a/src/main/utils/template.ts b/src/main/utils/template.ts index 492fa2f..a59ea49 100644 --- a/src/main/utils/template.ts +++ b/src/main/utils/template.ts @@ -94,6 +94,10 @@ export const defaultProfileConfig: IProfileConfig = { items: [] } +export const defaultOverrideConfig: IOverrideConfig = { + items: [] +} + export const defaultProfile: Partial = { proxies: [], 'proxy-groups': [], diff --git a/src/renderer/src/components/override/edit-file-modal.tsx b/src/renderer/src/components/override/edit-file-modal.tsx new file mode 100644 index 0000000..ef4bfc0 --- /dev/null +++ b/src/renderer/src/components/override/edit-file-modal.tsx @@ -0,0 +1,58 @@ +import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react' +import React, { useEffect, useState } from 'react' +import { BaseEditor } from '../base/base-editor' +import { getOverride, setOverride } from '@renderer/utils/ipc' +interface Props { + id: string + onClose: () => void +} +const EditFileModal: React.FC = (props) => { + const { id, onClose } = props + const [currData, setCurrData] = useState('') + + const getContent = async (): Promise => { + setCurrData(await getOverride(id)) + } + + useEffect(() => { + getContent() + }, []) + + return ( + + + 编辑覆写脚本 + + setCurrData(value)} + /> + + + + + + + + ) +} + +export default EditFileModal diff --git a/src/renderer/src/components/override/edit-info-modal.tsx b/src/renderer/src/components/override/edit-info-modal.tsx new file mode 100644 index 0000000..085f605 --- /dev/null +++ b/src/renderer/src/components/override/edit-info-modal.tsx @@ -0,0 +1,73 @@ +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + Input +} from '@nextui-org/react' +import React, { useState } from 'react' +import SettingItem from '../base/base-setting-item' +interface Props { + item: IOverrideItem + updateOverrideItem: (item: IOverrideItem) => Promise + onClose: () => void +} +const EditInfoModal: React.FC = (props) => { + const { item, updateOverrideItem, onClose } = props + const [values, setValues] = useState(item) + + const onSave = async (): Promise => { + await updateOverrideItem(values) + onClose() + } + + return ( + + + 编辑信息 + + + { + setValues({ ...values, name: v }) + }} + /> + + {values.type === 'remote' && ( + + { + setValues({ ...values, url: v }) + }} + /> + + )} + + + + + + + + ) +} + +export default EditInfoModal diff --git a/src/renderer/src/components/override/override-item.tsx b/src/renderer/src/components/override/override-item.tsx new file mode 100644 index 0000000..5e729dc --- /dev/null +++ b/src/renderer/src/components/override/override-item.tsx @@ -0,0 +1,197 @@ +import { + Button, + Card, + CardBody, + Chip, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger +} from '@nextui-org/react' +import { IoMdMore, IoMdRefresh } from 'react-icons/io' +import dayjs from 'dayjs' +import React, { Key, useEffect, useState } from 'react' +import EditFileModal from './edit-file-modal' +import EditInfoModal from './edit-info-modal' +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' + +interface Props { + info: IOverrideItem + addOverrideItem: (item: Partial) => Promise + updateOverrideItem: (item: IOverrideItem) => Promise + removeOverrideItem: (id: string) => Promise + mutateOverrideConfig: () => void +} + +interface MenuItem { + key: string + label: string + showDivider: boolean + color: 'default' | 'danger' + className: string +} + +const menuItems: MenuItem[] = [ + { + key: 'edit-info', + label: '编辑信息', + showDivider: false, + color: 'default', + className: '' + } as MenuItem, + { + key: 'edit-file', + label: '编辑文件', + showDivider: true, + color: 'default', + className: '' + } as MenuItem, + { + key: 'delete', + label: '删除', + showDivider: false, + color: 'danger', + className: 'text-danger' + } as MenuItem +] + +const OverrideItem: React.FC = (props) => { + const { info, addOverrideItem, removeOverrideItem, mutateOverrideConfig, updateOverrideItem } = + props + const [updating, setUpdating] = useState(false) + const [openInfo, setOpenInfo] = useState(false) + const [openFile, setOpenFile] = useState(false) + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: info.id + }) + const [disableOpen, setDisableOpen] = useState(false) + + const onMenuAction = (key: Key): void => { + switch (key) { + case 'edit-info': { + setOpenInfo(true) + break + } + case 'edit-file': { + setOpenFile(true) + break + } + case 'delete': { + removeOverrideItem(info.id) + mutateOverrideConfig() + break + } + } + } + + useEffect(() => { + if (isDragging) { + setTimeout(() => { + setDisableOpen(true) + }, 200) + } else { + setTimeout(() => { + setDisableOpen(false) + }, 200) + } + }, [isDragging]) + + return ( +
+ {openFile && setOpenFile(false)} />} + {openInfo && ( + setOpenInfo(false)} + updateOverrideItem={updateOverrideItem} + /> + )} + { + if (disableOpen) return + setOpenFile(true) + }} + > + +
+

+ {info?.name} +

+
+ {info.type === 'remote' && ( + + )} + + + + + + + {menuItems.map((item) => ( + + {item.label} + + ))} + + +
+
+ {info.type === 'remote' && ( +
+ {dayjs(info.updated).fromNow()} +
+ )} + {info.type === 'local' && ( +
+ + 本地 + +
+ )} +
+
+
+ ) +} + +export default OverrideItem diff --git a/src/renderer/src/components/profiles/edit-info-modal.tsx b/src/renderer/src/components/profiles/edit-info-modal.tsx index 5ffc6b2..fb76d54 100644 --- a/src/renderer/src/components/profiles/edit-info-modal.tsx +++ b/src/renderer/src/components/profiles/edit-info-modal.tsx @@ -5,11 +5,14 @@ import { ModalBody, ModalFooter, Button, - Input + Input, + Select, + SelectItem } from '@nextui-org/react' import React, { useState } from 'react' import SettingItem from '../base/base-setting-item' import dayjs from 'dayjs' +import { useOverrideConfig } from '@renderer/hooks/use-override-config' interface Props { item: IProfileItem updateProfileItem: (item: IProfileItem) => Promise @@ -17,6 +20,8 @@ interface Props { } const EditInfoModal: React.FC = (props) => { const { item, updateProfileItem, onClose } = props + const { overrideConfig } = useOverrideConfig() + const { items: overrideItems = [] } = overrideConfig || {} const [values, setValues] = useState(item) const onSave = async (): Promise => { @@ -77,6 +82,21 @@ const EditInfoModal: React.FC = (props) => { /> )} + + + - diff --git a/src/renderer/src/hooks/use-override-config.tsx b/src/renderer/src/hooks/use-override-config.tsx new file mode 100644 index 0000000..81239e9 --- /dev/null +++ b/src/renderer/src/hooks/use-override-config.tsx @@ -0,0 +1,52 @@ +import useSWR from 'swr' +import { + getOverrideConfig, + setOverrideConfig as set, + addOverrideItem as add, + removeOverrideItem as remove, + updateOverrideItem as update +} from '@renderer/utils/ipc' + +interface RetuenType { + overrideConfig: IOverrideConfig | undefined + setOverrideConfig: (config: IOverrideConfig) => Promise + mutateOverrideConfig: () => void + addOverrideItem: (item: Partial) => Promise + updateOverrideItem: (item: IOverrideItem) => Promise + removeOverrideItem: (id: string) => Promise +} + +export const useOverrideConfig = (): RetuenType => { + const { data: overrideConfig, mutate: mutateOverrideConfig } = useSWR('getOverrideConfig', () => + getOverrideConfig() + ) + + const setOverrideConfig = async (config: IOverrideConfig): Promise => { + await set(config) + mutateOverrideConfig() + } + + const addOverrideItem = async (item: Partial): Promise => { + await add(item) + mutateOverrideConfig() + } + + const removeOverrideItem = async (id: string): Promise => { + await remove(id) + mutateOverrideConfig() + } + + const updateOverrideItem = async (item: IOverrideItem): Promise => { + await update(item) + mutateOverrideConfig() + } + + return { + overrideConfig, + setOverrideConfig, + mutateOverrideConfig, + addOverrideItem, + removeOverrideItem, + updateOverrideItem + } +} diff --git a/src/renderer/src/pages/override.tsx b/src/renderer/src/pages/override.tsx index f5a44c4..c1da58d 100644 --- a/src/renderer/src/pages/override.tsx +++ b/src/renderer/src/pages/override.tsx @@ -1,7 +1,174 @@ +import { Button, Input } from '@nextui-org/react' import BasePage from '@renderer/components/base/base-page' +import { getFilePath, readTextFile } from '@renderer/utils/ipc' +import { useEffect, useRef, useState } from 'react' +import { MdContentPaste } from 'react-icons/md' +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, + DragEndEvent +} from '@dnd-kit/core' +import { SortableContext } from '@dnd-kit/sortable' +import { useOverrideConfig } from '@renderer/hooks/use-override-config' +import OverrideItem from '@renderer/components/override/override-item' const Override: React.FC = () => { - return + const { + overrideConfig, + setOverrideConfig, + addOverrideItem, + updateOverrideItem, + removeOverrideItem, + mutateOverrideConfig + } = useOverrideConfig() + const { items = [] } = overrideConfig || {} + const [sortedItems, setSortedItems] = useState(items) + const [importing, setImporting] = useState(false) + const [fileOver, setFileOver] = useState(false) + const [url, setUrl] = useState('') + const sensors = useSensors(useSensor(PointerSensor)) + const handleImport = async (): Promise => { + setImporting(true) + try { + await addOverrideItem({ name: '', type: 'remote', url }) + } finally { + setImporting(false) + } + } + const pageRef = useRef(null) + + const onDragEnd = async (event: DragEndEvent): Promise => { + const { active, over } = event + if (over) { + if (active.id !== over.id) { + const newOrder = sortedItems.slice() + const activeIndex = newOrder.findIndex((item) => item.id === active.id) + const overIndex = newOrder.findIndex((item) => item.id === over.id) + newOrder.splice(activeIndex, 1) + newOrder.splice(overIndex, 0, items[activeIndex]) + setSortedItems(newOrder) + await setOverrideConfig({ items: newOrder }) + } + } + } + + useEffect(() => { + pageRef.current?.addEventListener('dragover', (e) => { + e.preventDefault() + e.stopPropagation() + setFileOver(true) + }) + pageRef.current?.addEventListener('dragleave', (e) => { + e.preventDefault() + e.stopPropagation() + setFileOver(false) + }) + pageRef.current?.addEventListener('drop', async (event) => { + event.preventDefault() + event.stopPropagation() + if (event.dataTransfer?.files) { + const file = event.dataTransfer.files[0] + if (file.name.endsWith('.js')) { + const content = await readTextFile(file.path) + try { + await addOverrideItem({ name: file.name, type: 'local', file: content }) + } finally { + setFileOver(false) + } + } else { + alert('不支持的文件类型') + } + } + setFileOver(false) + }) + return (): void => { + pageRef.current?.removeEventListener('dragover', () => {}) + pageRef.current?.removeEventListener('dragleave', () => {}) + pageRef.current?.removeEventListener('drop', () => {}) + } + }, []) + + useEffect(() => { + setSortedItems(items) + }, [items]) + + return ( + +
+ { + navigator.clipboard.readText().then((text) => { + setUrl(text) + }) + }} + > + + + } + /> + + +
+ +
+ { + return item.id + })} + > + {sortedItems.map((item) => ( + + ))} + +
+
+
+ ) } export default Override diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index c175817..29b92e1 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -92,6 +92,10 @@ const Profiles: React.FC = () => { } }, []) + useEffect(() => { + setSortedItems(items) + }, [items]) + return (
@@ -130,7 +134,7 @@ const Profiles: React.FC = () => { color="primary" className="ml-2" onPress={() => { - getFilePath().then(async (files) => { + getFilePath(['yml', 'yaml']).then(async (files) => { if (files?.length) { const content = await readTextFile(files[0]) const fileName = files[0].split('/').pop()?.split('\\').pop() diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 4485836..5641c0e 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -138,6 +138,38 @@ export async function setProfileStr(id: string, str: string): Promise { return await window.electron.ipcRenderer.invoke('setProfileStr', id, str) } +export async function getOverrideConfig(force = false): Promise { + return await window.electron.ipcRenderer.invoke('getOverrideConfig', force) +} + +export async function setOverrideConfig(config: IOverrideConfig): Promise { + return await window.electron.ipcRenderer.invoke('setOverrideConfig', config) +} + +export async function getOverrideItem(id: string): Promise { + return await window.electron.ipcRenderer.invoke('getOverrideItem', id) +} + +export async function addOverrideItem(item: Partial): Promise { + return await window.electron.ipcRenderer.invoke('addOverrideItem', item) +} + +export async function removeOverrideItem(id: string): Promise { + return await window.electron.ipcRenderer.invoke('removeOverrideItem', id) +} + +export async function updateOverrideItem(item: IOverrideItem): Promise { + return await window.electron.ipcRenderer.invoke('updateOverrideItem', item) +} + +export async function getOverride(id: string): Promise { + return await window.electron.ipcRenderer.invoke('getOverride', id) +} + +export async function setOverride(id: string, str: string): Promise { + return await window.electron.ipcRenderer.invoke('setOverride', id, str) +} + export async function restartCore(): Promise { return await window.electron.ipcRenderer.invoke('restartCore') } @@ -154,8 +186,8 @@ export async function encryptString(str: string): Promise { return await window.electron.ipcRenderer.invoke('encryptString', str) } -export async function getFilePath(): Promise { - return await window.electron.ipcRenderer.invoke('getFilePath') +export async function getFilePath(ext: string[]): Promise { + return await window.electron.ipcRenderer.invoke('getFilePath', ext) } export async function readTextFile(filePath: string): Promise { diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index 2004cef..3be67de 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -311,6 +311,19 @@ interface IProfileConfig { items: IProfileItem[] } +interface IOverrideItem { + id: string + type: 'remote' | 'local' + name: string + updated: number + url?: string + file?: string +} + +interface IOverrideConfig { + items: IOverrideItem[] +} + interface ISubscriptionUserInfo { upload: number download: number @@ -327,5 +340,6 @@ interface IProfileItem { interval?: number home?: string updated?: number + override?: string[] extra?: ISubscriptionUserInfo }