Compare commits

...

5 Commits

Author SHA1 Message Date
ezequielnick
e35fa316fc fix: autoupdater admin privileges issue 2025-08-27 19:53:21 +08:00
ezequielnick
4bea1b93cc 1.8.5 Released 2025-08-27 14:15:34 +08:00
ezequielnick
7089b3ca5b chore: add smart core settings tootips 2025-08-27 14:11:29 +08:00
Memory
add4196dc5
fix: clean up outdated sub-store.bundle.js 2025-08-27 12:59:39 +08:00
Memory
78ec7f9822
opt: tray icon updating 2025-08-27 12:59:28 +08:00
17 changed files with 180 additions and 41 deletions

View File

@ -17,7 +17,7 @@
<img width='90%' src="./images/preview.jpg">
</div>
### 本项目“[狗狗加速](https://party.dginv.click/#/register?code=ARdo0mXx)”赞助
### 本项目认证稳定机场推荐:“[狗狗加速](https://party.dginv.click/#/register?code=ARdo0mXx)”
##### [狗狗加速 —— 技术流机场 Doggygo VPN](https://party.dginv.click/#/register?code=ARdo0mXx)
- 高性能海外机场,稳定首选,海外团队,无跑路风险
@ -30,10 +30,11 @@
### 特性
- [x] 一键 Smart Core 规则覆写,基于 AI 模型自动选择最优节点 详细介绍请看 [这里](https://mihomo.party/docs/guide/smart-core)
- [x] 开箱即用,无需服务模式的 Tun
- [x] 多种配色主题可选UI 焕然一新
- [x] 支持大部分 Mihomo 常用配置修改
- [x] 内置稳定版和预览版 Mihomo 内核
- [x] 支持大部分 Mihomo(Clash Meta) 常用配置修改
- [x] 内置 Smart内核 与 Mihomo(Clash Meta) 内核
- [x] 通过 WebDAV 一键备份和恢复配置
- [x] 强大的覆写功能,任意修订配置文件
- [x] 深度集成 Sub-Store轻松管理订阅

View File

@ -1,6 +1,9 @@
## 1.8.5
**本次更新增加了大家期待已久的托盘图标根据代理状态实时变化颜色,系统代理为蓝色,虚拟网卡为绿色,双开为红色;完善 Smart 规则写,当原有规则中有 url-test 和 load-balance 组时,自动替换为 Smart 规则组;当没有 url-test 和 load-balance 时,增加 全局 Smart 规则组,接管全部流量**
**🎉 本次更新增加了大家期待已久的托盘图标根据代理状态变化颜色,系统代理为蓝色,虚拟网卡为绿色,双开为红色
⚡ 完善 Smart 规则写,当原有规则中有 url-test 和 load-balance 组时,自动替换为 Smart 规则组;当没有 url-test 和 load-balance 时,增加 全局 Smart 规则组,接管全部流量**
#### 新增 Smart Core 详细文档: [点击查看](https://mihomo.party/docs/guide/smart-core)
### 新功能 (Feat)
- 支持 cron 表达式以自定义订阅更新频率(#766)
@ -44,13 +47,4 @@
### 修复 (Fix)
- 修复 DNS/嗅探覆写开关逻辑,修改设置不再会直接写入运行时配置,增加了“仅保存”按钮
- 悬浮窗改为纯矩形,修复 windows 下兼容性问题带来的白边
## 1.8.2
**本次更新主要集中在重大内核更新和依赖升级后所产生的 bug 修复解决了自1.7版以后首次安装无法启动的问题,推荐更新**
### 新功能 (Feat)
- 重构 域名嗅探 卡片模块,改为“覆写”逻辑,当开关打开后,使用 嗅探覆写 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
- 订阅/覆写卡片可右键呼出菜单
- MacOS 下“轻触(tap)”触控板可进行开关操作(之前必须“按下(click)”)
- 悬浮窗改为纯矩形,修复 windows 下兼容性问题带来的白边

View File

@ -1,6 +1,6 @@
{
"name": "mihomo-party",
"version": "1.8.5-dev",
"version": "1.8.5",
"description": "Mihomo Party",
"main": "./out/main/index.js",
"author": "mihomo-party-org",

View File

@ -375,9 +375,7 @@ export const TunStatus = async (): Promise<boolean> => {
return config?.tun?.enable === true
}
export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> {
const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()])
export function calculateTrayIconStatus(sysProxyEnabled: boolean, tunEnabled: boolean): 'white' | 'blue' | 'green' | 'red' {
if (sysProxyEnabled && tunEnabled) {
return 'red' // 系统代理 + TUN 同时启用(警告状态)
} else if (sysProxyEnabled) {
@ -387,4 +385,9 @@ export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' |
} else {
return 'white' // 全关
}
}
export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> {
const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()])
return calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
}

