From 06591b50f42bb07cc1ed678b19ffa40e7c7d32b6 Mon Sep 17 00:00:00 2001 From: Memory <134070804+Memory2314@users.noreply.github.com> Date: Sat, 4 Apr 2026 19:27:06 +0800 Subject: [PATCH] feat: add GitHub proxy selection with auto multi-source fallback --- src/main/resolve/autoUpdater.ts | 100 +++++++++++------- src/main/utils/github.ts | 43 +++++--- .../components/settings/general-config.tsx | 23 +++- src/renderer/src/locales/en-US.json | 6 +- src/renderer/src/locales/fa-IR.json | 5 +- src/renderer/src/locales/ru-RU.json | 5 +- src/renderer/src/locales/zh-CN.json | 5 +- src/renderer/src/locales/zh-TW.json | 5 +- src/shared/types.d.ts | 1 + 9 files changed, 131 insertions(+), 62 deletions(-) diff --git a/src/main/resolve/autoUpdater.ts b/src/main/resolve/autoUpdater.ts index 85ff3d0..90519d3 100644 --- a/src/main/resolve/autoUpdater.ts +++ b/src/main/resolve/autoUpdater.ts @@ -10,25 +10,47 @@ import i18next from 'i18next' import { mainWindow } from '../window' import { appLogger } from '../utils/logger' import { dataDir, exeDir, exePath, isPortable, resourcesFilesDir } from '../utils/dirs' -import { getControledMihomoConfig } from '../config' +import { getAppConfig, getControledMihomoConfig } from '../config' import { checkAdminPrivileges } from '../core/manager' import { parse } from '../utils/yaml' import * as chromeRequest from '../utils/chromeRequest' -export async function checkUpdate(): Promise { - const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig() - const res = await chromeRequest.get( - 'https://github.com/mihomo-party-org/mihomo-party/releases/latest/download/latest.yml', - { - headers: { 'Content-Type': 'application/octet-stream' }, - proxy: { - protocol: 'http', - host: '127.0.0.1', - port: mixedPort - }, - responseType: 'text' +const GITHUB_PROXIES = ['https://gh-proxy.org', 'https://ghfast.top', 'https://down.clashparty.org'] + +function buildDownloadUrls(githubUrl: string, proxyPref = ''): string[] { + if (proxyPref === 'direct') return [githubUrl] + if (proxyPref && proxyPref !== 'auto') return [`${proxyPref}/${githubUrl}`] + // auto: try each proxy then fall back to direct + return [...GITHUB_PROXIES.map((p) => `${p}/${githubUrl}`), githubUrl] +} + +async function tryDownload( + urls: string[], + options: Parameters[1] +): Promise>> { + let lastError: unknown + for (const url of urls) { + try { + return await chromeRequest.get(url, options) + } catch (e) { + lastError = e } - ) + } + throw lastError +} + +export async function checkUpdate(): Promise { + const [{ 'mixed-port': mixedPort = 7890 }, { githubProxy = '' }] = await Promise.all([ + getControledMihomoConfig(), + getAppConfig() + ]) + const githubUrl = + 'https://github.com/mihomo-party-org/mihomo-party/releases/latest/download/latest.yml' + const res = await tryDownload(buildDownloadUrls(githubUrl, githubProxy), { + headers: { 'Content-Type': 'application/octet-stream' }, + proxy: { protocol: 'http', host: '127.0.0.1', port: mixedPort }, + responseType: 'text' + }) const latest = parse(res.data as string) as IAppVersion const currentVersion = app.getVersion() if (compareVersions(latest.version, currentVersion) > 0) { @@ -57,8 +79,11 @@ function compareVersions(a: string, b: string): number { } export async function downloadAndInstallUpdate(version: string): Promise { - const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig() - const baseUrl = `https://github.com/mihomo-party-org/mihomo-party/releases/download/v${version}/` + const [{ 'mixed-port': mixedPort = 7890 }, { githubProxy = '' }] = await Promise.all([ + getControledMihomoConfig(), + getAppConfig() + ]) + const githubBase = `https://github.com/mihomo-party-org/mihomo-party/releases/download/v${version}/` const fileMap = { 'win32-x64': `clash-party-windows-${version}-x64-setup.exe`, 'win32-ia32': `clash-party-windows-${version}-ia32-setup.exe`, @@ -84,34 +109,27 @@ export async function downloadAndInstallUpdate(version: string): Promise { file = file.replace('macos', 'catalina') } } + const proxy = { protocol: 'http' as const, host: '127.0.0.1', port: mixedPort } try { if (!existsSync(path.join(dataDir(), file))) { - const sha256Res = await chromeRequest.get(`${baseUrl}${file}.sha256`, { - proxy: { - protocol: 'http', - host: '127.0.0.1', - port: mixedPort - }, - responseType: 'text' - }) + const sha256Res = await tryDownload( + buildDownloadUrls(`${githubBase}${file}.sha256`, githubProxy), + { proxy, responseType: 'text' } + ) const expectedHash = (sha256Res.data as string).trim().split(/\s+/)[0] - const res = await chromeRequest.get(`${baseUrl}${file}`, { - responseType: 'arraybuffer', - timeout: 0, - proxy: { - protocol: 'http', - host: '127.0.0.1', - port: mixedPort - }, - headers: { - 'Content-Type': 'application/octet-stream' - }, - onProgress: (loaded, total) => { - mainWindow?.webContents.send('updateDownloadProgress', { - status: 'downloading', - percent: Math.round((loaded / total) * 100) - }) - } + const res = await tryDownload( + buildDownloadUrls(`${githubBase}${file}`, githubProxy), + { + responseType: 'arraybuffer', + timeout: 0, + proxy, + headers: { 'Content-Type': 'application/octet-stream' }, + onProgress: (loaded: number, total: number) => { + mainWindow?.webContents.send('updateDownloadProgress', { + status: 'downloading', + percent: Math.round((loaded / total) * 100) + }) + } }) mainWindow?.webContents.send('updateDownloadProgress', { status: 'verifying' }) const fileBuffer = Buffer.from(res.data as ArrayBuffer) diff --git a/src/main/utils/github.ts b/src/main/utils/github.ts index bf5a041..801d017 100644 --- a/src/main/utils/github.ts +++ b/src/main/utils/github.ts @@ -9,9 +9,18 @@ import { stopCore } from '../core/manager' import { mihomoCoreDir } from './dirs' import * as chromeRequest from './chromeRequest' import { createLogger } from './logger' +import { getAppConfig } from '../config' const log = createLogger('GitHub') +const GITHUB_PROXIES = ['https://gh-proxy.org', 'https://ghfast.top', 'https://down.clashparty.org'] + +function buildDownloadUrls(githubUrl: string, proxyPref = ''): string[] { + if (proxyPref === 'direct') return [githubUrl] + if (proxyPref && proxyPref !== 'auto') return [`${proxyPref}/${githubUrl}`] + return [...GITHUB_PROXIES.map((p) => `${p}/${githubUrl}`), githubUrl] +} + export interface GitHubTag { name: string zipball_url: string @@ -115,22 +124,28 @@ export function clearVersionCache(owner: string, repo: string): void { * @param outputPath 输出路径 */ async function downloadGitHubAsset(url: string, outputPath: string): Promise { - try { - log.debug(`Downloading asset from ${url}`) - const response = await chromeRequest.get(url, { - responseType: 'arraybuffer', - timeout: 30000 - }) - - await writeFile(outputPath, Buffer.from(response.data as Buffer)) - log.debug(`Successfully downloaded asset to ${outputPath}`) - } catch (error) { - log.error(`Failed to download asset from ${url}`, error) - if (error instanceof Error) { - throw new Error(`Download error: ${error.message}`) + const { githubProxy = '' } = await getAppConfig() + const urls = buildDownloadUrls(url, githubProxy) + let lastError: unknown + for (const candidate of urls) { + try { + log.debug(`Downloading asset from ${candidate}`) + const response = await chromeRequest.get(candidate, { + responseType: 'arraybuffer', + timeout: 30000 + }) + await writeFile(outputPath, Buffer.from(response.data as Buffer)) + log.debug(`Successfully downloaded asset to ${outputPath}`) + return + } catch (error) { + log.warn(`Download failed from ${candidate}, trying next`, error) + lastError = error } - throw new Error('Failed to download core file') } + log.error(`Failed to download asset from all sources`, lastError) + throw lastError instanceof Error + ? new Error(`Download error: ${lastError.message}`) + : new Error('Failed to download core file') } /** diff --git a/src/renderer/src/components/settings/general-config.tsx b/src/renderer/src/components/settings/general-config.tsx index e6b18b8..f914dd3 100644 --- a/src/renderer/src/components/settings/general-config.tsx +++ b/src/renderer/src/components/settings/general-config.tsx @@ -66,6 +66,7 @@ const GeneralConfig: React.FC = () => { customTheme = 'default.css', envType = [platform === 'win32' ? 'powershell' : 'bash'], autoCheckUpdate, + githubProxy = 'auto', appTheme = 'system', language = 'zh-CN', triggerMainWindowBehavior = 'show', @@ -171,6 +172,24 @@ const GeneralConfig: React.FC = () => { }} /> + + + {
{ @@ -240,7 +259,7 @@ const GeneralConfig: React.FC = () => { >