support substore

This commit is contained in:
pompurin404 2024-09-04 00:03:42 +08:00
parent ba5380664a
commit 044b676464
No known key found for this signature in database
12 changed files with 181 additions and 7 deletions

View File

@ -4,6 +4,4 @@
### New Features ### New Features
- 优化内核管理逻辑 - 内置 SubStore 服务
- 完善错误处理
- 支持节点搜索

View File

@ -271,6 +271,12 @@ const resolveRunner = () =>
file: 'mihomo-party-run.exe', file: 'mihomo-party-run.exe',
downloadURL: `https://github.com/pompurin404/mihomo-party-run/releases/download/${arch}/mihomo-party-run.exe` downloadURL: `https://github.com/pompurin404/mihomo-party-run/releases/download/${arch}/mihomo-party-run.exe`
}) })
const resolveSubstore = () =>
resolveResource({
file: 'sub-store.bundle.js',
downloadURL:
'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js'
})
const resolveFont = async () => { const resolveFont = async () => {
const targetPath = path.join(cwd, 'src', 'renderer', 'src', 'assets', 'NotoColorEmoji.ttf') const targetPath = path.join(cwd, 'src', 'renderer', 'src', 'assets', 'NotoColorEmoji.ttf')
@ -322,6 +328,11 @@ const tasks = [
func: resolveRunner, func: resolveRunner,
retry: 5, retry: 5,
winOnly: true winOnly: true
},
{
name: 'substore',
func: resolveSubstore,
retry: 5
} }
] ]

View File

@ -23,6 +23,7 @@ export async function webdavBackup(): Promise<boolean> {
zip.addLocalFile(overrideConfigPath()) zip.addLocalFile(overrideConfigPath())
zip.addLocalFolder(profilesDir(), 'profiles') zip.addLocalFolder(profilesDir(), 'profiles')
zip.addLocalFolder(overrideDir(), 'override') zip.addLocalFolder(overrideDir(), 'override')
zip.addLocalFolder(overrideDir(), 'substore')
const date = new Date() const date = new Date()
const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip` const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`

View File

@ -1,8 +1,12 @@
import { getAppConfig, getControledMihomoConfig } from '../config' import { getAppConfig, getControledMihomoConfig } from '../config'
import { Worker } from 'worker_threads'
import { subStoreDir } from '../utils/dirs'
import http from 'http' import http from 'http'
import net from 'net' import net from 'net'
import path from 'path'
export let pacPort: number export let pacPort: number
export let subStorePort: number
const defaultPacScript = ` const defaultPacScript = `
function FindProxyForURL(url, host) { function FindProxyForURL(url, host) {
@ -47,3 +51,16 @@ export async function startPacServer(): Promise<void> {
.listen(pacPort) .listen(pacPort)
server.unref() server.unref()
} }
export async function startSubStoreServer(): Promise<void> {
const { useSubStore = true } = await getAppConfig()
if (!useSubStore) return
if (subStorePort) return
subStorePort = await findAvailablePort(3000)
new Worker(path.join(subStoreDir(), 'sub-store.bundle.js'), {
env: {
SUB_STORE_BACKEND_API_PORT: subStorePort.toString(),
SUB_STORE_DATA_BASE_PATH: subStoreDir()
}
})
}

View File

@ -25,6 +25,10 @@ export function taskDir(): string {
return dir return dir
} }
export function subStoreDir(): string {
return path.join(dataDir(), 'substore')
}
export function exeDir(): string { export function exeDir(): string {
return path.dirname(exePath()) return path.dirname(exePath())
} }

View File

@ -10,7 +10,8 @@ import {
profileConfigPath, profileConfigPath,
profilePath, profilePath,
profilesDir, profilesDir,
resourcesFilesDir resourcesFilesDir,
subStoreDir
} from './dirs' } from './dirs'
import { import {
defaultConfig, defaultConfig,
@ -23,7 +24,7 @@ import yaml from 'yaml'
import { mkdir, writeFile, copyFile, rm, readdir } from 'fs/promises' import { mkdir, writeFile, copyFile, rm, readdir } from 'fs/promises'
import { existsSync } from 'fs' import { existsSync } from 'fs'
import path from 'path' import path from 'path'
import { startPacServer } from '../resolve/server' import { startPacServer, startSubStoreServer } from '../resolve/server'
import { triggerSysProxy } from '../sys/sysproxy' import { triggerSysProxy } from '../sys/sysproxy'
import { getAppConfig } from '../config' import { getAppConfig } from '../config'
import { app } from 'electron' import { app } from 'electron'
@ -47,6 +48,9 @@ async function initDirs(): Promise<void> {
if (!existsSync(mihomoTestDir())) { if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir()) await mkdir(mihomoTestDir())
} }
if (!existsSync(subStoreDir())) {
await mkdir(subStoreDir())
}
} }
async function initConfig(): Promise<void> { async function initConfig(): Promise<void> {
@ -79,7 +83,15 @@ async function initFiles(): Promise<void> {
await copyFile(sourcePath, testTargrtPath) await copyFile(sourcePath, testTargrtPath)
} }
} }
const copySubStore = async (): Promise<void> => {
const targetPath = path.join(subStoreDir(), 'sub-store.bundle.js')
const sourcePath = path.join(resourcesFilesDir(), 'sub-store.bundle.js')
if (existsSync(sourcePath)) {
await copyFile(sourcePath, targetPath)
}
}
await Promise.all([ await Promise.all([
copySubStore(),
copy('country.mmdb'), copy('country.mmdb'),
copy('geoip.dat'), copy('geoip.dat'),
copy('geosite.dat'), copy('geosite.dat'),
@ -133,6 +145,7 @@ export async function init(): Promise<void> {
await initFiles() await initFiles()
await cleanup() await cleanup()
await startPacServer() await startPacServer()
await startSubStoreServer()
const { sysProxy } = await getAppConfig() const { sysProxy } = await getAppConfig()
await triggerSysProxy(sysProxy.enable) await triggerSysProxy(sysProxy.enable)

View File

@ -42,6 +42,7 @@ import {
setOverride, setOverride,
updateOverrideItem updateOverrideItem
} from '../config' } from '../config'
import { startSubStoreServer, subStorePort } from '../resolve/server'
import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager' import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager'
import { triggerSysProxy } from '../sys/sysproxy' import { triggerSysProxy } from '../sys/sysproxy'
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater' import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
@ -165,6 +166,8 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) => ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) =>
ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action) ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action)
) )
ipcMain.handle('startSubStoreServer', () => ipcErrorWrapper(startSubStoreServer)())
ipcMain.handle('subStorePort', () => subStorePort)
ipcMain.handle('setNativeTheme', (_e, theme) => { ipcMain.handle('setNativeTheme', (_e, theme) => {
setNativeTheme(theme) setNativeTheme(theme)
}) })