View File

@ -9,6 +9,8 @@ import { existsSync } from 'fs'
import os from 'os'
import { exec, execSync, spawn } from 'child_process'
import { promisify } from 'util'
import { appLogger } from '../utils/logger'
import { checkAdminPrivileges } from '../core/manager'
export async function checkUpdate(): Promise<IAppVersion | undefined> {
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
@ -95,10 +97,45 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
await writeFile(path.join(dataDir(), file), res.data)
}
if (file.endsWith('.exe')) {
spawn(path.join(dataDir(), file), ['/S', '--force-run'], {
detached: true,
stdio: 'ignore'
}).unref()
try {
const installerPath = path.join(dataDir(), file)
const isAdmin = await checkAdminPrivileges()
if (isAdmin) {
await appLogger.info('Running installer with existing admin privileges')
spawn(installerPath, ['/S', '--force-run'], {
detached: true,
stdio: 'ignore'
}).unref()
} else {
// 提升权限安装
const escapedPath = installerPath.replace(/'/g, "''")
const args = ['/S', '--force-run']
const argsString = args.map(arg => arg.replace(/'/g, "''")).join("', '")
const command = `powershell -Command "Start-Process -FilePath '${escapedPath}' -ArgumentList '${argsString}' -Verb RunAs -WindowStyle Hidden"`
await appLogger.info('Starting installer with elevated privileges')
const execPromise = promisify(exec)
await execPromise(command, { windowsHide: true })
await appLogger.info('Installer started successfully with elevation')
}
} catch (installerError) {
await appLogger.error('Failed to start installer, trying fallback', installerError)
// Fallback: 尝试使用shell.openPath打开安装包
try {
await shell.openPath(path.join(dataDir(), file))
await appLogger.info('Opened installer with shell.openPath as fallback')
} catch (fallbackError) {
await appLogger.error('Fallback method also failed', fallbackError)
const installerErrorMessage = installerError instanceof Error ? installerError.message : String(installerError)
const fallbackErrorMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError)
throw new Error(`Failed to execute installer: ${installerErrorMessage}. Fallback also failed: ${fallbackErrorMessage}`)
}
}
}
if (file.endsWith('.7z')) {
await copyFile(path.join(resourcesFilesDir(), '7za.exe'), path.join(dataDir(), '7za.exe'))

View File

@ -15,7 +15,7 @@ import pngIconBlue from '../../../resources/icon_blue.png?asset'
import pngIconRed from '../../../resources/icon_red.png?asset'
import pngIconGreen from '../../../resources/icon_green.png?asset'
import templateIcon from '../../../resources/iconTemplate.png?asset'
import { mihomoChangeProxy, mihomoCloseAllConnections, mihomoGroups, patchMihomoConfig, getTrayIconStatus } from '../core/mihomoApi'
import { mihomoChangeProxy, mihomoCloseAllConnections, mihomoGroups, patchMihomoConfig, getTrayIconStatus, calculateTrayIconStatus } from '../core/mihomoApi'
import { mainWindow, showMainWindow, triggerMainWindow } from '..'
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
@ -458,6 +458,27 @@ const getIconPaths = () => {
}
}
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
if (!tray) return
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
const iconPaths = getIconPaths()
const iconPath = iconPaths[status]
try {
if (process.platform === 'darwin') {
const icon = nativeImage.createFromPath(iconPath).resize({ height: 16 })
tray.setImage(icon)
} else if (process.platform === 'win32') {
tray.setImage(iconPath)
} else if (process.platform === 'linux') {
tray.setImage(iconPath)
}
} catch (error) {
console.error('更新托盘图标失败:', error)
}
}
export async function updateTrayIcon(): Promise<void> {
if (!tray) return

View File

@ -22,7 +22,7 @@ import {
defaultProfileConfig
} from './template'
import yaml from 'yaml'
import { mkdir, writeFile, rm, readdir, cp, stat } from 'fs/promises'
import { mkdir, writeFile, rm, readdir, cp, stat, rename } from 'fs/promises'
import { existsSync } from 'fs'
import { exec } from 'child_process'
import { promisify } from 'util'
@ -218,6 +218,19 @@ async function cleanup(): Promise<void> {
}
}
async function migrateSubStoreFiles(): Promise<void> {
const oldJsPath = path.join(mihomoWorkDir(), 'sub-store.bundle.js')
const newCjsPath = path.join(mihomoWorkDir(), 'sub-store.bundle.cjs')
if (existsSync(oldJsPath) && !existsSync(newCjsPath)) {
try {
await rename(oldJsPath, newCjsPath)
} catch (error) {
await initLogger.error('Failed to rename sub-store.bundle.js to sub-store.bundle.cjs', error)
}
}
}
async function migration(): Promise<void> {
const {
siderOrder = [
@ -334,6 +347,7 @@ export async function initBasic(): Promise<void> {
await initDirs()
await initConfig()
await migration()
await migrateSubStoreFiles()
await initFiles()
await cleanup()
}

View File

@ -84,7 +84,7 @@ import {
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore } from '../resolve/backup'
import { getInterfaces } from '../sys/interface'
import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon } from '../resolve/tray'
import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon, updateTrayIconImmediate } from '../resolve/tray'
import { registerShortcut } from '../resolve/shortcut'
import { closeMainWindow, mainWindow, showMainWindow, triggerMainWindow } from '..'
import {
@ -260,6 +260,9 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('showTrayIcon', () => ipcErrorWrapper(showTrayIcon)())
ipcMain.handle('closeTrayIcon', () => ipcErrorWrapper(closeTrayIcon)())
ipcMain.handle('updateTrayIcon', () => ipcErrorWrapper(updateTrayIcon)())
ipcMain.handle('updateTrayIconImmediate', (_e, sysProxyEnabled, tunEnabled) => {
updateTrayIconImmediate(sysProxyEnabled, tunEnabled)
})
ipcMain.handle('showMainWindow', showMainWindow)
ipcMain.handle('closeMainWindow', closeMainWindow)
ipcMain.handle('triggerMainWindow', triggerMainWindow)

View File

@ -2,7 +2,8 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
import BorderSwitch from '@renderer/components/base/border-swtich'
import { useLocation, useNavigate } from 'react-router-dom'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { triggerSysProxy, updateTrayIcon } from '@renderer/utils/ipc'
import { triggerSysProxy, updateTrayIcon, updateTrayIconImmediate } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { AiOutlineGlobal } from 'react-icons/ai'
import React from 'react'
import { useSortable } from '@dnd-kit/sortable'
@ -20,7 +21,9 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
const navigate = useNavigate()
const match = location.pathname.includes('/sysproxy')
const { appConfig, patchAppConfig } = useAppConfig()
const { controledMihomoConfig } = useControledMihomoConfig()
const { sysProxy, sysproxyCardStatus = 'col-span-1' } = appConfig || {}
const { tun } = controledMihomoConfig || {}
const { enable } = sysProxy || {}
const {
attributes,
@ -35,8 +38,11 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
const previousState = !enable
const tunEnabled = tun?.enable ?? false
// 立即更新图标
updateTrayIconImmediate(enable, tunEnabled)
// 立即更新UI
try {
await patchAppConfig({ sysProxy: { enable } })
await triggerSysProxy(enable)
@ -46,6 +52,8 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
await updateTrayIcon()
} catch (e) {
await patchAppConfig({ sysProxy: { enable: previousState } })
// 回滚图标
updateTrayIconImmediate(previousState, tunEnabled)
alert(e)
}
}

View File

@ -3,7 +3,7 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
import BorderSwitch from '@renderer/components/base/border-swtich'
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
import { useLocation, useNavigate } from 'react-router-dom'
import { restartCore, updateTrayIcon } from '@renderer/utils/ipc'
import { restartCore, updateTrayIcon, updateTrayIconImmediate } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import React from 'react'
@ -22,6 +22,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
const match = location.pathname.includes('/tun') || false
const { appConfig } = useAppConfig()
const { tunCardStatus = 'col-span-1' } = appConfig || {}
const sysProxyEnabled = appConfig?.sysProxy?.enable ?? false
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { tun } = controledMihomoConfig || {}
const { enable } = tun || {}
@ -37,6 +38,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
})
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
updateTrayIconImmediate(sysProxyEnabled, enable)
if (enable) {
try {
// 检查内核权限
@ -54,9 +56,11 @@ const TunSwitcher: React.FC<Props> = (props) => {
} catch (error) {
console.error('Failed to restart as admin:', error)
await window.electron.ipcRenderer.invoke('showErrorDialog', t('tun.permissions.failed'), String(error))
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
} else {
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
} else {
@ -66,6 +70,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
} catch (error) {
console.warn('Permission grant failed:', error)
await window.electron.ipcRenderer.invoke('showErrorDialog', t('tun.permissions.failed'), String(error))
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
}

View File

@ -55,17 +55,18 @@ const UpdaterModal: React.FC<Props> = (props) => {
</Button>
</ModalHeader>
<ModalBody className="h-full">
<ReactMarkdown
className="markdown-body select-text"
components={{
a: ({ ...props }) => <a target="_blank" className="text-primary" {...props} />,
code: ({ children }) => <Code size="sm">{children}</Code>,
h3: ({ ...props }) => <h3 className="text-lg font-bold" {...props} />,
li: ({ children }) => <li className="list-disc list-inside">{children}</li>
}}
>
{changelog}
</ReactMarkdown>
<div className="markdown-body select-text">
<ReactMarkdown
components={{
a: ({ ...props }) => <a target="_blank" className="text-primary" {...props} />,
code: ({ children }) => <Code size="sm">{children}</Code>,
h3: ({ ...props }) => <h3 className="text-lg font-bold" {...props} />,
li: ({ children }) => <li className="list-disc list-inside">{children}</li>
}}
>
{changelog}
</ReactMarkdown>
</div>
</ModalBody>
<ModalFooter>
<Button size="sm" variant="light" onPress={onClose}>

View File

@ -127,6 +127,8 @@
"mihomo.smartCoreStrategy": "Strategy Mode",
"mihomo.smartCoreStrategyStickySession": "Sticky Sessions",
"mihomo.smartCoreStrategyRoundRobin": "Round Robin",
"mihomo.smartCoreUseLightGBMTooltip": "Use pre-trained general model for quick node selection improvement, but may not suit your specific network environment",
"mihomo.smartCoreCollectDataTooltip": "Collect your network usage data for training custom models better suited to your network environment (turn off if you don't know how to train models)",
"mihomo.mixedPort": "Mixed Port",
"mihomo.confirm": "Confirm",
"mihomo.socksPort": "Socks Port",

View File

@ -115,6 +115,17 @@
"mihomo.selectCoreVersion": "انتخاب نسخه هسته",
"mihomo.stableVersion": "نسخه پایدار",
"mihomo.alphaVersion": "نسخه آلفا",
"mihomo.smartVersion": "Smart",
"mihomo.enableSmartCore": "فعال‌سازی Smart Core",
"mihomo.enableSmartOverride": "استفاده از بازنویسی خودکار Smart",
"mihomo.smartOverrideTooltip": "از اسکریپت بازنویسی هوشمند داخلی Party برای جایگزینی url-test و load-balance با گروه قواعد Smart استفاده کنید؛ اگر گروه‌های قواعد فوق وجود ندارند، تمام گره‌ها را از اشتراک استخراج کرده و قواعد پیش‌فرض را جایگزین کنید، برای کاربرانی که نمی‌خواهند دردسر باشند مناسب است، قابلیت با یک کلیک فعال می‌شود؛ اگر از حالت جهانی استفاده می‌کنید، لطفاً گره با نام 'Smart Group' را انتخاب کنید",
"mihomo.smartCoreUseLightGBM": "استفاده از LightGBM",
"mihomo.smartCoreCollectData": "جمع‌آوری داده‌ها",
"mihomo.smartCoreStrategy": "حالت استراتژی",
"mihomo.smartCoreStrategyStickySession": "نشست‌های چسبنده",
"mihomo.smartCoreStrategyRoundRobin": "نوبتی دایره‌ای",
"mihomo.smartCoreUseLightGBMTooltip": "از مدل عمومی پیش‌آموزش دیده برای بهبود سریع انتخاب گره استفاده کنید، اما ممکن است برای محیط شبکه خاص شما مناسب نباشد",
"mihomo.smartCoreCollectDataTooltip": "داده‌های استفاده شبکه شما را برای آموزش مدل‌های سفارشی متناسب با محیط شبکه شما جمع‌آوری کنید (اگر نمی‌دانید چگونه مدل‌ها را آموزش دهید، آن را خاموش کنید)",
"mihomo.mixedPort": "پورت ترکیبی",
"mihomo.confirm": "تایید",
"mihomo.socksPort": "پورت Socks",

View File

@ -115,6 +115,17 @@
"mihomo.selectCoreVersion": "Выберите версию ядра",
"mihomo.stableVersion": "Стабильная",
"mihomo.alphaVersion": "Альфа",
"mihomo.smartVersion": "Smart",
"mihomo.enableSmartCore": "Включить Smart ядро",
"mihomo.enableSmartOverride": "Использовать автоматическое Smart переопределение правил",
"mihomo.smartOverrideTooltip": "Использовать встроенный скрипт умного переопределения в Party для замены url-test и load-balance на Smart группы правил; если указанные группы правил отсутствуют, извлечь все узлы из подписки и заменить правила по умолчанию, подходит для пользователей, которые не хотят заморачиваться, функция действует одним кликом; если используется глобальный режим, выберите узел с именем 'Smart Group'",
"mihomo.smartCoreUseLightGBM": "Использовать LightGBM",
"mihomo.smartCoreCollectData": "Собирать данные",
"mihomo.smartCoreStrategy": "Режим стратегии",
"mihomo.smartCoreStrategyStickySession": "Липкие сессии",
"mihomo.smartCoreStrategyRoundRobin": "Круговой опрос",
"mihomo.smartCoreUseLightGBMTooltip": "Использовать предварительно обученную универсальную модель для быстрого улучшения выбора узлов, но может не подходить для вашей специфической сетевой среды",
"mihomo.smartCoreCollectDataTooltip": "Собирать данные о вашем сетевом использовании для обучения пользовательских моделей, более подходящих для вашей сетевой среды (отключите, если не знаете, как обучать модели)",
"mihomo.mixedPort": "Смешанный порт",
"mihomo.confirm": "Подтвердить",
"mihomo.socksPort": "Порт Socks",

View File

@ -127,6 +127,8 @@
"mihomo.smartCoreStrategy": "策略模式",
"mihomo.smartCoreStrategyStickySession": "粘性会话",
"mihomo.smartCoreStrategyRoundRobin": "轮询",
"mihomo.smartCoreUseLightGBMTooltip": "使用预训练的通用模型,可快速提升节点选择效果,但可能不适合您的特定网络环境",
"mihomo.smartCoreCollectDataTooltip": "收集您的网络使用数据,可用于训练更适合您的网络环境的自定义模型(如果您不懂如何训练模型,请关闭)",
"mihomo.mixedPort": "混合端口",
"mihomo.confirm": "确认",
"mihomo.socksPort": "Socks 端口",

View File

@ -232,7 +232,18 @@ const Mihomo: React.FC = () => {
{enableSmartCore && core === 'mihomo-smart' && (
<>
<SettingItem
title={t('mihomo.smartCoreUseLightGBM')}
title={
<div className="flex items-center gap-2">
<span>{t('mihomo.smartCoreUseLightGBM')}</span>
<Tooltip
content={t('mihomo.smartCoreUseLightGBMTooltip')}
placement="top"
className="max-w-xs"
>
<IoMdInformationCircleOutline className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-help" />
</Tooltip>
</div>
}
divider
>
<Switch
@ -247,7 +258,18 @@ const Mihomo: React.FC = () => {
</SettingItem>
<SettingItem
title={t('mihomo.smartCoreCollectData')}
title={
<div className="flex items-center gap-2">
<span>{t('mihomo.smartCoreCollectData')}</span>
<Tooltip
content={t('mihomo.smartCoreCollectDataTooltip')}
placement="top"
className="max-w-xs"
>
<IoMdInformationCircleOutline className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-help" />
</Tooltip>
</div>
}
divider
>
<Switch

View File

@ -406,6 +406,10 @@ export async function updateTrayIcon(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateTrayIcon'))
}
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled)
}
export async function showMainWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow'))
}