From b43f078ece5b967b916f7536be977076dfce60f0 Mon Sep 17 00:00:00 2001 From: pompurin404 Date: Thu, 1 Aug 2024 21:55:35 +0800 Subject: [PATCH] import profile --- src/main/config.ts | 72 ++++++++++++++++++- .../src/components/sider/profile-card.tsx | 20 +++++- src/renderer/src/hooks/use-profile.tsx | 48 +++++++++++++ src/renderer/src/pages/profiles.tsx | 16 ++++- src/renderer/src/utils/ipc.ts | 2 +- src/shared/types.d.ts | 3 +- 6 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/main/config.ts b/src/main/config.ts index 96f55196..8d41f57b 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -7,6 +7,8 @@ import { defaultProfileConfig } from './template' import { appConfigPath, controledMihomoConfigPath, profileConfigPath, profilePath } from './dirs' +import axios from 'axios' +import { app } from 'electron' export let appConfig: IAppConfig // config.yaml export let profileConfig: IProfileConfig // profile.yaml @@ -68,11 +70,14 @@ export function getProfileItem(id: string | undefined): IProfileItem { return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' } } -export function addProfileItem(item: IProfileItem): void { - profileConfig.items.push(item) +export async function addProfileItem(item: Partial): Promise { + const newItem = await createProfile(item) + profileConfig.items.push(newItem) + console.log(!profileConfig.current) if (!profileConfig.current) { - profileConfig.current = item.id + profileConfig.current = newItem.id } + console.log(profileConfig.current) fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig)) } @@ -97,3 +102,64 @@ export function getCurrentProfile(force = false): Partial { } return currentProfile } + +export async function createProfile(item: Partial): Promise { + const id = item.id || new Date().getTime().toString(16) + const newItem = { + id, + name: item.name || 'Local File', + type: item.type || 'local', + url: item.url, + updated: new Date().getTime() + } as IProfileItem + switch (newItem.type) { + case 'remote': { + if (!item.url) { + throw new Error('URL is required for remote profile') + } + try { + const res = await axios.get(item.url, { + proxy: { + protocol: 'http', + host: '127.0.0.1', + port: controledMihomoConfig['mixed-port'] || 7890 + }, + headers: { + 'User-Agent': `Mihomo.Party.${app.getVersion()}` + }, + responseType: 'text' + }) + const data = res.data + const headers = res.headers + if (headers['content-disposition']) { + newItem.name = headers['content-disposition'].split('filename=')[1] + } + if (headers['subscription-userinfo']) { + const extra = headers['subscription-userinfo'] + .split(';') + .map((item: string) => item.split('=')[1].trim()) + newItem.extra = { + upload: parseInt(extra[0]), + download: parseInt(extra[1]), + total: parseInt(extra[2]), + expire: parseInt(extra[3]) + } + } + fs.writeFileSync(profilePath(id), data, 'utf-8') + } catch (e) { + throw new Error(`Failed to fetch remote profile ${e}`) + } + break + } + case 'local': { + if (!item.file) { + throw new Error('File is required for local profile') + } + const data = item.file + fs.writeFileSync(profilePath(id), yaml.stringify(data)) + break + } + } + + return newItem +} diff --git a/src/renderer/src/components/sider/profile-card.tsx b/src/renderer/src/components/sider/profile-card.tsx index 739c865f..ded19dd6 100644 --- a/src/renderer/src/components/sider/profile-card.tsx +++ b/src/renderer/src/components/sider/profile-card.tsx @@ -1,16 +1,29 @@ import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react' import { getCurrentProfileItem } from '@renderer/utils/ipc' +import { useEffect } from 'react' import { IoMdRefresh } from 'react-icons/io' import { useLocation, useNavigate } from 'react-router-dom' +import { calcTraffic } from '@renderer/utils/calc' import useSWR from 'swr' + const ProfileCard: React.FC = () => { const navigate = useNavigate() const location = useLocation() const match = location.pathname.includes('/profiles') - const { data: info } = useSWR('getCurrentProfileItem', getCurrentProfileItem) + const { data: info, mutate } = useSWR('getCurrentProfileItem', getCurrentProfileItem) const extra = info?.extra + const usage = (extra?.upload ?? 0) + (extra?.download ?? 0) + const total = extra?.total ?? 0 + useEffect(() => { + window.electron.ipcRenderer.on('profileConfigUpdated', () => { + mutate() + }) + return (): void => { + window.electron.ipcRenderer.removeAllListeners('profileConfigUpdated') + } + }) return ( { >
-

{info?.name}

+

+ {info?.name} +

@@ -29,6 +44,7 @@ const ProfileCard: React.FC = () => { diff --git a/src/renderer/src/hooks/use-profile.tsx b/src/renderer/src/hooks/use-profile.tsx index e69de29b..8a60ed30 100644 --- a/src/renderer/src/hooks/use-profile.tsx +++ b/src/renderer/src/hooks/use-profile.tsx @@ -0,0 +1,48 @@ +import useSWR from 'swr' +import { + getProfileConfig, + addProfileItem as add, + removeProfileItem as remove +} from '@renderer/utils/ipc' +import { useEffect } from 'react' + +interface RetuenType { + profileConfig: IProfileConfig | undefined + mutateProfileConfig: () => void + addProfileItem: (item: Partial) => Promise + removeProfileItem: (id: string) => void +} + +export const useProfileConfig = (): RetuenType => { + const { data: profileConfig, mutate: mutateProfileConfig } = useSWR('getProfileConfig', () => + getProfileConfig() + ) + + const addProfileItem = async (item: Partial): Promise => { + await add(item) + mutateProfileConfig() + window.electron.ipcRenderer.send('profileConfigUpdated') + } + + const removeProfileItem = async (id: string): Promise => { + await remove(id) + mutateProfileConfig() + window.electron.ipcRenderer.send('profileConfigUpdated') + } + + useEffect(() => { + window.electron.ipcRenderer.on('profileConfigUpdated', () => { + mutateProfileConfig() + }) + return (): void => { + window.electron.ipcRenderer.removeAllListeners('profileConfigUpdated') + } + }, []) + + return { + profileConfig, + mutateProfileConfig, + addProfileItem, + removeProfileItem + } +} diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index 01f653e1..c9e0a751 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -1,14 +1,25 @@ import { Button, Input } from '@nextui-org/react' import BasePage from '@renderer/components/base/base-page' +import { useProfileConfig } from '@renderer/hooks/use-profile' import { useState } from 'react' import { MdContentPaste } from 'react-icons/md' const Profiles: React.FC = () => { + const { profileConfig, addProfileItem } = useProfileConfig() + const [importing, setImporting] = useState(false) const [url, setUrl] = useState('') const handleImport = async (): Promise => { - console.log('import', url) + setImporting(true) + try { + await addProfileItem({ name: 'Remote File', type: 'remote', url }) + } catch (e) { + console.error(e) + } finally { + setImporting(false) + } } + return (
@@ -33,10 +44,11 @@ const Profiles: React.FC = () => { } /> -
+ {JSON.stringify(profileConfig)}
) } diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index a3e573f8..121b602c 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -58,7 +58,7 @@ export async function getProfileItem(id: string | undefined): Promise { +export async function addProfileItem(item: Partial): Promise { await window.electron.ipcRenderer.invoke('addProfileItem', item) } diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index a8ef726f..95205768 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -131,7 +131,8 @@ interface IProfileItem { id: string type: 'remote' | 'local' name: string - url?: string + url?: string // remote + file?: string // local updated?: number extra?: { upload: number