fix: support provider proxies after mihomo api change

This commit is contained in:
zjdndjf
2026-07-02 22:04:20 +08:00
parent 4d11d2b00d
commit 7ea11bb645
4 changed files with 104 additions and 26 deletions

View File

@@ -238,31 +238,95 @@ export const mihomoProxies = async (): Promise<IMihomoProxies> => {
return proxies
}
function isMihomoGroup(proxy: IMihomoProxy | IMihomoGroup | undefined): proxy is IMihomoGroup {
return Boolean(proxy && 'all' in proxy)
}
const PROVIDER_DETAIL_FETCH_THRESHOLD = 8
async function mihomoProxyProvider(name: string): Promise<IMihomoProxyProvider> {
const instance = await getAxios()
return await instance.get(`/providers/proxies/${encodeURIComponent(name)}`)
}
async function resolveProviderProxies(
names: Set<string>,
providerNames: Set<string>,
fallbackToAllProviders: boolean
): Promise<Record<string, IMihomoProxy>> {
if (names.size === 0) return {}
const providers =
fallbackToAllProviders || providerNames.size > PROVIDER_DETAIL_FETCH_THRESHOLD
? Object.values((await mihomoProxyProviders()).providers)
: (
await Promise.allSettled([...providerNames].map((name) => mihomoProxyProvider(name)))
).flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []))
const providerProxies: Record<string, IMihomoProxy> = {}
providers.forEach((provider) => {
provider.proxies?.forEach((proxy) => {
if (names.has(proxy.name)) {
providerProxies[proxy.name] = proxy
}
})
})
return providerProxies
}
export const mihomoGroups = async (): Promise<IMihomoMixedGroup[]> => {
const { mode = 'rule' } = await getControledMihomoConfig()
if (mode === 'direct') return []
const proxies = await mihomoProxies()
const runtime = await getRuntimeConfig()
const groups: IMihomoMixedGroup[] = []
runtime?.['proxy-groups']?.forEach((group: { name: string; url?: string }) => {
const { name, url } = group
if (proxies.proxies[name] && 'all' in proxies.proxies[name] && !proxies.proxies[name].hidden) {
const newGroup = proxies.proxies[name]
newGroup.testUrl = url
const newAll = (newGroup.all || []).map((name) => proxies.proxies[name])
groups.push({ ...newGroup, all: newAll })
const [proxies, runtime] = await Promise.all([mihomoProxies(), getRuntimeConfig()])
const rawGroups: { group: IMihomoGroup; providers: string[] }[] = []
runtime?.['proxy-groups']?.forEach((group: { name: string; url?: string; use?: string[] }) => {
const proxy = proxies.proxies[group.name]
if (isMihomoGroup(proxy) && !proxy.hidden) {
rawGroups.push({ group: { ...proxy, testUrl: group.url }, providers: group.use || [] })
}
})
if (!groups.find((group) => group.name === 'GLOBAL')) {
const newGlobal = proxies.proxies['GLOBAL'] as IMihomoGroup
if (!newGlobal.hidden) {
const newAll = (newGlobal.all || []).map((name) => proxies.proxies[name])
groups.push({ ...newGlobal, all: newAll })
if (!rawGroups.find(({ group }) => group.name === 'GLOBAL')) {
const global = proxies.proxies['GLOBAL']
if (isMihomoGroup(global) && !global.hidden) {
rawGroups.push({ group: global, providers: [] })
}
}
const missingProxyNames = new Set<string>()
const providerNames = new Set<string>()
let fallbackToAllProviders = false
rawGroups.forEach(({ group, providers }) => {
const proxyNames = group.all || []
proxyNames.forEach((name) => {
if (!proxies.proxies[name]) {
missingProxyNames.add(name)
if (providers.length > 0) {
providers.forEach((provider) => providerNames.add(provider))
} else {
fallbackToAllProviders = true
}
}
})
})
const providerProxies = await resolveProviderProxies(
missingProxyNames,
providerNames,
fallbackToAllProviders
)
const groups: IMihomoMixedGroup[] = []
rawGroups.forEach(({ group }) => {
const newAll = (group.all || [])
.map((name) => proxies.proxies[name] || providerProxies[name])
.filter((proxy): proxy is IMihomoProxy | IMihomoGroup => Boolean(proxy))
groups.push({ ...group, all: newAll })
})
if (mode === 'global') {
const global = groups.findIndex((group) => group.name === 'GLOBAL')
groups.unshift(groups.splice(global, 1)[0])
if (global > 0) groups.unshift(groups.splice(global, 1)[0])
}
return groups
}
@@ -302,11 +366,18 @@ export const mihomoUpgradeGeo = async (): Promise<void> => {
return await instance.post('/configs/geo')
}
export const mihomoProxyDelay = async (proxy: string, url?: string): Promise<IMihomoDelay> => {
export const mihomoProxyDelay = async (
proxy: string,
url?: string,
provider?: string
): Promise<IMihomoDelay> => {
const appConfig = await getAppConfig()
const { delayTestUrl, delayTestTimeout } = appConfig
const instance = await getAxios()
return await instance.get(`/proxies/${encodeURIComponent(proxy)}/delay`, {
const path = provider
? `/providers/proxies/${encodeURIComponent(provider)}/${encodeURIComponent(proxy)}/healthcheck`
: `/proxies/${encodeURIComponent(proxy)}/delay`
return await instance.get(path, {
params: {
url: delayTestUrl || url || 'https://www.gstatic.com/generate_204',
timeout: delayTestTimeout || 5000

View File

@@ -7,7 +7,7 @@ import { KeyedMutator } from 'swr'
interface Props {
mutateProxies: KeyedMutator<IMihomoMixedGroup[]>
onProxyDelay: (proxy: string, url?: string) => Promise<IMihomoDelay>
onProxyDelay: (proxy: IMihomoProxy | IMihomoGroup, url?: string) => Promise<IMihomoDelay>
proxyDisplayMode: 'simple' | 'full'
proxy: IMihomoProxy | IMihomoGroup
group: IMihomoMixedGroup
@@ -55,11 +55,11 @@ const ProxyItemBase: React.FC<Props> = (props) => {
const onDelay = useCallback((): void => {
setLoading(true)
onProxyDelay(proxy.name, group.testUrl).finally(() => {
onProxyDelay(proxy, group.testUrl).finally(() => {
mutateProxies()
setLoading(false)
})
}, [proxy.name, group.testUrl, onProxyDelay, mutateProxies])
}, [proxy, group.testUrl, onProxyDelay, mutateProxies])
const fixed = useMemo(() => group.fixed && group.fixed === proxy.name, [group.fixed, proxy.name])

View File

@@ -24,6 +24,10 @@ import { useTranslation } from 'react-i18next'
const GROUP_EXPAND_STATE_KEY = 'proxy_group_expand_state'
function getProviderName(proxy: IMihomoProxy | IMihomoGroup): string | undefined {
return 'provider-name' in proxy ? proxy['provider-name'] : undefined
}
// 自定义 hook 用于管理展开状态
const useProxyState = (
groups: IMihomoMixedGroup[]
@@ -191,9 +195,12 @@ const Proxies: React.FC = () => {
[autoCloseConnection, mutate]
)
const onProxyDelay = useCallback(async (proxy: string, url?: string): Promise<IMihomoDelay> => {
return await mihomoProxyDelay(proxy, url)
}, [])
const onProxyDelay = useCallback(
async (proxy: IMihomoProxy | IMihomoGroup, url?: string): Promise<IMihomoDelay> => {
return await mihomoProxyDelay(proxy.name, url, getProviderName(proxy))
},
[]
)
// 组测速时逐节点写回会造成 O(N²) 分配与 N 次 allProxies 重算
const pendingDelayResults = useRef<Map<string, Map<string, { time: string; delay: number }>>>(
@@ -299,7 +306,7 @@ const Proxies: React.FC = () => {
const promise = Promise.resolve().then(async () => {
let res: IMihomoDelay | undefined
try {
res = await mihomoProxyDelay(proxy.name, groups[index].testUrl)
res = await mihomoProxyDelay(proxy.name, groups[index].testUrl, getProviderName(proxy))
} catch {
// ignore
}

View File

@@ -31,7 +31,7 @@ interface IpcApi {
mihomoUpgradeGeo: () => Promise<void>
mihomoUpgrade: () => Promise<void>
mihomoUpgradeUI: () => Promise<void>
mihomoProxyDelay: (proxy: string, url?: string) => Promise<IMihomoDelay>
mihomoProxyDelay: (proxy: string, url?: string, provider?: string) => Promise<IMihomoDelay>
mihomoGroupDelay: (group: string, url?: string) => Promise<IMihomoGroupDelay>
patchMihomoConfig: (patch: Partial<IMihomoConfig>) => Promise<void>
mihomoSmartGroupWeights: (groupName: string) => Promise<Record<string, number>>