mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 13:10:30 +08:00
Improve substore integration
This commit is contained in:
parent
1cbae3a510
commit
1773be543f
14
src/main/core/subStoreApi.ts
Normal file
14
src/main/core/subStoreApi.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { subStorePort } from '../resolve/server'
|
||||||
|
|
||||||
|
export async function subStoreSubs(): Promise<ISubStoreSub[]> {
|
||||||
|
const res = await axios.get(`http://127.0.0.1:${subStorePort}/api/subs`)
|
||||||
|
|
||||||
|
return res.data.data as ISubStoreSub[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function subStoreCollections(): Promise<ISubStoreSub[]> {
|
||||||
|
const res = await axios.get(`http://127.0.0.1:${subStorePort}/api/collections`)
|
||||||
|
|
||||||
|
return res.data.data as ISubStoreSub[]
|
||||||
|
}
|
||||||
@ -60,6 +60,7 @@ import { getInterfaces } from '../sys/interface'
|
|||||||
import { copyEnv } from '../resolve/tray'
|
import { copyEnv } from '../resolve/tray'
|
||||||
import { registerShortcut } from '../resolve/shortcut'
|
import { registerShortcut } from '../resolve/shortcut'
|
||||||
import { mainWindow } from '..'
|
import { mainWindow } from '..'
|
||||||
|
import { subStoreCollections, subStoreSubs } from '../core/subStoreApi'
|
||||||
|
|
||||||
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -168,6 +169,8 @@ export function registerIpcMainHandlers(): void {
|
|||||||
)
|
)
|
||||||
ipcMain.handle('startSubStoreServer', () => ipcErrorWrapper(startSubStoreServer)())
|
ipcMain.handle('startSubStoreServer', () => ipcErrorWrapper(startSubStoreServer)())
|
||||||
ipcMain.handle('subStorePort', () => subStorePort)
|
ipcMain.handle('subStorePort', () => subStorePort)
|
||||||
|
ipcMain.handle('subStoreSubs', () => ipcErrorWrapper(subStoreSubs)())
|
||||||
|
ipcMain.handle('subStoreCollections', () => ipcErrorWrapper(subStoreCollections)())
|
||||||
ipcMain.handle('setNativeTheme', (_e, theme) => {
|
ipcMain.handle('setNativeTheme', (_e, theme) => {
|
||||||
setNativeTheme(theme)
|
setNativeTheme(theme)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -12,8 +12,14 @@ import BasePage from '@renderer/components/base/base-page'
|
|||||||
import ProfileItem from '@renderer/components/profiles/profile-item'
|
import ProfileItem from '@renderer/components/profiles/profile-item'
|
||||||
import { useProfileConfig } from '@renderer/hooks/use-profile-config'
|
import { useProfileConfig } from '@renderer/hooks/use-profile-config'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { getFilePath, readTextFile, subStorePort } from '@renderer/utils/ipc'
|
import {
|
||||||
import { useEffect, useRef, useState } from 'react'
|
getFilePath,
|
||||||
|
readTextFile,
|
||||||
|
subStoreCollections,
|
||||||
|
subStorePort,
|
||||||
|
subStoreSubs
|
||||||
|
} from '@renderer/utils/ipc'
|
||||||
|
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { MdContentPaste } from 'react-icons/md'
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -27,6 +33,7 @@ import { SortableContext } from '@dnd-kit/sortable'
|
|||||||
import { FaPlus } from 'react-icons/fa6'
|
import { FaPlus } from 'react-icons/fa6'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
import SubStoreIcon from '@renderer/components/base/substore-icon'
|
import SubStoreIcon from '@renderer/components/base/substore-icon'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
const Profiles: React.FC = () => {
|
const Profiles: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@ -43,11 +50,51 @@ const Profiles: React.FC = () => {
|
|||||||
const { current, items = [] } = profileConfig || {}
|
const { current, items = [] } = profileConfig || {}
|
||||||
const [sortedItems, setSortedItems] = useState(items)
|
const [sortedItems, setSortedItems] = useState(items)
|
||||||
const [useProxy, setUseProxy] = useState(false)
|
const [useProxy, setUseProxy] = useState(false)
|
||||||
|
const [subStoreImporting, setSubStoreImporting] = useState(false)
|
||||||
const [importing, setImporting] = useState(false)
|
const [importing, setImporting] = useState(false)
|
||||||
const [updating, setUpdating] = useState(false)
|
const [updating, setUpdating] = useState(false)
|
||||||
const [fileOver, setFileOver] = useState(false)
|
const [fileOver, setFileOver] = useState(false)
|
||||||
const [url, setUrl] = useState('')
|
const [url, setUrl] = useState('')
|
||||||
const sensors = useSensors(useSensor(PointerSensor))
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
|
const { data: subs = [], mutate: mutateSubs } = useSWR(
|
||||||
|
useSubStore ? 'subStoreSubs' : undefined,
|
||||||
|
useSubStore ? subStoreSubs : (): undefined => {}
|
||||||
|
)
|
||||||
|
const { data: collections = [], mutate: mutateCollections } = useSWR(
|
||||||
|
useSubStore ? 'subStoreCollections' : undefined,
|
||||||
|
useSubStore ? subStoreCollections : (): undefined => {}
|
||||||
|
)
|
||||||
|
const subStoreMenuItems = useMemo(() => {
|
||||||
|
const items: { icon?: ReactNode; key: string; name: string; divider: boolean }[] = [
|
||||||
|
{
|
||||||
|
key: 'open-substore',
|
||||||
|
name: '访问 SubStore',
|
||||||
|
icon: <SubStoreIcon />,
|
||||||
|
divider: Boolean(subs) || Boolean(collections)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (subs) {
|
||||||
|
subs.forEach((sub, index) => {
|
||||||
|
items.push({
|
||||||
|
key: `sub-${sub.name}`,
|
||||||
|
name: sub.displayName,
|
||||||
|
icon: sub.icon ? <img src={sub.icon} className="h-[18px] w-[18px]" /> : null,
|
||||||
|
divider: index === subs.length - 1 && Boolean(collections)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (collections) {
|
||||||
|
collections.forEach((sub) => {
|
||||||
|
items.push({
|
||||||
|
key: `collection-${sub.name}`,
|
||||||
|
name: sub.displayName,
|
||||||
|
icon: sub.icon ? <img src={sub.icon} className="h-[18px] w-[18px]" /> : null,
|
||||||
|
divider: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}, [subs, collections])
|
||||||
const handleImport = async (): Promise<void> => {
|
const handleImport = async (): Promise<void> => {
|
||||||
setImporting(true)
|
setImporting(true)
|
||||||
await addProfileItem({ name: '', type: 'remote', url, useProxy })
|
await addProfileItem({ name: '', type: 'remote', url, useProxy })
|
||||||
@ -194,12 +241,16 @@ const Profiles: React.FC = () => {
|
|||||||
导入
|
导入
|
||||||
</Button>
|
</Button>
|
||||||
{useSubStore && (
|
{useSubStore && (
|
||||||
<Button
|
<Dropdown
|
||||||
title="SubStore"
|
onOpenChange={() => {
|
||||||
onPress={async () => {
|
mutateSubs()
|
||||||
const port = await subStorePort()
|
mutateCollections()
|
||||||
open(`https://sub-store.vercel.app/subs?api=http://127.0.0.1:${port}`)
|
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<DropdownTrigger>
|
||||||
|
<Button
|
||||||
|
isLoading={subStoreImporting}
|
||||||
|
title="SubStore"
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
@ -207,6 +258,58 @@ const Profiles: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<SubStoreIcon />
|
<SubStoreIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
</DropdownTrigger>
|
||||||
|
<DropdownMenu
|
||||||
|
onAction={async (key) => {
|
||||||
|
if (key === 'open-substore') {
|
||||||
|
const port = await subStorePort()
|
||||||
|
open(`https://sub-store.vercel.app/subs?api=http://127.0.0.1:${port}`)
|
||||||
|
} else if (key.toString().startsWith('sub-')) {
|
||||||
|
setSubStoreImporting(true)
|
||||||
|
try {
|
||||||
|
const port = await subStorePort()
|
||||||
|
const sub = subs.find(
|
||||||
|
(sub) => sub.name === key.toString().replace('sub-', '')
|
||||||
|
)
|
||||||
|
await addProfileItem({
|
||||||
|
name: sub?.displayName ?? '',
|
||||||
|
type: 'remote',
|
||||||
|
url: `http://127.0.0.1:${port}/download/${key.toString().replace('sub-', '')}?target=ClashMeta`,
|
||||||
|
useProxy
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
} finally {
|
||||||
|
setSubStoreImporting(false)
|
||||||
|
}
|
||||||
|
} else if (key.toString().startsWith('collection-')) {
|
||||||
|
setSubStoreImporting(true)
|
||||||
|
try {
|
||||||
|
const port = await subStorePort()
|
||||||
|
const sub = collections.find(
|
||||||
|
(sub) => sub.name === key.toString().replace('sub-', '')
|
||||||
|
)
|
||||||
|
await addProfileItem({
|
||||||
|
name: sub?.displayName ?? '',
|
||||||
|
type: 'remote',
|
||||||
|
url: `http://127.0.0.1:${port}/download/collection/${key.toString().replace('collection-', '')}?target=ClashMeta`,
|
||||||
|
useProxy
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
} finally {
|
||||||
|
setSubStoreImporting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subStoreMenuItems.map((item) => (
|
||||||
|
<DropdownItem startContent={item?.icon} key={item.key} showDivider={item.divider}>
|
||||||
|
{item.name}
|
||||||
|
</DropdownItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
|
|||||||
@ -299,6 +299,14 @@ export async function subStorePort(): Promise<number> {
|
|||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function subStoreSubs(): Promise<ISubStoreSub[]> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreSubs'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function subStoreCollections(): Promise<ISubStoreSub[]> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreCollections'))
|
||||||
|
}
|
||||||
|
|
||||||
export async function openFile(
|
export async function openFile(
|
||||||
type: 'profile' | 'override',
|
type: 'profile' | 'override',
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
7
src/shared/types.d.ts
vendored
7
src/shared/types.d.ts
vendored
@ -399,3 +399,10 @@ interface IProfileItem {
|
|||||||
useProxy?: boolean
|
useProxy?: boolean
|
||||||
extra?: ISubscriptionUserInfo
|
extra?: ISubscriptionUserInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ISubStoreSub {
|
||||||
|
name: string
|
||||||
|
displayName: sstring
|
||||||
|
icon?: string
|
||||||
|
tag?: string[]
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user