import axios, { AxiosInstance } from 'axios' import { getAppConfig, getControledMihomoConfig } from '../config' import { mainWindow } from '..' import WebSocket from 'ws' import { tray } from '../resolve/tray' import { calcTraffic } from '../utils/calc' import { getRuntimeConfig } from './factory' import { floatingWindow } from '../resolve/floatingWindow' import { mihomoIpcPath } from './manager' let axiosIns: AxiosInstance = null! let mihomoTrafficWs: WebSocket | null = null let trafficRetry = 10 let mihomoMemoryWs: WebSocket | null = null let memoryRetry = 10 let mihomoLogsWs: WebSocket | null = null let logsRetry = 10 let mihomoConnectionsWs: WebSocket | null = null let connectionsRetry = 10 export const getAxios = async (force: boolean = false): Promise => { if (axiosIns && !force) return axiosIns axiosIns = axios.create({ baseURL: `http://localhost`, socketPath: mihomoIpcPath, timeout: 15000 }) axiosIns.interceptors.response.use( (response) => { return response.data }, (error) => { if (error.response && error.response.data) { return Promise.reject(error.response.data) } return Promise.reject(error) } ) return axiosIns } export async function mihomoVersion(): Promise { const instance = await getAxios() return await instance.get('/version') } export const mihomoConfig = async (): Promise => { const instance = await getAxios() return await instance.get('/configs') } export const patchMihomoConfig = async (patch: Partial): Promise => { const instance = await getAxios() return await instance.patch('/configs', patch) } export const mihomoCloseConnection = async (id: string): Promise => { const instance = await getAxios() return await instance.delete(`/connections/${encodeURIComponent(id)}`) } export const mihomoCloseAllConnections = async (): Promise => { const instance = await getAxios() return await instance.delete('/connections') } export const mihomoRules = async (): Promise => { const instance = await getAxios() return await instance.get('/rules') } export const mihomoProxies = async (): Promise => { const instance = await getAxios() const proxies = (await instance.get('/proxies')) as IMihomoProxies if (!proxies.proxies['GLOBAL']) { throw new Error('GLOBAL proxy not found') } return proxies } export const mihomoGroups = async (): Promise => { 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 }) } }) 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 (mode === 'global') { const global = groups.findIndex((group) => group.name === 'GLOBAL') groups.unshift(groups.splice(global, 1)[0]) } return groups } export const mihomoProxyProviders = async (): Promise => { const instance = await getAxios() return await instance.get('/providers/proxies') } export const mihomoUpdateProxyProviders = async (name: string): Promise => { const instance = await getAxios() return await instance.put(`/providers/proxies/${encodeURIComponent(name)}`) } export const mihomoRuleProviders = async (): Promise => { const instance = await getAxios() return await instance.get('/providers/rules') } export const mihomoUpdateRuleProviders = async (name: string): Promise => { const instance = await getAxios() return await instance.put(`/providers/rules/${encodeURIComponent(name)}`) } export const mihomoChangeProxy = async (group: string, proxy: string): Promise => { const instance = await getAxios() return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy }) } export const mihomoUnfixedProxy = async (group: string): Promise => { const instance = await getAxios() return await instance.delete(`/proxies/${encodeURIComponent(group)}`) } export const mihomoUpgradeGeo = async (): Promise => { const instance = await getAxios() return await instance.post('/configs/geo') } export const mihomoProxyDelay = async (proxy: string, url?: string): Promise => { const appConfig = await getAppConfig() const { delayTestUrl, delayTestTimeout } = appConfig const instance = await getAxios() return await instance.get(`/proxies/${encodeURIComponent(proxy)}/delay`, { params: { url: url || delayTestUrl || 'https://www.gstatic.com/generate_204', timeout: delayTestTimeout || 5000 } }) } export const mihomoGroupDelay = async (group: string, url?: string): Promise => { const appConfig = await getAppConfig() const { delayTestUrl, delayTestTimeout } = appConfig const instance = await getAxios() return await instance.get(`/group/${encodeURIComponent(group)}/delay`, { params: { url: url || delayTestUrl || 'https://www.gstatic.com/generate_204', timeout: delayTestTimeout || 5000 } }) } export const mihomoUpgrade = async (): Promise => { const instance = await getAxios() return await instance.post('/upgrade') } export const startMihomoTraffic = async (): Promise => { await mihomoTraffic() } export const stopMihomoTraffic = (): void => { if (mihomoTrafficWs) { mihomoTrafficWs.removeAllListeners() if (mihomoTrafficWs.readyState === WebSocket.OPEN) { mihomoTrafficWs.close() } mihomoTrafficWs = null } } const mihomoTraffic = async (): Promise => { mihomoTrafficWs = new WebSocket(`ws+unix:${mihomoIpcPath}:/traffic`) mihomoTrafficWs.onmessage = async (e): Promise => { const data = e.data as string const json = JSON.parse(data) as IMihomoTrafficInfo trafficRetry = 10 try { mainWindow?.webContents.send('mihomoTraffic', json) if (process.platform !== 'linux') { tray?.setToolTip( '↑' + `${calcTraffic(json.up)}/s`.padStart(9) + '\n↓' + `${calcTraffic(json.down)}/s`.padStart(9) ) } floatingWindow?.webContents.send('mihomoTraffic', json) } catch { // ignore } } mihomoTrafficWs.onclose = (): void => { if (trafficRetry) { trafficRetry-- mihomoTraffic() } } mihomoTrafficWs.onerror = (): void => { if (mihomoTrafficWs) { mihomoTrafficWs.close() mihomoTrafficWs = null } } } export const startMihomoMemory = async (): Promise => { await mihomoMemory() } export const stopMihomoMemory = (): void => { if (mihomoMemoryWs) { mihomoMemoryWs.removeAllListeners() if (mihomoMemoryWs.readyState === WebSocket.OPEN) { mihomoMemoryWs.close() } mihomoMemoryWs = null } } const mihomoMemory = async (): Promise => { mihomoMemoryWs = new WebSocket(`ws+unix:${mihomoIpcPath}:/memory`) mihomoMemoryWs.onmessage = (e): void => { const data = e.data as string memoryRetry = 10 try { mainWindow?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo) } catch { // ignore } } mihomoMemoryWs.onclose = (): void => { if (memoryRetry) { memoryRetry-- mihomoMemory() } } mihomoMemoryWs.onerror = (): void => { if (mihomoMemoryWs) { mihomoMemoryWs.close() mihomoMemoryWs = null } } } export const startMihomoLogs = async (): Promise => { await mihomoLogs() } export const stopMihomoLogs = (): void => { if (mihomoLogsWs) { mihomoLogsWs.removeAllListeners() if (mihomoLogsWs.readyState === WebSocket.OPEN) { mihomoLogsWs.close() } mihomoLogsWs = null } } const mihomoLogs = async (): Promise => { const { 'log-level': logLevel = 'info' } = await getControledMihomoConfig() mihomoLogsWs = new WebSocket(`ws+unix:${mihomoIpcPath}:/logs?level=${logLevel}`) mihomoLogsWs.onmessage = (e): void => { const data = e.data as string logsRetry = 10 try { mainWindow?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo) } catch { // ignore } } mihomoLogsWs.onclose = (): void => { if (logsRetry) { logsRetry-- mihomoLogs() } } mihomoLogsWs.onerror = (): void => { if (mihomoLogsWs) { mihomoLogsWs.close() mihomoLogsWs = null } } } export const startMihomoConnections = async (): Promise => { await mihomoConnections() } export const stopMihomoConnections = (): void => { if (mihomoConnectionsWs) { mihomoConnectionsWs.removeAllListeners() if (mihomoConnectionsWs.readyState === WebSocket.OPEN) { mihomoConnectionsWs.close() } mihomoConnectionsWs = null } } const mihomoConnections = async (): Promise => { mihomoConnectionsWs = new WebSocket(`ws+unix:${mihomoIpcPath}:/connections`) mihomoConnectionsWs.onmessage = (e): void => { const data = e.data as string connectionsRetry = 10 try { mainWindow?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo) } catch { // ignore } } mihomoConnectionsWs.onclose = (): void => { if (connectionsRetry) { connectionsRetry-- mihomoConnections() } } mihomoConnectionsWs.onerror = (): void => { if (mihomoConnectionsWs) { mihomoConnectionsWs.close() mihomoConnectionsWs = null } } }