View File

@ -0,0 +1,83 @@
import React from 'react'
const SubStoreIcon: React.FC = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 192 192"
width="20"
height="20"
{...props}
>
<g stroke-width="2" fill="none" stroke-linecap="butt" stroke="#ffffff" data-c-stroke="9bf6e1">
<path d="M 68.39 29.61 L 66.04 44.57" data-c-stroke="9cf7dc" />
<path d="M 49.10 42.15 Q 50.32 34.82 49.38 27.02" data-c-stroke="98f3ed" />
<path
d="M139 26.36 139.01 40.67M56.72 151.84 56.05 166.25"
data-c-stroke="9bf6e1"
stroke="#ffffff"
/>
<path d="M 139.28 166.07 L 138.77 151.74" data-c-stroke="98f3ed" />
<path d="M 121.23 143.44 L 119.51 159.49" data-c-stroke="9cf7dc" />
</g>
<path
fill="currentColor"
d="M 68.39 29.61 L 66.04 44.57 Q 58.35 38.29 49.10 42.15 Q 50.32 34.82 49.38 27.02 Q 59.70 24.72 68.39 29.61 Z"
data-c-fill="9af5e5"
/>
<path
fill="currentColor"
d="M 139.00 26.36 L 139.01 40.67 Q 129.55 40.19 122.90 47.18 Q 118.96 51.31 117.05 53.18 Q 116.67 53.55 116.30 53.17 L 107.39 44.27 A 0.43 0.43 0.0 0 1 107.39 43.67 Q 110.90 40.03 114.45 36.45 C 121.01 29.80 129.51 25.72 139.00 26.36 Z"
data-c-fill="97f2ee"
/>
<path
fill="currentColor"
d="M 139.00 26.36 C 149.79 27.49 158.88 35.13 163.59 44.55 C 168.66 54.70 165.55 66.43 158.45 74.97 Q 155.25 78.82 149.02 85.23 Q 148.70 85.57 148.37 85.24 L 139.73 76.60 Q 139.30 76.16 139.71 75.70 Q 142.64 72.41 146.67 67.43 Q 149.30 64.19 150.73 59.99 C 153.81 50.98 148.48 42.41 139.01 40.67 L 139.00 26.36 Z"
data-c-fill="9ef9d3"
/>
<path
fill="currentColor"
d="M 49.38 27.02 Q 50.32 34.82 49.10 42.15 Q 40.00 46.95 40.92 56.52 C 41.75 65.26 47.53 69.89 53.42 75.42 A 0.48 0.47 -46.3 0 1 53.43 76.10 L 44.44 85.08 Q 44.18 85.35 43.90 85.09 C 36.16 77.74 27.62 70.06 26.59 58.80 Q 25.43 46.09 34.03 36.28 Q 40.19 29.26 49.38 27.02 Z"
data-c-fill="95f0f5"
/>
<path
fill="currentColor"
d="M 66.04 44.57 L 68.39 29.61 Q 75.36 34.19 80.49 39.03 Q 90.49 48.44 105.30 63.79 Q 105.67 64.17 105.29 64.55 L 96.67 73.17 Q 96.31 73.53 95.95 73.17 Q 82.37 59.67 71.05 48.70 Q 69.90 47.58 66.04 44.57 Z"
data-c-fill="9ef9d3"
/>
<path
fill="currentColor"
fill-opacity=".961"
d="M74.45 96.86 86.25 108.66Q86.65 109.06 86.25 109.47L77.35 118.37Q77.11 118.61 76.86 118.37L55.28 96.78Q54.82 96.33 55.27 95.87 67.38 83.29 76.25 75.52 77.32 74.58 78.33 75.59L86.23 83.48Q86.55 83.81 86.23 84.14L74.45 95.91Q73.98 96.39 74.45 96.86ZM117.92 96.06 106.07 84.21Q105.67 83.81 106.07 83.4L113.56 75.91Q114.99 74.49 116.47 75.85 127.14 85.6 137.26 96.16 137.42 96.32 137.26 96.48L115.35 118.39Q115.13 118.61 114.91 118.39L106.11 109.59Q105.58 109.06 106.11 108.53L117.92 96.72A.47.47 0 0 0 117.92 96.06Z"
data-c-fill="ffffff"
data-o-fill=".961"
/>
<path
fill="currentColor"
d="M 56.72 151.84 L 56.05 166.25 C 43.95 166.03 33.55 157.74 28.69 147.07 C 23.99 136.75 27.53 125.24 34.82 116.79 Q 38.65 112.35 43.53 107.39 Q 43.98 106.93 44.43 107.38 L 53.06 116.01 Q 53.37 116.32 53.08 116.64 C 47.94 122.46 42.28 128.07 41.22 135.90 C 39.98 145.11 47.70 152.27 56.72 151.84 Z"
data-c-fill="9ef9d3"
/>
<path
fill="currentColor"
d="M 139.28 166.07 L 138.77 151.74 Q 148.28 149.97 150.90 141.99 C 152.98 135.69 150.90 128.82 146.49 124.19 Q 144.17 121.74 139.51 117.31 Q 138.89 116.72 139.50 116.10 L 148.09 107.52 Q 148.46 107.14 148.85 107.50 C 154.93 113.23 158.56 116.62 162.48 122.95 C 166.31 129.12 167.21 137.50 165.26 144.01 Q 160.45 160.01 144.46 165.22 Q 142.72 165.78 139.28 166.07 Z"
data-c-fill="95f0f5"
/>
<path
fill="currentColor"
d="M 121.23 143.44 L 119.51 159.49 Q 113.86 155.23 108.76 150.22 Q 97.69 139.36 87.37 128.74 Q 86.96 128.32 87.38 127.91 L 96.15 119.13 A 0.30 0.29 44.3 0 1 96.56 119.13 L 121.23 143.44 Z"
data-c-fill="9ef9d3"
/>
<path
fill="currentColor"
d="M 56.05 166.25 L 56.72 151.84 Q 62.78 151.21 66.28 148.29 Q 71.06 144.32 75.45 139.32 A 0.62 0.62 0.0 0 1 76.35 139.29 L 85.16 148.10 Q 85.45 148.40 85.18 148.71 Q 79.76 155.07 74.12 159.60 Q 66.32 165.87 56.05 166.25 Z"
data-c-fill="97f2ee"
/>
<path
fill="currentColor"
d="M 121.23 143.44 C 126.74 148.19 130.95 152.38 138.77 151.74 L 139.28 166.07 Q 128.14 166.99 119.51 159.49 L 121.23 143.44 Z"
data-c-fill="9af5e5"
/>
</svg>
)
export default SubStoreIcon

