import axios, { AxiosInstance } from 'axios' import { getAppConfig, getControledMihomoConfig } from '../config' import templateIcon from '../../../resources/iconTemplate.png?asset' import { mainWindow } from '..' import WebSocket from 'ws' import { tray } from '../resolve/tray' import { calcTraffic } from '../utils/calc' import { getRuntimeConfig } from './factory' import { nativeImage } from 'electron' import svg2img from 'svg2img' const icon = nativeImage.createFromPath(templateIcon) icon.setTemplateImage(true) const base64 = icon.toPNG().toString('base64') 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 const controledMihomoConfig = await getControledMihomoConfig() let server = controledMihomoConfig['external-controller'] const secret = controledMihomoConfig.secret ?? '' if (server?.startsWith(':')) server = `127.0.0.1${server}` axiosIns = axios.create({ baseURL: `http://${server}`, proxy: false, headers: secret ? { Authorization: `Bearer ${secret}` } : {}, 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 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() return await instance.get('/proxies') } export const mihomoGroups = async (): Promise => { const proxies = await mihomoProxies() const runtime = await getRuntimeConfig() const groups: IMihomoMixedGroup[] = [] runtime?.['proxy-groups']?.forEach((group: { name: string; url?: string }) => { group = Object.assign(group, group['<<']) 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 const newAll = newGlobal.all.map((name) => proxies.proxies[name]) groups.push({ ...newGlobal, all: newAll }) } 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 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 => { const { showTraffic = true } = await getAppConfig() const controledMihomoConfig = await getControledMihomoConfig() let server = controledMihomoConfig['external-controller'] const secret = controledMihomoConfig.secret ?? '' if (server?.startsWith(':')) server = `127.0.0.1${server}` stopMihomoTraffic() mihomoTrafficWs = new WebSocket(`ws://${server}/traffic?token=${encodeURIComponent(secret)}`) mihomoTrafficWs.onmessage = (e): void => { const data = e.data as string const json = JSON.parse(data) as IMihomoTrafficInfo if (showTraffic) { const svgContent = ` ↑ ${calcTraffic(json.up)}/s ↓ ${calcTraffic(json.down)}/s ` svg2img(svgContent, {}, (error, buffer) => { if (error) return const image = nativeImage.createFromBuffer(buffer).resize({ height: 16 }) image.setTemplateImage(true) tray?.setImage(image) }) } else { tray?.setImage(icon) } if (process.platform !== 'linux') { tray?.setToolTip( '↑' + `${calcTraffic(json.up)}/s`.padStart(9) + '\n↓' + `${calcTraffic(json.down)}/s`.padStart(9) ) } trafficRetry = 10 mainWindow?.webContents.send('mihomoTraffic', json) } 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 => { const controledMihomoConfig = await getControledMihomoConfig() let server = controledMihomoConfig['external-controller'] const secret = controledMihomoConfig.secret ?? '' if (server?.startsWith(':')) server = `127.0.0.1${server}` stopMihomoMemory() mihomoMemoryWs = new WebSocket(`ws://${server}/memory?token=${encodeURIComponent(secret)}`) mihomoMemoryWs.onmessage = (e): void => { const data = e.data as string memoryRetry = 10 mainWindow?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo) } 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 controledMihomoConfig = await getControledMihomoConfig() const { secret = '', 'log-level': level = 'info' } = controledMihomoConfig let { 'external-controller': server } = controledMihomoConfig if (server?.startsWith(':')) server = `127.0.0.1${server}` stopMihomoLogs() mihomoLogsWs = new WebSocket( `ws://${server}/logs?token=${encodeURIComponent(secret)}&level=${level}` ) mihomoLogsWs.onmessage = (e): void => { const data = e.data as string logsRetry = 10 mainWindow?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo) } 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 => { const controledMihomoConfig = await getControledMihomoConfig() let server = controledMihomoConfig['external-controller'] const secret = controledMihomoConfig.secret ?? '' if (server?.startsWith(':')) server = `127.0.0.1${server}` stopMihomoConnections() mihomoConnectionsWs = new WebSocket( `ws://${server}/connections?token=${encodeURIComponent(secret)}` ) mihomoConnectionsWs.onmessage = (e): void => { const data = e.data as string connectionsRetry = 10 mainWindow?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo) } mihomoConnectionsWs.onclose = (): void => { if (connectionsRetry) { connectionsRetry-- mihomoConnections() } } mihomoConnectionsWs.onerror = (): void => { if (mihomoConnectionsWs) { mihomoConnectionsWs.close() mihomoConnectionsWs = null } } } export const pauseWebsockets = () => { const recoverList: (() => void)[] = [] // Traffic 始终开启 stopMihomoTraffic() recoverList.push(startMihomoTraffic) if (mihomoMemoryWs) { stopMihomoMemory() recoverList.push(startMihomoMemory) } if (mihomoLogsWs) { stopMihomoLogs() recoverList.push(startMihomoLogs) } if (mihomoConnectionsWs) { stopMihomoConnections() recoverList.push(startMihomoConnections) } return (): void => { recoverList.forEach((recover) => recover()) } }