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 => { // 添加调试日志 await trayLogger.debug('Current translation for tray.showWindow', t('tray.showWindow')) - await trayLogger.debug('Current translation for tray.hideFloatingWindow', t('tray.hideFloatingWindow')) - await trayLogger.debug('Current translation for tray.showFloatingWindow', t('tray.showFloatingWindow')) + await trayLogger.debug( + 'Current translation for tray.hideFloatingWindow', + t('tray.hideFloatingWindow') + ) + await trayLogger.debug( + 'Current translation for tray.showFloatingWindow', + t('tray.showFloatingWindow') + ) const { mode, tun } = await getControledMihomoConfig() const { @@ -55,8 +74,8 @@ export const buildContextMenu = async (): Promise => { try { const groups = await mihomoGroups() groupsMenu = groups.map((group) => { - const groupLabel = showCurrentProxyInTray ? `${group.name} | ${group.now}` : group.name; - + const groupLabel = showCurrentProxyInTray ? `${group.name} | ${group.now}` : group.name + return { id: group.name, label: groupLabel, @@ -106,7 +125,9 @@ export const buildContextMenu = async (): Promise => { { id: 'show-floating', accelerator: showFloatingWindowShortcut, - label: floatingWindow?.isVisible() ? t('tray.hideFloatingWindow') : t('tray.showFloatingWindow'), + label: floatingWindow?.isVisible() + ? t('tray.hideFloatingWindow') + : t('tray.showFloatingWindow'), type: 'normal', click: async (): Promise => { await triggerFloatingWindow() @@ -487,7 +508,7 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled) const iconPaths = getIconPaths() - + getAppConfig().then(({ disableTrayIconColor = false }) => { if (!tray) return const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status] @@ -526,4 +547,4 @@ export async function updateTrayIcon(): Promise { } catch (error) { console.error('更新托盘图标失败:', error) } -} \ No newline at end of file +} diff --git a/src/main/sys/autoRun.ts b/src/main/sys/autoRun.ts index 5f999b7..f84d037 100644 --- a/src/main/sys/autoRun.ts +++ b/src/main/sys/autoRun.ts @@ -96,8 +96,7 @@ export async function enableAutoRun(): Promise { await execPromise( `powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"` ) - } - catch (e) { + } catch (e) { await managerLogger.info('Maybe the user rejected the UAC dialog?') } } diff --git a/src/main/sys/misc.ts b/src/main/sys/misc.ts index f64191d..89796c3 100644 --- a/src/main/sys/misc.ts +++ b/src/main/sys/misc.ts @@ -77,8 +77,6 @@ export function setNativeTheme(theme: 'system' | 'light' | 'dark'): void { nativeTheme.themeSource = theme } - - export function resetAppConfig(): void { if (process.platform === 'win32') { spawn( diff --git a/src/main/sys/sysproxy.ts b/src/main/sys/sysproxy.ts index 8d20f1b..197d84c 100644 --- a/src/main/sys/sysproxy.ts +++ b/src/main/sys/sysproxy.ts @@ -84,7 +84,7 @@ async function enableSysProxy(): Promise { triggerAutoProxy(true, `http://${host || '127.0.0.1'}:${pacPort}/pac`) } } else if (process.platform === 'darwin') { - await helperRequest(() => + await helperRequest(() => axios.post( 'http://localhost/pac', { url: `http://${host || '127.0.0.1'}:${pacPort}/pac` }, @@ -167,14 +167,14 @@ async function requestSocketRecreation(): Promise { const { exec } = require('child_process') const { promisify } = require('util') const execPromise = promisify(exec) - + // Use osascript with administrator privileges (same pattern as grantTunPermissions) const shell = `pkill -USR1 -f party.mihomo.helper` const command = `do shell script "${shell}" with administrator privileges` await execPromise(`osascript -e '${command}'`) - + // Wait a bit for socket recreation - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) } catch (error) { await proxyLogger.error('Failed to send signal to helper', error) throw error @@ -184,21 +184,24 @@ async function requestSocketRecreation(): Promise { // Wrapper function for helper requests with auto-retry on socket issues async function helperRequest(requestFn: () => Promise, maxRetries = 1): Promise { let lastError: Error | null = null - + for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await requestFn() } catch (error) { lastError = error as Error - + // Check if it's a connection error and socket file doesn't exist - if (attempt < maxRetries && - ((error as NodeJS.ErrnoException).code === 'ECONNREFUSED' || - (error as NodeJS.ErrnoException).code === 'ENOENT' || - (error as Error).message?.includes('connect ECONNREFUSED') || - (error as Error).message?.includes('ENOENT'))) { - - await proxyLogger.info(`Helper request failed (attempt ${attempt + 1}), checking socket file...`) + if ( + attempt < maxRetries && + ((error as NodeJS.ErrnoException).code === 'ECONNREFUSED' || + (error as NodeJS.ErrnoException).code === 'ENOENT' || + (error as Error).message?.includes('connect ECONNREFUSED') || + (error as Error).message?.includes('ENOENT')) + ) { + await proxyLogger.info( + `Helper request failed (attempt ${attempt + 1}), checking socket file...` + ) if (!isSocketFileExists()) { await proxyLogger.info('Socket file missing, requesting recreation...') @@ -211,13 +214,13 @@ async function helperRequest(requestFn: () => Promise, maxRetries = 1): } } } - + // If not a connection error or we've exhausted retries, throw the error if (attempt === maxRetries) { throw lastError } } } - + throw lastError } diff --git a/src/main/utils/chromeRequest.ts b/src/main/utils/chromeRequest.ts index b20140d..be42cba 100644 --- a/src/main/utils/chromeRequest.ts +++ b/src/main/utils/chromeRequest.ts @@ -4,11 +4,13 @@ export interface RequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' headers?: Record body?: string | Buffer - proxy?: { - protocol: 'http' | 'https' | 'socks5' - host: string - port: number - } | false + proxy?: + | { + protocol: 'http' | 'https' | 'socks5' + host: string + port: number + } + | false timeout?: number responseType?: 'text' | 'json' | 'arraybuffer' followRedirect?: boolean diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts index 12181de..246c98b 100644 --- a/src/main/utils/dirs.ts +++ b/src/main/utils/dirs.ts @@ -23,7 +23,7 @@ export function taskDir(): string { if (!existsSync(userDataDir)) { mkdirSync(userDataDir, { recursive: true }) } - + const dir = path.join(userDataDir, 'tasks') if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }) diff --git a/src/main/utils/github.ts b/src/main/utils/github.ts index b486910..ee320d7 100644 --- a/src/main/utils/github.ts +++ b/src/main/utils/github.ts @@ -48,9 +48,13 @@ const versionCache = new Map() * @param forceRefresh 是否强制刷新缓存 * @returns 标签列表 */ -export async function getGitHubTags(owner: string, repo: string, forceRefresh = false): Promise { +export async function getGitHubTags( + owner: string, + repo: string, + forceRefresh = false +): Promise { const cacheKey = `${owner}/${repo}` - + // 检查缓存 if (!forceRefresh && versionCache.has(cacheKey)) { const cache = versionCache.get(cacheKey)! @@ -60,7 +64,7 @@ export async function getGitHubTags(owner: string, repo: string, forceRefresh = return cache.data } } - + try { console.log(`[GitHub] Fetching tags for ${owner}/${repo}`) const response = await chromeRequest.get( @@ -135,45 +139,45 @@ async function downloadGitHubAsset(url: string, outputPath: string): Promise { try { console.log(`[GitHub] Installing mihomo core version ${version}`) - + const plat = platform() let arch = process.arch - + // 映射平台和架构到GitHub Release文件名 const key = `${plat}-${arch}` const name = PLATFORM_MAP[key] - + if (!name) { throw new Error(`Unsupported platform "${plat}-${arch}"`) } - + const isWin = plat === 'win32' const urlExt = isWin ? 'zip' : 'gz' const downloadURL = `https://github.com/MetaCubeX/mihomo/releases/download/${version}/${name}-${version}.${urlExt}` - + const coreDir = mihomoCoreDir() const tempZip = join(coreDir, `temp-core.${urlExt}`) const exeFile = `${name}${isWin ? '.exe' : ''}` const targetFile = `mihomo-specific${isWin ? '.exe' : ''}` const targetPath = join(coreDir, targetFile) - + // 如果目标文件已存在,先停止核心 if (existsSync(targetPath)) { console.log('[GitHub] Stopping core before extracting new core file') // 先停止核心 await stopCore(true) } - + // 下载文件 await downloadGitHubAsset(downloadURL, tempZip) - + // 解压文件 if (urlExt === 'zip') { console.log(`[GitHub] Extracting ZIP file ${tempZip}`) const zip = new AdmZip(tempZip) const entries = zip.getEntries() - const entry = entries.find(e => e.entryName.includes(exeFile)) - + const entry = entries.find((e) => e.entryName.includes(exeFile)) + if (entry) { zip.extractEntryTo(entry, coreDir, false, true, false, targetFile) console.log(`[GitHub] Successfully extracted ${exeFile} to ${targetPath}`) @@ -185,13 +189,13 @@ export async function installMihomoCore(version: string): Promise { console.log(`[GitHub] Extracting GZ file ${tempZip}`) const readStream = createReadStream(tempZip) const writeStream = createWriteStream(targetPath) - + await new Promise((resolve, reject) => { const onError = (error: Error) => { console.error('[GitHub] Gzip decompression failed:', error.message) reject(new Error(`Gzip decompression failed: ${error.message}`)) } - + readStream .pipe(createGunzip().on('error', onError)) .pipe(writeStream) @@ -208,14 +212,16 @@ export async function installMihomoCore(version: string): Promise { .on('error', onError) }) } - + // 清理临时文件 console.log(`[GitHub] Cleaning up temporary file ${tempZip}`) rmSync(tempZip) - + console.log(`[GitHub] Successfully installed mihomo core version ${version}`) } catch (error) { console.error('[GitHub] Failed to install mihomo core:', error) - throw new Error(`Failed to install core: ${error instanceof Error ? error.message : String(error)}`) + throw new Error( + `Failed to install core: ${error instanceof Error ? error.message : String(error)}` + ) } -} \ No newline at end of file +} diff --git a/src/main/utils/init.ts b/src/main/utils/init.ts index 1bad56c..4c6d91c 100644 --- a/src/main/utils/init.ts +++ b/src/main/utils/init.ts @@ -84,7 +84,7 @@ async function fixDataDirPermissions(): Promise { } } - // 比较修改geodata文件修改时间 +// 比较修改geodata文件修改时间 async function isSourceNewer(sourcePath: string, targetPath: string): Promise { try { const sourceStats = await stat(sourcePath) @@ -130,7 +130,11 @@ async function initConfig(): Promise { { path: profileConfigPath(), content: defaultProfileConfig, name: 'profile config' }, { path: overrideConfigPath(), content: defaultOverrideConfig, name: 'override config' }, { path: profilePath('default'), content: defaultProfile, name: 'default profile' }, - { path: controledMihomoConfigPath(), content: defaultControledMihomoConfig, name: 'mihomo config' } + { + path: controledMihomoConfigPath(), + content: defaultControledMihomoConfig, + name: 'mihomo config' + } ] for (const config of configs) { @@ -154,13 +158,15 @@ async function initFiles(): Promise { try { // 检查是否需要复制 if (existsSync(sourcePath)) { - const shouldCopyToWork = !existsSync(targetPath) || await isSourceNewer(sourcePath, targetPath) + const shouldCopyToWork = + !existsSync(targetPath) || (await isSourceNewer(sourcePath, targetPath)) if (shouldCopyToWork) { await cp(sourcePath, targetPath, { recursive: true }) } } if (existsSync(sourcePath)) { - const shouldCopyToTest = !existsSync(testTargetPath) || await isSourceNewer(sourcePath, testTargetPath) + const shouldCopyToTest = + !existsSync(testTargetPath) || (await isSourceNewer(sourcePath, testTargetPath)) if (shouldCopyToTest) { await cp(sourcePath, testTargetPath, { recursive: true }) } @@ -223,7 +229,7 @@ async function cleanup(): Promise { async function migrateSubStoreFiles(): Promise { 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) @@ -276,7 +282,7 @@ async function migration(): Promise { if (!skipAuthPrefixes) { await patchControledMihomoConfig({ 'skip-auth-prefixes': ['127.0.0.1/32', '::1/128'] }) } else if (skipAuthPrefixes.length >= 1 && skipAuthPrefixes[0] === '127.0.0.1/32') { - const filteredPrefixes = skipAuthPrefixes.filter(ip => ip !== '::1/128') + const filteredPrefixes = skipAuthPrefixes.filter((ip) => ip !== '::1/128') const newPrefixes = [filteredPrefixes[0], '::1/128', ...filteredPrefixes.slice(1)] if (JSON.stringify(newPrefixes) !== JSON.stringify(skipAuthPrefixes)) { await patchControledMihomoConfig({ 'skip-auth-prefixes': newPrefixes }) diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index c3b0dad..db7c168 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -1,4 +1,4 @@ -import { app, dialog, ipcMain } from 'electron' +import { app, ipcMain } from 'electron' import { mihomoChangeProxy, mihomoCloseAllConnections, @@ -86,9 +86,22 @@ import { setupFirewall } from '../sys/misc' import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory' -import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore, exportLocalBackup, importLocalBackup } from '../resolve/backup' +import { + listWebdavBackups, + webdavBackup, + webdavDelete, + webdavRestore, + exportLocalBackup, + importLocalBackup +} from '../resolve/backup' import { getInterfaces } from '../sys/interface' -import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon, updateTrayIconImmediate } from '../resolve/tray' +import { + closeTrayIcon, + copyEnv, + showTrayIcon, + updateTrayIcon, + updateTrayIconImmediate +} from '../resolve/tray' import { registerShortcut } from '../resolve/shortcut' import { closeMainWindow, mainWindow, showMainWindow, triggerMainWindow } from '..' import { @@ -135,7 +148,9 @@ function ipcErrorWrapper( // eslint-disable-next-line @typescript-eslint/no-e } // GitHub版本管理相关IPC处理程序 -export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> { +export async function fetchMihomoTags( + forceRefresh = false +): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> { return await getGitHubTags('MetaCubeX', 'mihomo', forceRefresh) } @@ -216,7 +231,9 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id)) ipcMain.handle('getFileStr', (_e, path) => ipcErrorWrapper(getFileStr)(path)) ipcMain.handle('setFileStr', (_e, path, str) => ipcErrorWrapper(setFileStr)(path, str)) - ipcMain.handle('convertMrsRuleset', (_e, path, behavior) => ipcErrorWrapper(convertMrsRuleset)(path, behavior)) + ipcMain.handle('convertMrsRuleset', (_e, path, behavior) => + ipcErrorWrapper(convertMrsRuleset)(path, behavior) + ) ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str)) ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item)) ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id)) @@ -242,7 +259,9 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)()) ipcMain.handle('checkHighPrivilegeCore', () => ipcErrorWrapper(checkHighPrivilegeCore)()) ipcMain.handle('showTunPermissionDialog', () => ipcErrorWrapper(showTunPermissionDialog)()) - ipcMain.handle('showErrorDialog', (_, title: string, message: string) => ipcErrorWrapper(showErrorDialog)(title, message)) + ipcMain.handle('showErrorDialog', (_, title: string, message: string) => + ipcErrorWrapper(showErrorDialog)(title, message) + ) ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)()) ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)()) @@ -347,17 +366,21 @@ export function registerIpcMainHandlers(): void { // 触发托盘菜单更新 ipcMain.emit('updateTrayMenu') }) - + // 注册获取Mihomo标签的IPC处理程序 - ipcMain.handle('fetchMihomoTags', (_e, forceRefresh) => ipcErrorWrapper(fetchMihomoTags)(forceRefresh)) + ipcMain.handle('fetchMihomoTags', (_e, forceRefresh) => + ipcErrorWrapper(fetchMihomoTags)(forceRefresh) + ) // 注册安装特定版本Mihomo核心的IPC处理程序 - ipcMain.handle('installSpecificMihomoCore', (_e, version) => ipcErrorWrapper(installSpecificMihomoCore)(version)) + ipcMain.handle('installSpecificMihomoCore', (_e, version) => + ipcErrorWrapper(installSpecificMihomoCore)(version) + ) // 注册清除版本缓存的IPC处理程序 ipcMain.handle('clearMihomoVersionCache', () => ipcErrorWrapper(clearMihomoVersionCache)()) - + // 规则相关IPC处理程序 ipcMain.handle('getRuleStr', (_e, id) => ipcErrorWrapper(getRuleStr)(id)) ipcMain.handle('setRuleStr', (_e, id, str) => ipcErrorWrapper(setRuleStr)(id, str)) -} \ No newline at end of file +} diff --git a/src/main/utils/logger.ts b/src/main/utils/logger.ts index db22cb5..6259301 100644 --- a/src/main/utils/logger.ts +++ b/src/main/utils/logger.ts @@ -28,13 +28,16 @@ class Logger { } catch (logError) { // 如果写入日志文件失败,仍然输出到控制台 console.error(`[Logger] Failed to write to log file:`, logError) - console.error(`[Logger] Original message: [${level.toUpperCase()}] [${this.moduleName}] ${message}`, error) + console.error( + `[Logger] Original message: [${level.toUpperCase()}] [${this.moduleName}] ${message}`, + error + ) } } private logToConsole(level: LogLevel, message: string, error?: any): void { const prefix = `[${this.moduleName}] ${message}` - + switch (level) { case 'debug': console.debug(prefix, error || '') diff --git a/src/main/utils/yaml.ts b/src/main/utils/yaml.ts index 039b1d7..371b6a6 100644 --- a/src/main/utils/yaml.ts +++ b/src/main/utils/yaml.ts @@ -10,4 +10,4 @@ export const parse = (content: string): T => { export function stringify(content: unknown): string { return yaml.stringify(content) -} \ No newline at end of file +} diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 5a00f49..4fe61f4 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -5,7 +5,9 @@ @source '../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'; @theme { - --default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --default-font-family: + system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, + 'Open Sans', 'Helvetica Neue', sans-serif; } @font-face { @@ -192,4 +194,4 @@ to { width: 0%; } -} \ No newline at end of file +} diff --git a/src/renderer/src/components/base/base-editor.tsx b/src/renderer/src/components/base/base-editor.tsx index e2a2d11..21fed85 100644 --- a/src/renderer/src/components/base/base-editor.tsx +++ b/src/renderer/src/components/base/base-editor.tsx @@ -141,7 +141,7 @@ export const BaseEditor: React.FC = (props) => { }} editorWillMount={editorWillMount} editorDidMount={editorDidMount} - editorWillUnmount={(): void => { }} + editorWillUnmount={(): void => {}} onChange={onChange} /> ) diff --git a/src/renderer/src/components/base/base-error-boundary.tsx b/src/renderer/src/components/base/base-error-boundary.tsx index a5da2bd..b3dc69d 100644 --- a/src/renderer/src/components/base/base-error-boundary.tsx +++ b/src/renderer/src/components/base/base-error-boundary.tsx @@ -5,12 +5,10 @@ import { useTranslation } from 'react-i18next' const ErrorFallback = ({ error }: FallbackProps): React.ReactElement => { const { t } = useTranslation() - + return (
-

- {t('common.error.appCrash')} -

+

{t('common.error.appCrash')}

-
+
已复制
@@ -166,16 +176,14 @@ const ToastItem: React.FC<{ `} style={{ width: 340 }} > -
+
{icon}
- {data.title && ( -

{data.title}

- )} -

- {data.message} -

+ {data.title &&

{data.title}

} +

{data.message}