View File

@ -10,7 +10,8 @@ import {
disableAutoRun, disableAutoRun,
enableAutoRun, enableAutoRun,
relaunchApp, relaunchApp,
restartCore restartCore,
startSubStoreServer
} from '@renderer/utils/ipc' } from '@renderer/utils/ipc'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
import { platform } from '@renderer/utils/init' import { platform } from '@renderer/utils/init'
@ -26,6 +27,7 @@ const GeneralConfig: React.FC = () => {
showTraffic = true, showTraffic = true,
proxyInTray = true, proxyInTray = true,
useWindowFrame = false, useWindowFrame = false,
useSubStore = true,
envType = platform === 'win32' ? 'powershell' : 'bash', envType = platform === 'win32' ? 'powershell' : 'bash',
autoCheckUpdate, autoCheckUpdate,
appTheme = 'system' appTheme = 'system'
@ -154,6 +156,20 @@ const GeneralConfig: React.FC = () => {
</SettingItem> </SettingItem>
</> </>
)} )}
<SettingItem title="启用SubStore" divider>
<Switch
size="sm"
isSelected={useSubStore}
onValueChange={async (v) => {
try {
await patchAppConfig({ useSubStore: v })
if (v) await startSubStoreServer()
} catch (e) {
alert(e)
}
}}
/>
</SettingItem>
<SettingItem title="使用系统标题栏" divider> <SettingItem title="使用系统标题栏" divider>
<Switch <Switch
size="sm" size="sm"

