From ba10dfd3df02ddde291c8812811a68d3b0b5f354 Mon Sep 17 00:00:00 2001
From: Tongyuxiu Zhou <3232825542@qq.com>
Date: Thu, 11 Dec 2025 07:15:30 +0800
Subject: [PATCH 1/3] fix: missing placeholder and error handling in override
page
---
src/renderer/src/locales/en-US.json | 2 ++
src/renderer/src/locales/fa-IR.json | 2 ++
src/renderer/src/locales/ru-RU.json | 2 ++
src/renderer/src/locales/zh-CN.json | 2 ++
src/renderer/src/locales/zh-TW.json | 2 ++
src/renderer/src/pages/override.tsx | 3 +++
6 files changed, 13 insertions(+)
diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json
index 7f7f46d..7c104de 100644
--- a/src/renderer/src/locales/en-US.json
+++ b/src/renderer/src/locales/en-US.json
@@ -498,10 +498,12 @@
"rules.title": "Rules",
"rules.filter": "Filter Rules",
"override.title": "Override",
+ "override.input.placeholder": "Enter override URL",
"override.import": "Import",
"override.docs": "Documentation",
"override.repository": "Override Repository",
"override.unsupportedFileType": "Unsupported file type",
+ "override.error.importFailed": "Import failed: {{error}}",
"override.actions.open": "Open",
"override.actions.newYaml": "New YAML",
"override.actions.newJs": "New JavaScript",
diff --git a/src/renderer/src/locales/fa-IR.json b/src/renderer/src/locales/fa-IR.json
index b8eedb0..e25d044 100644
--- a/src/renderer/src/locales/fa-IR.json
+++ b/src/renderer/src/locales/fa-IR.json
@@ -467,10 +467,12 @@
"rules.title": "قوانین",
"rules.filter": "فیلتر قوانین",
"override.title": "جایگزینی",
+ "override.input.placeholder": "وارد کردن URL جایگزین",
"override.import": "وارد کردن",
"override.docs": "مستندات",
"override.repository": "مخزن جایگزینی",
"override.unsupportedFileType": "نوع فایل پشتیبانی نمیشود",
+ "override.error.importFailed": "وارد کردن ناموفق بود: {{error}}",
"override.actions.open": "باز کردن",
"override.actions.newYaml": "YAML جدید",
"override.actions.newJs": "جاوااسکریپت جدید",
diff --git a/src/renderer/src/locales/ru-RU.json b/src/renderer/src/locales/ru-RU.json
index b8fd524..a6f9e40 100644
--- a/src/renderer/src/locales/ru-RU.json
+++ b/src/renderer/src/locales/ru-RU.json
@@ -467,10 +467,12 @@
"rules.title": "Правила",
"rules.filter": "Фильтр правил",
"override.title": "Переопределение",
+ "override.input.placeholder": "Введите URL переопределения",
"override.import": "Импорт",
"override.docs": "Документация",
"override.repository": "Репозиторий переопределений",
"override.unsupportedFileType": "Неподдерживаемый тип файла",
+ "override.error.importFailed": "Не удалось импортировать: {{error}}",
"override.actions.open": "Открыть",
"override.actions.newYaml": "Новый YAML",
"override.actions.newJs": "Новый JavaScript",
diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json
index 33748ba..37fb2d1 100644
--- a/src/renderer/src/locales/zh-CN.json
+++ b/src/renderer/src/locales/zh-CN.json
@@ -498,10 +498,12 @@
"rules.title": "分流规则",
"rules.filter": "筛选过滤",
"override.title": "覆写",
+ "override.input.placeholder": "输入覆写 URL",
"override.import": "导入",
"override.docs": "使用文档",
"override.repository": "常用覆写仓库",
"override.unsupportedFileType": "不支持的文件类型",
+ "override.error.importFailed": "导入失败:{{error}}",
"override.actions.open": "打开",
"override.actions.newYaml": "新建 YAML",
"override.actions.newJs": "新建 JavaScript",
diff --git a/src/renderer/src/locales/zh-TW.json b/src/renderer/src/locales/zh-TW.json
index 771509e..3c52ddf 100644
--- a/src/renderer/src/locales/zh-TW.json
+++ b/src/renderer/src/locales/zh-TW.json
@@ -498,10 +498,12 @@
"rules.title": "分流規則",
"rules.filter": "篩選過濾",
"override.title": "覆寫",
+ "override.input.placeholder": "輸入覆寫 URL",
"override.import": "匯入",
"override.docs": "使用文檔",
"override.repository": "常用覆寫倉庫",
"override.unsupportedFileType": "不支持的檔案類型",
+ "override.error.importFailed": "匯入失敗:{{error}}",
"override.actions.open": "打開",
"override.actions.newYaml": "新建 YAML",
"override.actions.newJs": "新建 JavaScript",
diff --git a/src/renderer/src/pages/override.tsx b/src/renderer/src/pages/override.tsx
index 6117b52..3fd8c43 100644
--- a/src/renderer/src/pages/override.tsx
+++ b/src/renderer/src/pages/override.tsx
@@ -55,6 +55,8 @@ const Override: React.FC = () => {
url,
ext: urlObj.pathname.endsWith('.js') ? 'js' : 'yaml'
})
+ } catch (e) {
+ toast.error(t('override.error.importFailed', { error: String(e) }))
} finally {
setImporting(false)
}
@@ -174,6 +176,7 @@ const Override: React.FC = () => {
Date: Thu, 11 Dec 2025 20:13:16 +0800
Subject: [PATCH 2/3] refactor: remove no-longer-used ipc
---
src/main/utils/ipc.ts | 6 ------
src/renderer/src/utils/ipc.ts | 4 ----
2 files changed, 10 deletions(-)
diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts
index bcdbf83..c3b0dad 100644
--- a/src/main/utils/ipc.ts
+++ b/src/main/utils/ipc.ts
@@ -324,12 +324,6 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('writeTheme', (_e, theme, css) => ipcErrorWrapper(writeTheme)(theme, css))
ipcMain.handle('applyTheme', (_e, theme) => ipcErrorWrapper(applyTheme)(theme))
ipcMain.handle('copyEnv', (_e, type) => ipcErrorWrapper(copyEnv)(type))
- ipcMain.handle('alert', (_e, msg) => {
- dialog.showErrorBox('Clash Party', msg)
- })
- ipcMain.handle('showDetailedError', (_e, title, message) => {
- dialog.showErrorBox(title, message)
- })
ipcMain.handle('getSmartOverrideContent', async () => {
const { getOverrideItem } = await import('../config')
try {
diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts
index c633307..7fb550c 100644
--- a/src/renderer/src/utils/ipc.ts
+++ b/src/renderer/src/utils/ipc.ts
@@ -108,10 +108,6 @@ export async function mihomoSmartFlushCache(configName?: string): Promise
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName))
}
-export async function showDetailedError(title: string, message: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showDetailedError', title, message))
-}
-
export async function getSmartOverrideContent(): Promise {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getSmartOverrideContent'))
}
From 6542be84905da7431f28e1444ae9aba63eb438d4 Mon Sep 17 00:00:00 2001
From: Memory <134070804+Memory2314@users.noreply.github.com>
Date: Sat, 13 Dec 2025 15:22:32 +0800
Subject: [PATCH 3/3] chore: ensure ESLint passes and format code & update
changelog.md
* chore: ensure ESLint passes and format code
* chore: update changelog.md
---
README.md | 1 +
changelog.md | 36 +-
scripts/copy-legacy-artifacts.mjs | 2 +-
scripts/telegram.mjs | 50 +-
scripts/update-version.mjs | 5 +-
scripts/updater.mjs | 10 +-
scripts/version-utils.mjs | 26 +-
src/main/config/controledMihomo.ts | 8 +-
src/main/config/smartOverride.ts | 7 +-
src/main/core/factory.ts | 51 +-
src/main/core/manager.ts | 67 +-
src/main/core/mihomoApi.ts | 11 +-
src/main/core/profileUpdater.ts | 12 +-
src/main/index.ts | 45 +-
src/main/resolve/autoUpdater.ts | 10 +-
src/main/resolve/backup.ts | 10 +-
src/main/resolve/floatingWindow.ts | 14 +-
src/main/resolve/gistApi.ts | 4 +-
src/main/resolve/shortcut.ts | 10 +-
src/main/resolve/tray.ts | 39 +-
src/main/sys/autoRun.ts | 3 +-
src/main/sys/misc.ts | 2 -
src/main/sys/sysproxy.ts | 33 +-
src/main/utils/chromeRequest.ts | 12 +-
src/main/utils/dirs.ts | 2 +-
src/main/utils/github.ts | 44 +-
src/main/utils/init.ts | 18 +-
src/main/utils/ipc.ts | 45 +-
src/main/utils/logger.ts | 7 +-
src/main/utils/yaml.ts | 2 +-
src/renderer/src/assets/main.css | 6 +-
.../src/components/base/base-editor.tsx | 2 +-
.../components/base/base-error-boundary.tsx | 6 +-
src/renderer/src/components/base/toast.tsx | 34 +-
.../connections/connection-detail-modal.tsx | 141 +-
.../connections/connection-item.tsx | 3 +-
.../connections/connection-table.tsx | 240 +--
.../src/components/mihomo/interface-modal.tsx | 2 +-
.../components/override/edit-file-modal.tsx | 5 +-
.../src/components/override/override-item.tsx | 14 +-
.../components/profiles/edit-info-modal.tsx | 48 +-
.../components/profiles/edit-rules-modal.tsx | 1533 ++++++++++-------
.../src/components/profiles/profile-item.tsx | 26 +-
.../src/components/proxies/proxy-item.tsx | 283 +--
.../components/resources/proxy-provider.tsx | 10 +-
.../components/resources/rule-provider.tsx | 18 +-
.../src/components/resources/viewer.tsx | 16 +-
.../src/components/settings/actions.tsx | 4 +-
.../components/settings/general-config.tsx | 88 +-
.../settings/local-backup-config.tsx | 14 +-
.../src/components/settings/mihomo-config.tsx | 9 +-
.../src/components/settings/webdav-config.tsx | 21 +-
.../settings/webdav-restore-modal.tsx | 81 +-
.../src/components/sider/conn-card.tsx | 7 +-
.../src/components/sider/dns-card.tsx | 6 +-
.../sider/outbound-mode-switcher.tsx | 18 +-
.../src/components/sider/profile-card.tsx | 10 +-
.../src/components/sider/sniff-card.tsx | 6 +-
.../src/components/sider/substore-card.tsx | 6 +-
.../components/sider/sysproxy-switcher.tsx | 6 +-
.../src/components/sider/tun-switcher.tsx | 16 +-
.../src/components/updater/updater-modal.tsx | 10 +-
src/renderer/src/i18n.ts | 2 +-
src/renderer/src/pages/connections.tsx | 99 +-
src/renderer/src/pages/logs.tsx | 7 +-
src/renderer/src/pages/mihomo.tsx | 254 +--
src/renderer/src/pages/profiles.tsx | 112 +-
src/renderer/src/pages/proxies.tsx | 495 +++---
src/renderer/src/pages/resources.tsx | 2 +-
src/renderer/src/pages/settings.tsx | 2 +-
src/renderer/src/utils/dayjs.ts | 2 +-
src/renderer/src/utils/ipc.ts | 28 +-
72 files changed, 2429 insertions(+), 1849 deletions(-)
diff --git a/README.md b/README.md
index 78e0428..36cc479 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@
### 本项目认证稳定机场推荐:“[狗狗加速](https://party.dginv.click/#/register?code=ARdo0mXx)”
+
##### [狗狗加速 —— 技术流机场 Doggygo VPN](https://party.dginv.click/#/register?code=ARdo0mXx)
- 高性能海外机场,稳定首选,海外团队,无跑路风险
diff --git a/changelog.md b/changelog.md
index b4a912c..956e0e4 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,7 @@
-## 1.8.9
+# 1.8.9
+
+## 新功能 (Feat)
-### 新功能 (Feat)
- 升级内核版本
- 可视化规则编辑
- 连接页面支持暂停
@@ -10,18 +11,21 @@
- 在菜单中显示当前代理
- 支持修改 数据收集文件大小
-### 修复 (Fix)
+## 修复 (Fix)
+
- 更安全的内核提权检查
- Tun 模式无法在 linux 中正常工作
- 配置导致的程序崩溃问题
-# 其他 (chore)
+### 其他 (chore)
+
- 添加缺失的多国语言翻译
- 更新依赖
-## 1.8.8
+# 1.8.8
+
+## 新功能 (Feat)
-### 新功能 (Feat)
- 升级内核版本
- 增加内核版本选择
- 记住日志页面的筛选关键字
@@ -30,23 +34,27 @@
- 支持修改点击任务栏的窗口触发行为
- 内核设置下增加 WebUI 快捷打开方式
-### 修复 (Fix)
+## 修复 (Fix)
+
- MacOS 首次启动时的 ENOENT: no such file or directory(config.yaml)
- 自动更新获取老的文件名称
- 修复 mihomo.yaml 文件缺失的问题
- Smart 配置文件验证出错的问题
- 开发环境的 electron 问题
-### 优化 (Optimize)
+## 优化 (Optimize)
+
- 加快以管理员模式重启速度
- 优化仅用户滚动滚轮时触发自动滚动
- 改进俄语翻译
- 使用重载替换不必要的重启
-# 其他 (chore)
- - 更新依赖
+## 样式调整 (Sytle)
-### 样式调整 (Sytle)
- - 改进 logo 设计
- - 卡片尺寸
- - 设置页可展开项增加指示图标
\ No newline at end of file
+- 改进 logo 设计
+- 卡片尺寸
+- 设置页可展开项增加指示图标
+
+### 其他 (chore)
+
+- 更新依赖
diff --git a/scripts/copy-legacy-artifacts.mjs b/scripts/copy-legacy-artifacts.mjs
index 6416f00..13da728 100644
--- a/scripts/copy-legacy-artifacts.mjs
+++ b/scripts/copy-legacy-artifacts.mjs
@@ -52,7 +52,7 @@ if (copiedCount > 0) {
console.log('📋 现在 dist 目录包含以下文件:')
const finalFiles = readdirSync(distDir).sort()
- finalFiles.forEach(file => {
+ finalFiles.forEach((file) => {
if (file.includes('clash-party') || file.includes('mihomo-party')) {
const isLegacy = file.includes('mihomo-party')
console.log(` ${isLegacy ? '🔄' : '📦'} ${file}`)
diff --git a/scripts/telegram.mjs b/scripts/telegram.mjs
index 79fac47..6c884e9 100644
--- a/scripts/telegram.mjs
+++ b/scripts/telegram.mjs
@@ -1,6 +1,12 @@
import axios from 'axios'
import { readFileSync } from 'fs'
-import { getProcessedVersion, isDevBuild, getDownloadUrl, generateDownloadLinksMarkdown, getGitCommitHash } from './version-utils.mjs'
+import {
+ getProcessedVersion,
+ isDevBuild,
+ getDownloadUrl,
+ generateDownloadLinksMarkdown,
+ getGitCommitHash
+} from './version-utils.mjs'
const chat_id = '@MihomoPartyChannel'
const pkg = readFileSync('package.json', 'utf-8')
@@ -14,41 +20,35 @@ const isDevRelease = releaseType === 'dev' || isDevBuild()
function convertMarkdownToTelegramHTML(content) {
return content
- .split("\n")
+ .split('\n')
.map((line) => {
if (line.trim().length === 0) {
- return "";
- } else if (line.startsWith("## ")) {
- return `${line.replace("## ", "")}`;
- } else if (line.startsWith("### ")) {
- return `${line.replace("### ", "")}`;
- } else if (line.startsWith("#### ")) {
- return `${line.replace("#### ", "")}`;
+ return ''
+ } else if (line.startsWith('## ')) {
+ return `${line.replace('## ', '')}`
+ } else if (line.startsWith('### ')) {
+ return `${line.replace('### ', '')}`
+ } else if (line.startsWith('#### ')) {
+ return `${line.replace('#### ', '')}`
} else {
- let processedLine = line.replace(
- /\[([^\]]+)\]\(([^)]+)\)/g,
- (match, text, url) => {
- const encodedUrl = encodeURI(url);
- return `${text}`;
- },
- );
- processedLine = processedLine.replace(
- /\*\*([^*]+)\*\*/g,
- "$1",
- );
- return processedLine;
+ let processedLine = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
+ const encodedUrl = encodeURI(url)
+ return `${text}`
+ })
+ processedLine = processedLine.replace(/\*\*([^*]+)\*\*/g, '$1')
+ return processedLine
}
})
- .join("\n");
+ .join('\n')
}
-let content = '';
+let content = ''
if (isDevRelease) {
// 版本号中提取commit hash
const shortCommitSha = getGitCommitHash(true)
const commitSha = getGitCommitHash(false)
-
+
content = `🚧 Clash Party Dev Build 开发版本发布\n\n`
content += `基于版本: ${version}\n`
content += `提交哈希: ${shortCommitSha}\n\n`
@@ -78,4 +78,4 @@ await axios.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/
parse_mode: 'HTML'
})
-console.log(`${isDevRelease ? '开发版本' : '正式版本'}Telegram 通知发送成功`)
\ No newline at end of file
+console.log(`${isDevRelease ? '开发版本' : '正式版本'}Telegram 通知发送成功`)
diff --git a/scripts/update-version.mjs b/scripts/update-version.mjs
index 9f99e0b..fcfe20e 100644
--- a/scripts/update-version.mjs
+++ b/scripts/update-version.mjs
@@ -10,7 +10,7 @@ function updatePackageVersion() {
// 获取处理后的版本号
const newVersion = getProcessedVersion()
-
+
console.log(`当前版本: ${packageData.version}`)
console.log(`${isDevBuild() ? 'Dev构建' : '正式构建'} - 新版本: ${newVersion}`)
@@ -20,7 +20,6 @@ function updatePackageVersion() {
writeFileSync(packagePath, JSON.stringify(packageData, null, 2) + '\n')
console.log(`✅ package.json版本号已更新为: ${newVersion}`)
-
} catch (error) {
console.error('❌ 更新package.json版本号失败:', error.message)
process.exit(1)
@@ -29,4 +28,4 @@ function updatePackageVersion() {
updatePackageVersion()
-export { updatePackageVersion }
\ No newline at end of file
+export { updatePackageVersion }
diff --git a/scripts/updater.mjs b/scripts/updater.mjs
index de9e455..8c12d76 100644
--- a/scripts/updater.mjs
+++ b/scripts/updater.mjs
@@ -1,6 +1,11 @@
import yaml from 'yaml'
import { readFileSync, writeFileSync } from 'fs'
-import { getProcessedVersion, isDevBuild, getDownloadUrl, generateDownloadLinksMarkdown } from './version-utils.mjs'
+import {
+ getProcessedVersion,
+ isDevBuild,
+ getDownloadUrl,
+ generateDownloadLinksMarkdown
+} from './version-utils.mjs'
const pkg = readFileSync('package.json', 'utf-8')
let changelog = readFileSync('changelog.md', 'utf-8')
@@ -19,7 +24,8 @@ const latest = {
// 使用统一的下载链接生成函数
changelog += generateDownloadLinksMarkdown(downloadUrl, version)
-changelog += '\n\n### 机场推荐:\n- 高性能海外机场,稳定首选:[https://狗狗加速.com](https://party.dginv.click/#/register?code=ARdo0mXx)'
+changelog +=
+ '\n\n### 机场推荐:\n- 高性能海外机场,稳定首选:[https://狗狗加速.com](https://party.dginv.click/#/register?code=ARdo0mXx)'
writeFileSync('latest.yml', yaml.stringify(latest))
writeFileSync('changelog.md', changelog)
diff --git a/scripts/version-utils.mjs b/scripts/version-utils.mjs
index 8080a04..15fda79 100644
--- a/scripts/version-utils.mjs
+++ b/scripts/version-utils.mjs
@@ -1,7 +1,7 @@
import { execSync } from 'child_process'
import { readFileSync } from 'fs'
- // 获取Git commit hash
+// 获取Git commit hash
export function getGitCommitHash(short = true) {
try {
const command = short ? 'git rev-parse --short HEAD' : 'git rev-parse HEAD'
@@ -12,7 +12,7 @@ export function getGitCommitHash(short = true) {
}
}
- // 获取当前月份日期
+// 获取当前月份日期
export function getCurrentMonthDate() {
const now = new Date()
const month = String(now.getMonth() + 1).padStart(2, '0')
@@ -20,7 +20,7 @@ export function getCurrentMonthDate() {
return `${month}${day}`
}
- // 从package.json读取基础版本号
+// 从package.json读取基础版本号
export function getBaseVersion() {
try {
const pkg = readFileSync('package.json', 'utf-8')
@@ -33,7 +33,7 @@ export function getBaseVersion() {
}
}
- // 生成dev版本号
+// 生成dev版本号
export function getDevVersion() {
const baseVersion = getBaseVersion()
const monthDate = getCurrentMonthDate()
@@ -42,14 +42,16 @@ export function getDevVersion() {
return `${baseVersion}-d${monthDate}.${commitHash}`
}
- // 检查当前环境是否为dev构建
+// 检查当前环境是否为dev构建
export function isDevBuild() {
- return process.env.NODE_ENV === 'development' ||
- process.argv.includes('--dev') ||
- process.env.GITHUB_EVENT_NAME === 'workflow_dispatch'
+ return (
+ process.env.NODE_ENV === 'development' ||
+ process.argv.includes('--dev') ||
+ process.env.GITHUB_EVENT_NAME === 'workflow_dispatch'
+ )
}
- // 获取处理后的版本号
+// 获取处理后的版本号
export function getProcessedVersion() {
if (isDevBuild()) {
return getDevVersion()
@@ -58,7 +60,7 @@ export function getProcessedVersion() {
}
}
- // 生成下载URL
+// 生成下载URL
export function getDownloadUrl(isDev, version) {
if (isDev) {
return 'https://github.com/mihomo-party-org/clash-party/releases/download/dev'
@@ -81,6 +83,6 @@ export function generateDownloadLinksMarkdown(downloadUrl, version) {
links += '\n#### Linux:\n\n'
links += `- DEB:[64位](${downloadUrl}/clash-party-linux-${version}-amd64.deb) | [ARM64](${downloadUrl}/clash-party-linux-${version}-arm64.deb)\n\n`
links += `- RPM:[64位](${downloadUrl}/clash-party-linux-${version}-x86_64.rpm) | [ARM64](${downloadUrl}/clash-party-linux-${version}-aarch64.rpm)`
-
+
return links
-}
\ No newline at end of file
+}
diff --git a/src/main/config/controledMihomo.ts b/src/main/config/controledMihomo.ts
index 0f162aa..0eb2c94 100644
--- a/src/main/config/controledMihomo.ts
+++ b/src/main/config/controledMihomo.ts
@@ -17,12 +17,16 @@ export async function getControledMihomoConfig(force = false): Promise {
+
+ ruleStrings.forEach((ruleStr) => {
const parts = ruleStr.split(',')
- const firstPartIsNumber = !isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
-
+ const firstPartIsNumber =
+ !isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
+
if (firstPartIsNumber) {
const offset = parseInt(parts[0])
const rule = parts.slice(1).join(',')
-
+
if (isAppend) {
// 后置规则的插入位置计算
const insertPosition = Math.max(0, rules.length - Math.min(offset, rules.length))
@@ -51,14 +52,19 @@ function processRulesWithOffset(ruleStrings: string[], currentRules: string[], i
normalRules.push(ruleStr)
}
})
-
+
return { normalRules, insertRules: rules }
}
export async function generateProfile(): Promise {
// 读取最新的配置
const { current } = await getProfileConfig(true)
- const { diffWorkDir = false, controlDns = true, controlSniff = true, useNameserverPolicy } = await getAppConfig()
+ const {
+ diffWorkDir = false,
+ controlDns = true,
+ controlSniff = true,
+ useNameserverPolicy
+ } = await getAppConfig()
let currentProfile = await overrideProfile(current, await getProfile(current))
let controledMihomoConfig = await getControledMihomoConfig()
@@ -80,37 +86,48 @@ export async function generateProfile(): Promise {
const ruleFilePath = rulePath(current || 'default')
if (existsSync(ruleFilePath)) {
const ruleFileContent = await readFile(ruleFilePath, 'utf-8')
- const ruleData = parse(ruleFileContent) as { prepend?: string[], append?: string[], delete?: string[] } | null
-
+ const ruleData = parse(ruleFileContent) as {
+ prepend?: string[]
+ append?: string[]
+ delete?: string[]
+ } | null
+
if (ruleData && typeof ruleData === 'object') {
// 确保 rules 数组存在
if (!currentProfile.rules) {
currentProfile.rules = [] as unknown as []
}
-
+
let rules = [...currentProfile.rules] as unknown as string[]
-
+
// 处理前置规则
if (ruleData.prepend?.length) {
- const { normalRules: prependRules, insertRules } = processRulesWithOffset(ruleData.prepend, rules)
+ const { normalRules: prependRules, insertRules } = processRulesWithOffset(
+ ruleData.prepend,
+ rules
+ )
rules = [...prependRules, ...insertRules]
}
-
+
// 处理后置规则
if (ruleData.append?.length) {
- const { normalRules: appendRules, insertRules } = processRulesWithOffset(ruleData.append, rules, true)
+ const { normalRules: appendRules, insertRules } = processRulesWithOffset(
+ ruleData.append,
+ rules,
+ true
+ )
rules = [...insertRules, ...appendRules]
}
-
+
// 处理删除规则
if (ruleData.delete?.length) {
const deleteSet = new Set(ruleData.delete)
- rules = rules.filter(rule => {
+ rules = rules.filter((rule) => {
const ruleStr = Array.isArray(rule) ? rule.join(',') : rule
return !deleteSet.has(ruleStr)
})
}
-
+
currentProfile.rules = rules as unknown as []
}
}
diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts
index d076930..d978554 100644
--- a/src/main/core/manager.ts
+++ b/src/main/core/manager.ts
@@ -46,15 +46,14 @@ import { managerLogger } from '../utils/logger'
// 内核名称白名单
const ALLOWED_CORES = ['mihomo', 'mihomo-alpha', 'mihomo-smart'] as const
-type AllowedCore = typeof ALLOWED_CORES[number]
+type AllowedCore = (typeof ALLOWED_CORES)[number]
function isValidCoreName(core: string): core is AllowedCore {
return ALLOWED_CORES.includes(core as AllowedCore)
}
- // 路径检查
+// 路径检查
function validateCorePath(corePath: string): void {
-
if (corePath.includes('..')) {
throw new Error('Invalid core path: directory traversal detected')
}
@@ -176,7 +175,9 @@ export async function startCore(detached = false): Promise[]> {
os.setPriority(child.pid, os.constants.priority[mihomoCpuPriority])
}
if (detached) {
- await managerLogger.info(`Core process detached successfully on ${process.platform}, PID: ${child.pid}`)
+ await managerLogger.info(
+ `Core process detached successfully on ${process.platform}, PID: ${child.pid}`
+ )
child.unref()
return new Promise((resolve) => {
resolve([new Promise(() => {})])
@@ -210,7 +211,8 @@ export async function startCore(detached = false): Promise[]> {
reject(i18next.t('tun.error.tunPermissionDenied'))
}
- if ((process.platform !== 'win32' && str.includes('External controller unix listen error')) ||
+ if (
+ (process.platform !== 'win32' && str.includes('External controller unix listen error')) ||
(process.platform === 'win32' && str.includes('External controller pipe listen error'))
) {
await managerLogger.error('External controller listen error detected:', str)
@@ -219,7 +221,7 @@ export async function startCore(detached = false): Promise[]> {
await managerLogger.info('Attempting Windows pipe cleanup and retry...')
try {
await cleanupWindowsNamedPipes()
- await new Promise(resolve => setTimeout(resolve, 2000))
+ await new Promise((resolve) => setTimeout(resolve, 2000))
} catch (cleanupError) {
await managerLogger.error('Pipe cleanup failed:', cleanupError)
}
@@ -235,7 +237,9 @@ export async function startCore(detached = false): Promise[]> {
resolve([
new Promise((resolve) => {
child.stdout?.on('data', async (data) => {
- if (data.toString().toLowerCase().includes('start initial compatible provider default')) {
+ if (
+ data.toString().toLowerCase().includes('start initial compatible provider default')
+ ) {
try {
mainWindow?.webContents.send('groupsUpdated')
mainWindow?.webContents.send('rulesUpdated')
@@ -337,7 +341,7 @@ async function cleanupWindowsNamedPipes(): Promise {
await managerLogger.warn('Failed to parse process list JSON:', parseError)
// 回退到文本解析
- const lines = stdout.split('\n').filter(line => line.includes('mihomo'))
+ const lines = stdout.split('\n').filter((line) => line.includes('mihomo'))
for (const line of lines) {
const match = line.match(/(\d+)/)
if (match) {
@@ -361,8 +365,7 @@ async function cleanupWindowsNamedPipes(): Promise {
await managerLogger.warn('Failed to check mihomo processes:', error)
}
- await new Promise(resolve => setTimeout(resolve, 1000))
-
+ await new Promise((resolve) => setTimeout(resolve, 1000))
} catch (error) {
await managerLogger.error('Windows named pipe cleanup failed:', error)
}
@@ -451,10 +454,7 @@ export async function quitWithoutCore(): Promise {
}
async function checkProfile(): Promise {
- const {
- core = 'mihomo',
- diffWorkDir = false
- } = await getAppConfig()
+ const { core = 'mihomo', diffWorkDir = false } = await getAppConfig()
const { current } = await getProfileConfig()
const corePath = mihomoCorePath(core)
const execFilePromise = promisify(execFile)
@@ -484,13 +484,15 @@ async function checkProfile(): Promise {
}
return line.trim()
})
- .filter(line => line.length > 0)
+ .filter((line) => line.length > 0)
if (errorLines.length === 0) {
- const allLines = stdout.split('\n').filter(line => line.trim().length > 0)
+ const allLines = stdout.split('\n').filter((line) => line.trim().length > 0)
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${allLines.join('\n')}`)
} else {
- throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`)
+ throw new Error(
+ `${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`
+ )
}
} else {
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}: ${error}`)
@@ -570,7 +572,7 @@ async function waitForCoreReady(): Promise {
return
}
- await new Promise(resolve => setTimeout(resolve, retryInterval))
+ await new Promise((resolve) => setTimeout(resolve, retryInterval))
}
}
}
@@ -598,7 +600,9 @@ export async function checkAdminPrivileges(): Promise {
return true
} catch (netSessionError: any) {
const netErrorCode = netSessionError?.code || 0
- await managerLogger.debug(`Both fltmc and net session failed, no admin privileges. Error codes: fltmc=${errorCode}, net=${netErrorCode}`)
+ await managerLogger.debug(
+ `Both fltmc and net session failed, no admin privileges. Error codes: fltmc=${errorCode}, net=${netErrorCode}`
+ )
return false
}
}
@@ -613,11 +617,14 @@ export async function showTunPermissionDialog(): Promise {
await managerLogger.info(`i18next available: ${typeof i18next.t === 'function'}`)
const title = i18next.t('tun.permissions.title') || '需要管理员权限'
- const message = i18next.t('tun.permissions.message') || '启用TUN模式需要管理员权限,是否现在重启应用获取权限?'
+ const message =
+ i18next.t('tun.permissions.message') || '启用TUN模式需要管理员权限,是否现在重启应用获取权限?'
const confirmText = i18next.t('common.confirm') || '确认'
const cancelText = i18next.t('common.cancel') || '取消'
- await managerLogger.info(`Dialog texts - Title: "${title}", Message: "${message}", Confirm: "${confirmText}", Cancel: "${cancelText}"`)
+ await managerLogger.info(
+ `Dialog texts - Title: "${title}", Message: "${message}", Confirm: "${confirmText}", Cancel: "${cancelText}"`
+ )
const choice = dialog.showMessageBoxSync({
type: 'warning',
@@ -759,8 +766,11 @@ async function checkHighPrivilegeMihomoProcess(): Promise {
for (const executable of mihomoExecutables) {
try {
- const { stdout } = await execPromise(`chcp 65001 >nul 2>&1 && tasklist /FI "IMAGENAME eq ${executable}" /FO CSV`, { encoding: 'utf8' })
- const lines = stdout.split('\n').filter(line => line.includes(executable))
+ const { stdout } = await execPromise(
+ `chcp 65001 >nul 2>&1 && tasklist /FI "IMAGENAME eq ${executable}" /FO CSV`,
+ { encoding: 'utf8' }
+ )
+ const lines = stdout.split('\n').filter((line) => line.includes(executable))
if (lines.length > 0) {
await managerLogger.info(`Found ${lines.length} ${executable} processes running`)
@@ -781,7 +791,9 @@ async function checkHighPrivilegeMihomoProcess(): Promise {
return true
}
} catch (error) {
- await managerLogger.info(`Cannot get info for process ${pid}, might be high privilege`)
+ await managerLogger.info(
+ `Cannot get info for process ${pid}, might be high privilege`
+ )
}
}
}
@@ -802,7 +814,9 @@ async function checkHighPrivilegeMihomoProcess(): Promise {
for (const executable of mihomoExecutables) {
try {
const { stdout } = await execPromise(`ps aux | grep ${executable} | grep -v grep`)
- const lines = stdout.split('\n').filter(line => line.trim() && line.includes(executable))
+ const lines = stdout
+ .split('\n')
+ .filter((line) => line.trim() && line.includes(executable))
if (lines.length > 0) {
foundProcesses = true
@@ -820,8 +834,7 @@ async function checkHighPrivilegeMihomoProcess(): Promise {
}
}
}
- } catch (error) {
- }
+ } catch (error) {}
}
if (!foundProcesses) {
diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts
index 6cad3df..fbc2838 100644
--- a/src/main/core/mihomoApi.ts
+++ b/src/main/core/mihomoApi.ts
@@ -190,12 +190,12 @@ export const mihomoUpgradeUI = async (): Promise => {
export const mihomoUpgradeConfig = async (): Promise => {
console.log('[mihomoApi] mihomoUpgradeConfig called')
-
+
try {
const instance = await getAxios()
console.log('[mihomoApi] axios instance obtained')
const { diffWorkDir = false } = await getAppConfig()
- const { current } = await import('../config').then(mod => mod.getProfileConfig(true))
+ const { current } = await import('../config').then((mod) => mod.getProfileConfig(true))
const { mihomoWorkConfigPath } = await import('../utils/dirs')
const configPath = diffWorkDir ? mihomoWorkConfigPath(current) : mihomoWorkConfigPath('work')
console.log('[mihomoApi] config path:', configPath)
@@ -441,7 +441,10 @@ export const TunStatus = async (): Promise => {
return config?.tun?.enable === true
}
-export function calculateTrayIconStatus(sysProxyEnabled: boolean, tunEnabled: boolean): 'white' | 'blue' | 'green' | 'red' {
+export function calculateTrayIconStatus(
+ sysProxyEnabled: boolean,
+ tunEnabled: boolean
+): 'white' | 'blue' | 'green' | 'red' {
if (sysProxyEnabled && tunEnabled) {
return 'red' // 系统代理 + TUN 同时启用(警告状态)
} else if (sysProxyEnabled) {
@@ -456,4 +459,4 @@ export function calculateTrayIconStatus(sysProxyEnabled: boolean, tunEnabled: bo
export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> {
const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()])
return calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
-}
\ No newline at end of file
+}
diff --git a/src/main/core/profileUpdater.ts b/src/main/core/profileUpdater.ts
index de399ec..e0d0242 100644
--- a/src/main/core/profileUpdater.ts
+++ b/src/main/core/profileUpdater.ts
@@ -6,7 +6,7 @@ const intervalPool: Record = {}
export async function initProfileUpdater(): Promise {
const { items, current } = await getProfileConfig()
const currentItem = await getCurrentProfileItem()
-
+
for (const item of items.filter((i) => i.id !== current)) {
if (item.type === 'remote' && item.autoUpdate && item.interval) {
if (typeof item.interval === 'number') {
@@ -31,7 +31,7 @@ export async function initProfileUpdater(): Promise {
}
})
}
-
+
try {
await addProfileItem(item)
} catch (e) {
@@ -52,7 +52,7 @@ export async function initProfileUpdater(): Promise {
},
currentItem.interval * 60 * 1000
)
-
+
setTimeout(
async () => {
try {
@@ -85,7 +85,7 @@ export async function addProfileUpdater(item: IProfileItem): Promise {
if (item.type === 'remote' && item.autoUpdate && item.interval) {
if (intervalPool[item.id]) {
if (intervalPool[item.id] instanceof Cron) {
- (intervalPool[item.id] as Cron).stop()
+ ;(intervalPool[item.id] as Cron).stop()
} else {
clearInterval(intervalPool[item.id] as NodeJS.Timeout)
}
@@ -117,10 +117,10 @@ export async function addProfileUpdater(item: IProfileItem): Promise {
export async function removeProfileUpdater(id: string): Promise {
if (intervalPool[id]) {
if (intervalPool[id] instanceof Cron) {
- (intervalPool[id] as Cron).stop()
+ ;(intervalPool[id] as Cron).stop()
} else {
clearInterval(intervalPool[id] as NodeJS.Timeout)
}
delete intervalPool[id]
}
-}
\ No newline at end of file
+}
diff --git a/src/main/index.ts b/src/main/index.ts
index 4ba2fb3..e7559ed 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -3,7 +3,15 @@ import { registerIpcMainHandlers } from './utils/ipc'
import windowStateKeeper from 'electron-window-state'
import { app, shell, BrowserWindow, Menu, dialog, Notification, powerMonitor } from 'electron'
import { addProfileItem, getAppConfig, patchAppConfig } from './config'
-import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun, checkHighPrivilegeCore, restartAsAdmin, initAdminStatus } from './core/manager'
+import {
+ quitWithoutCore,
+ startCore,
+ stopCore,
+ checkAdminRestartForTun,
+ checkHighPrivilegeCore,
+ restartAsAdmin,
+ initAdminStatus
+} from './core/manager'
import { triggerSysProxy } from './sys/sysproxy'
import icon from '../../resources/icon.png?asset'
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
@@ -23,7 +31,6 @@ import i18next from 'i18next'
import { logger } from './utils/logger'
import { initWebdavBackupScheduler } from './resolve/backup'
-
async function fixUserDataPermissions(): Promise {
if (process.platform !== 'darwin') return
@@ -60,11 +67,10 @@ async function initApp(): Promise {
await fixUserDataPermissions()
}
-initApp()
- .catch((e) => {
- safeShowErrorBox('common.error.initFailed', `${e}`)
- app.quit()
- })
+initApp().catch((e) => {
+ safeShowErrorBox('common.error.initFailed', `${e}`)
+ app.quit()
+})
export function customRelaunch(): void {
const script = `while kill -0 ${process.pid} 2>/dev/null; do
@@ -111,7 +117,8 @@ async function checkHighPrivilegeCoreEarly(): Promise {
if (hasHighPrivilegeCore) {
try {
const appConfig = await getAppConfig()
- const language = appConfig.language || (app.getLocale().startsWith('zh') ? 'zh-CN' : 'en-US')
+ const language =
+ appConfig.language || (app.getLocale().startsWith('zh') ? 'zh-CN' : 'en-US')
await initI18n({ lng: language })
} catch {
await initI18n({ lng: 'zh-CN' })
@@ -223,8 +230,8 @@ app.whenReady().then(async () => {
try {
const [startPromise] = await startCore()
startPromise.then(async () => {
- await initProfileUpdater()
- await initWebdavBackupScheduler() // 初始化WebDAV定时备份任务
+ await initProfileUpdater()
+ await initWebdavBackupScheduler() // 初始化WebDAV定时备份任务
// 上次是否为了开启 TUN 而重启
await checkAdminRestartForTun()
})
@@ -412,18 +419,18 @@ export async function createWindow(): Promise {
export function triggerMainWindow(force?: boolean): void {
if (mainWindow) {
getAppConfig()
- .then(({ triggerMainWindowBehavior = 'toggle' }) => {
- if (force === true || triggerMainWindowBehavior === 'toggle') {
- if (mainWindow?.isVisible()) {
- closeMainWindow()
+ .then(({ triggerMainWindowBehavior = 'toggle' }) => {
+ if (force === true || triggerMainWindowBehavior === 'toggle') {
+ if (mainWindow?.isVisible()) {
+ closeMainWindow()
+ } else {
+ showMainWindow()
+ }
} else {
showMainWindow()
}
- } else {
- showMainWindow()
- }
- })
- .catch(showMainWindow)
+ })
+ .catch(showMainWindow)
}
}
diff --git a/src/main/resolve/autoUpdater.ts b/src/main/resolve/autoUpdater.ts
index 4373383..4d9d9d1 100644
--- a/src/main/resolve/autoUpdater.ts
+++ b/src/main/resolve/autoUpdater.ts
@@ -131,9 +131,13 @@ export async function downloadAndInstallUpdate(version: string): Promise {
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}`)
+ 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}`
+ )
}
}
}
diff --git a/src/main/resolve/backup.ts b/src/main/resolve/backup.ts
index 639b484..c01502e 100644
--- a/src/main/resolve/backup.ts
+++ b/src/main/resolve/backup.ts
@@ -138,7 +138,7 @@ export async function initWebdavBackupScheduler(): Promise {
}
const { webdavBackupCron } = await getAppConfig()
-
+
// 如果配置了Cron表达式,则启动定时任务
if (webdavBackupCron) {
backupCronJob = new Cron(webdavBackupCron, async () => {
@@ -149,7 +149,7 @@ export async function initWebdavBackupScheduler(): Promise {
await systemLogger.error('Failed to execute WebDAV backup via cron job', error)
}
})
-
+
await systemLogger.info(`WebDAV backup scheduler initialized with cron: ${webdavBackupCron}`)
await systemLogger.info(`WebDAV backup scheduler nextRun: ${backupCronJob.nextRun()}`)
} else {
@@ -214,7 +214,7 @@ export async function exportLocalBackup(): Promise {
if (existsSync(rulesDir())) {
zip.addLocalFolder(rulesDir(), 'rules')
}
-
+
const date = new Date()
const zipFileName = `clash-party-backup-${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
const result = await dialog.showSaveDialog({
@@ -225,7 +225,7 @@ export async function exportLocalBackup(): Promise {
{ name: 'All Files', extensions: ['*'] }
]
})
-
+
if (!result.canceled && result.filePath) {
zip.writeZip(result.filePath)
await systemLogger.info(`Local backup exported to: ${result.filePath}`)
@@ -246,7 +246,7 @@ export async function importLocalBackup(): Promise {
],
properties: ['openFile']
})
-
+
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0]
const zip = new AdmZip(filePath)
diff --git a/src/main/resolve/floatingWindow.ts b/src/main/resolve/floatingWindow.ts
index e3e4876..3c703ea 100644
--- a/src/main/resolve/floatingWindow.ts
+++ b/src/main/resolve/floatingWindow.ts
@@ -19,9 +19,8 @@ async function createFloatingWindow(): Promise {
const { customTheme = 'default.css', floatingWindowCompatMode = true } = await getAppConfig()
const safeMode = process.env.FLOATING_SAFE_MODE === 'true'
- const useCompatMode = floatingWindowCompatMode ||
- process.env.FLOATING_COMPAT_MODE === 'true' ||
- safeMode
+ const useCompatMode =
+ floatingWindowCompatMode || process.env.FLOATING_COMPAT_MODE === 'true' || safeMode
const windowOptions: Electron.BrowserWindowConstructorOptions = {
width: 120,
@@ -38,7 +37,7 @@ async function createFloatingWindow(): Promise {
maximizable: safeMode,
fullscreenable: false,
closable: safeMode,
- backgroundColor: safeMode ? '#ffffff' : (useCompatMode ? '#f0f0f0' : '#00000000'),
+ backgroundColor: safeMode ? '#ffffff' : useCompatMode ? '#f0f0f0' : '#00000000',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
spellcheck: false,
@@ -81,9 +80,10 @@ async function createFloatingWindow(): Promise {
})
// 加载页面
- const url = is.dev && process.env['ELECTRON_RENDERER_URL']
- ? `${process.env['ELECTRON_RENDERER_URL']}/floating.html`
- : join(__dirname, '../renderer/floating.html')
+ const url =
+ is.dev && process.env['ELECTRON_RENDERER_URL']
+ ? `${process.env['ELECTRON_RENDERER_URL']}/floating.html`
+ : join(__dirname, '../renderer/floating.html')
is.dev ? await floatingWindow.loadURL(url) : await floatingWindow.loadFile(url)
} catch (error) {
diff --git a/src/main/resolve/gistApi.ts b/src/main/resolve/gistApi.ts
index a803827..a144e60 100644
--- a/src/main/resolve/gistApi.ts
+++ b/src/main/resolve/gistApi.ts
@@ -83,9 +83,7 @@ export async function getGistUrl(): Promise {
} else {
await uploadRuntimeConfig()
const gists = await listGists(githubToken)
- const gist = gists.find(
- (gist) => gist.description === 'Auto Synced Clash Party Runtime Config'
- )
+ const gist = gists.find((gist) => gist.description === 'Auto Synced Clash Party Runtime Config')
if (!gist) throw new Error('Gist not found')
return gist.html_url
}
diff --git a/src/main/resolve/shortcut.ts b/src/main/resolve/shortcut.ts
index 7746d8d..beb3aa5 100644
--- a/src/main/resolve/shortcut.ts
+++ b/src/main/resolve/shortcut.ts
@@ -44,7 +44,11 @@ export async function registerShortcut(
await triggerSysProxy(!enable)
await patchAppConfig({ sysProxy: { enable: !enable } })
new Notification({
- title: i18next.t(!enable ? 'common.notification.systemProxyEnabled' : 'common.notification.systemProxyDisabled')
+ title: i18next.t(
+ !enable
+ ? 'common.notification.systemProxyEnabled'
+ : 'common.notification.systemProxyDisabled'
+ )
}).show()
mainWindow?.webContents.send('appConfigUpdated')
floatingWindow?.webContents.send('appConfigUpdated')
@@ -68,7 +72,9 @@ export async function registerShortcut(
}
await restartCore()
new Notification({
- title: i18next.t(!enable ? 'common.notification.tunEnabled' : 'common.notification.tunDisabled')
+ title: i18next.t(
+ !enable ? 'common.notification.tunEnabled' : 'common.notification.tunDisabled'
+ )
}).show()
mainWindow?.webContents.send('controledMihomoConfigUpdated')
floatingWindow?.webContents.send('appConfigUpdated')
diff --git a/src/main/resolve/tray.ts b/src/main/resolve/tray.ts
index a35f4d5..740ba9a 100644
--- a/src/main/resolve/tray.ts
+++ b/src/main/resolve/tray.ts
@@ -15,12 +15,25 @@ 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, calculateTrayIconStatus } 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'
import { triggerSysProxy } from '../sys/sysproxy'
-import { quitWithoutCore, restartCore, checkMihomoCorePermissions, requestTunPermissions, restartAsAdmin } from '../core/manager'
+import {
+ quitWithoutCore,
+ restartCore,
+ checkMihomoCorePermissions,
+ requestTunPermissions,
+ restartAsAdmin
+} from '../core/manager'
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
import { t } from 'i18next'
import { trayLogger } from '../utils/logger'
@@ -30,8 +43,14 @@ export let tray: Tray | null = null
export const buildContextMenu = async (): Promise