View File

@ -11,7 +11,8 @@ import {
import BasePage from '@renderer/components/base/base-page' 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 { getFilePath, readTextFile } from '@renderer/utils/ipc' import { useAppConfig } from '@renderer/hooks/use-app-config'
import { getFilePath, readTextFile, subStorePort } from '@renderer/utils/ipc'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { MdContentPaste } from 'react-icons/md' import { MdContentPaste } from 'react-icons/md'
import { import {
@ -25,6 +26,7 @@ import {
import { SortableContext } from '@dnd-kit/sortable' 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'
const Profiles: React.FC = () => { const Profiles: React.FC = () => {
const { const {
@ -36,6 +38,8 @@ const Profiles: React.FC = () => {
changeCurrentProfile, changeCurrentProfile,
mutateProfileConfig mutateProfileConfig
} = useProfileConfig() } = useProfileConfig()
const { appConfig } = useAppConfig()
const { useSubStore = true } = appConfig || {}
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)
@ -221,6 +225,21 @@ const Profiles: React.FC = () => {
<DropdownItem key="new"></DropdownItem> <DropdownItem key="new"></DropdownItem>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
{useSubStore && (
<Button
title="SubStore"
onPress={async () => {
const port = await subStorePort()
open(`https://sub-store.vercel.app/subs?api=http://127.0.0.1:${port}`)
}}
className="ml-2"
size="sm"
isIconOnly
color="primary"
>
<SubStoreIcon />
</Button>
)}
</div> </div>
<Divider /> <Divider />
</div> </div>

View File

@ -291,6 +291,14 @@ export async function setNativeTheme(theme: 'system' | 'light' | 'dark'): Promis
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setNativeTheme', theme)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setNativeTheme', theme))
} }
export async function startSubStoreServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreServer'))
}
export async function subStorePort(): Promise<number> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
}
export async function openFile( export async function openFile(
type: 'profile' | 'override', type: 'profile' | 'override',
id: string, id: string,

View File

@ -219,6 +219,7 @@ interface IAppConfig {
proxyCols: 'auto' | '1' | '2' | '3' | '4' proxyCols: 'auto' | '1' | '2' | '3' | '4'
connectionDirection: 'asc' | 'desc' connectionDirection: 'asc' | 'desc'
connectionOrderBy: 'time' | 'upload' | 'download' | 'uploadSpeed' | 'downloadSpeed' connectionOrderBy: 'time' | 'upload' | 'download' | 'uploadSpeed' | 'downloadSpeed'
useSubStore?: boolean
autoSetDNS?: boolean autoSetDNS?: boolean
originDNS?: string originDNS?: string
useWindowFrame: boolean useWindowFrame: boolean