Compare commits

..

No commits in common. "f541b5ead1eb1955fa065beb636023683bbd137e" and "b76757bc192f3221bdd93640965193895569adaa" have entirely different histories.

10 changed files with 120 additions and 184 deletions

View File

@ -142,7 +142,6 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
useProxy: item.useProxy || false, useProxy: item.useProxy || false,
allowFixedInterval: item.allowFixedInterval || false, allowFixedInterval: item.allowFixedInterval || false,
autoUpdate: item.autoUpdate ?? false, autoUpdate: item.autoUpdate ?? false,
authToken: item.authToken,
updated: new Date().getTime() updated: new Date().getTime()
} as IProfileItem } as IProfileItem
switch (newItem.type) { switch (newItem.type) {
@ -160,24 +159,14 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
} else { } else {
urlObj.searchParams.delete('proxy') urlObj.searchParams.delete('proxy')
} }
const headers: Record<string, string> = {
'User-Agent': userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`
}
if (item.authToken) {
headers['Authorization'] = item.authToken
}
res = await chromeRequest.get(urlObj.toString(), { res = await chromeRequest.get(urlObj.toString(), {
headers, headers: {
'User-Agent': userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`
},
responseType: 'text', responseType: 'text',
timeout: subscriptionTimeout timeout: subscriptionTimeout
}) })
} else { } else {
const headers: Record<string, string> = {
'User-Agent': userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`
}
if (item.authToken) {
headers['Authorization'] = item.authToken
}
res = await chromeRequest.get(item.url, { res = await chromeRequest.get(item.url, {
proxy: newItem.useProxy proxy: newItem.useProxy
? { ? {
@ -186,7 +175,9 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
port: mixedPort port: mixedPort
} }
: false, : false,
headers, headers: {
'User-Agent': userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`
},
responseType: 'text', responseType: 'text',
timeout: subscriptionTimeout timeout: subscriptionTimeout
}) })

View File

@ -163,7 +163,7 @@ export async function startCore(detached = false): Promise<Promise<void>[]> {
// 内核日志输出到独立的 core-日期.log 文件 // 内核日志输出到独立的 core-日期.log 文件
const stdout = createWriteStream(coreLogPath(), { flags: 'a' }) const stdout = createWriteStream(coreLogPath(), { flags: 'a' })
const stderr = createWriteStream(coreLogPath(), { flags: 'a' }) const stderr = createWriteStream(coreLogPath(), { flags: 'a' })
child = spawn( child = spawn(
corePath, corePath,
['-d', diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), ctlParam, dynamicIpcPath], ['-d', diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), ctlParam, dynamicIpcPath],
@ -307,7 +307,7 @@ async function cleanupWindowsNamedPipes(): Promise<void> {
try { try {
const { stdout } = await execPromise( const { stdout } = await execPromise(
`powershell -NoProfile -Command "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Process | Where-Object {$_.ProcessName -like '*mihomo*'} | Select-Object Id,ProcessName | ConvertTo-Json"`, `powershell -Command "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Process | Where-Object {$_.ProcessName -like '*mihomo*'} | Select-Object Id,ProcessName | ConvertTo-Json"`,
{ encoding: 'utf8' } { encoding: 'utf8' }
) )
@ -458,7 +458,7 @@ async function checkProfile(): Promise<void> {
const { current } = await getProfileConfig() const { current } = await getProfileConfig()
const corePath = mihomoCorePath(core) const corePath = mihomoCorePath(core)
const execFilePromise = promisify(execFile) const execFilePromise = promisify(execFile)
try { try {
await execFilePromise(corePath, [ await execFilePromise(corePath, [
'-t', '-t',
@ -598,7 +598,7 @@ export async function checkAdminPrivileges(): Promise<boolean> {
} }
const execPromise = promisify(exec) const execPromise = promisify(exec)
try { try {
// fltmc 检测管理员权限 // fltmc 检测管理员权限
await execPromise('chcp 65001 >nul 2>&1 && fltmc', { encoding: 'utf8' }) await execPromise('chcp 65001 >nul 2>&1 && fltmc', { encoding: 'utf8' })
@ -607,7 +607,7 @@ export async function checkAdminPrivileges(): Promise<boolean> {
} catch (fltmcError: any) { } catch (fltmcError: any) {
const errorCode = fltmcError?.code || 0 const errorCode = fltmcError?.code || 0
await managerLogger.debug(`fltmc failed with code ${errorCode}, trying net session as fallback`) await managerLogger.debug(`fltmc failed with code ${errorCode}, trying net session as fallback`)
try { try {
// net session 备用 // net session 备用
await execPromise('chcp 65001 >nul 2>&1 && net session', { encoding: 'utf8' }) await execPromise('chcp 65001 >nul 2>&1 && net session', { encoding: 'utf8' })
@ -678,13 +678,13 @@ export async function restartAsAdmin(forTun: boolean = true): Promise<void> {
try { try {
// 处理路径和参数的引号 // 处理路径和参数的引号
const escapedExePath = exePath.replace(/'/g, "''") const escapedExePath = exePath.replace(/'/g, "''")
const argsString = restartArgs.map((arg) => arg.replace(/'/g, "''")).join("', '") const argsString = restartArgs.map(arg => arg.replace(/'/g, "''")).join("', '")
let command: string let command: string
if (restartArgs.length > 0) { if (restartArgs.length > 0) {
command = `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -Verb RunAs"` command = `powershell -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -Verb RunAs"`
} else { } else {
command = `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs"` command = `powershell -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs"`
} }
await managerLogger.info('Restarting as administrator with command', command) await managerLogger.info('Restarting as administrator with command', command)

View File

@ -100,7 +100,7 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
try { try {
const installerPath = path.join(dataDir(), file) const installerPath = path.join(dataDir(), file)
const isAdmin = await checkAdminPrivileges() const isAdmin = await checkAdminPrivileges()
if (isAdmin) { if (isAdmin) {
await appLogger.info('Running installer with existing admin privileges') await appLogger.info('Running installer with existing admin privileges')
spawn(installerPath, ['/S', '--force-run'], { spawn(installerPath, ['/S', '--force-run'], {
@ -111,20 +111,20 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
// 提升权限安装 // 提升权限安装
const escapedPath = installerPath.replace(/'/g, "''") const escapedPath = installerPath.replace(/'/g, "''")
const args = ['/S', '--force-run'] const args = ['/S', '--force-run']
const argsString = args.map((arg) => arg.replace(/'/g, "''")).join("', '") const argsString = args.map(arg => arg.replace(/'/g, "''")).join("', '")
const command = `powershell -NoProfile -Command "Start-Process -FilePath '${escapedPath}' -ArgumentList '${argsString}' -Verb RunAs -WindowStyle Hidden"` const command = `powershell -Command "Start-Process -FilePath '${escapedPath}' -ArgumentList '${argsString}' -Verb RunAs -WindowStyle Hidden"`
await appLogger.info('Starting installer with elevated privileges') await appLogger.info('Starting installer with elevated privileges')
const execPromise = promisify(exec) const execPromise = promisify(exec)
await execPromise(command, { windowsHide: true }) await execPromise(command, { windowsHide: true })
await appLogger.info('Installer started successfully with elevation') await appLogger.info('Installer started successfully with elevation')
} }
} catch (installerError) { } catch (installerError) {
await appLogger.error('Failed to start installer, trying fallback', installerError) await appLogger.error('Failed to start installer, trying fallback', installerError)
// Fallback: 尝试使用shell.openPath打开安装包 // Fallback: 尝试使用shell.openPath打开安装包
try { try {
await shell.openPath(path.join(dataDir(), file)) await shell.openPath(path.join(dataDir(), file))

View File

@ -88,13 +88,11 @@ export async function enableAutoRun(): Promise<void> {
const isAdmin = await checkAdminPrivileges() const isAdmin = await checkAdminPrivileges()
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml(isAdmin)}`, 'utf-16le')) await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml(isAdmin)}`, 'utf-16le'))
if (isAdmin) { if (isAdmin) {
await execPromise( await execPromise(`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`)
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
)
} else { } else {
try { try {
await execPromise( await execPromise(
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"` `powershell -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"`
) )
} }
catch (e) { catch (e) {
@ -142,9 +140,7 @@ export async function disableAutoRun(): Promise<void> {
await execPromise(`%SystemRoot%\\System32\\schtasks.exe /delete /tn "${appName}" /f`) await execPromise(`%SystemRoot%\\System32\\schtasks.exe /delete /tn "${appName}" /f`)
} else { } else {
try { try {
await execPromise( await execPromise(`powershell -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden"`)
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden"`
)
} catch (e) { } catch (e) {
await managerLogger.info('Maybe the user rejected the UAC dialog?') await managerLogger.info('Maybe the user rejected the UAC dialog?')
} }

View File

@ -43,7 +43,7 @@ export async function openUWPTool(): Promise<void> {
if (!isAdmin) { if (!isAdmin) {
const escapedPath = uwpToolPath.replace(/'/g, "''") const escapedPath = uwpToolPath.replace(/'/g, "''")
const command = `powershell -NoProfile -Command "Start-Process -FilePath '${escapedPath}' -Verb RunAs -Wait"` const command = `powershell -Command "Start-Process -FilePath '${escapedPath}' -Verb RunAs -Wait"`
await execPromise(command, { windowsHide: true }) await execPromise(command, { windowsHide: true })
return return

View File

@ -91,18 +91,6 @@ const EditInfoModal: React.FC<Props> = (props) => {
}} }}
/> />
</SettingItem> </SettingItem>
<SettingItem title={t('profiles.editInfo.authToken')}>
<Input
size="sm"
type="password"
className={cn(inputWidth)}
value={values.authToken || ''}
onValueChange={(v) => {
setValues({ ...values, authToken: v })
}}
placeholder={t('profiles.editInfo.authTokenPlaceholder')}
/>
</SettingItem>
<SettingItem title={t('profiles.editInfo.useProxy')}> <SettingItem title={t('profiles.editInfo.useProxy')}>
<Switch <Switch
size="sm" size="sm"

View File

@ -438,8 +438,6 @@
"profiles.editInfo.title": "Edit Information", "profiles.editInfo.title": "Edit Information",
"profiles.editInfo.name": "Name", "profiles.editInfo.name": "Name",
"profiles.editInfo.url": "Subscription URL", "profiles.editInfo.url": "Subscription URL",
"profiles.editInfo.authToken": "Authorization Token",
"profiles.editInfo.authTokenPlaceholder": "Bearer token or other auth header value",
"profiles.editInfo.useProxy": "Use Proxy to Update", "profiles.editInfo.useProxy": "Use Proxy to Update",
"profiles.editInfo.interval": "Upd. Interval", "profiles.editInfo.interval": "Upd. Interval",
"profiles.editInfo.intervalPlaceholder": "e.g.: 30 or '0 * * * *'", "profiles.editInfo.intervalPlaceholder": "e.g.: 30 or '0 * * * *'",

View File

@ -443,8 +443,6 @@
"profiles.editInfo.title": "编辑信息", "profiles.editInfo.title": "编辑信息",
"profiles.editInfo.name": "名称", "profiles.editInfo.name": "名称",
"profiles.editInfo.url": "订阅地址", "profiles.editInfo.url": "订阅地址",
"profiles.editInfo.authToken": "授权令牌",
"profiles.editInfo.authTokenPlaceholder": "Bearer token 或其他认证头值",
"profiles.editInfo.useProxy": "使用代理更新", "profiles.editInfo.useProxy": "使用代理更新",
"profiles.editInfo.interval": "更新间隔", "profiles.editInfo.interval": "更新间隔",
"profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'", "profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'",

View File

@ -7,8 +7,7 @@ import {
DropdownItem, DropdownItem,
DropdownMenu, DropdownMenu,
DropdownTrigger, DropdownTrigger,
Input, Input
Tooltip
} from '@heroui/react' } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page' import BasePage from '@renderer/components/base/base-page'
import ProfileItem from '@renderer/components/profiles/profile-item' import ProfileItem from '@renderer/components/profiles/profile-item'
@ -51,8 +50,6 @@ const Profiles: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [sortedItems, setSortedItems] = useState(items) const [sortedItems, setSortedItems] = useState(items)
const [useProxy, setUseProxy] = useState(false) const [useProxy, setUseProxy] = useState(false)
const [authToken, setAuthToken] = useState('')
const [showAdvanced, setShowAdvanced] = useState(false)
const [subStoreImporting, setSubStoreImporting] = useState(false) const [subStoreImporting, setSubStoreImporting] = useState(false)
const [importing, setImporting] = useState(false) const [importing, setImporting] = useState(false)
const [updating, setUpdating] = useState(false) const [updating, setUpdating] = useState(false)
@ -128,15 +125,8 @@ const Profiles: React.FC = () => {
}, [subs, collections]) }, [subs, collections])
const handleImport = async (): Promise<void> => { const handleImport = async (): Promise<void> => {
setImporting(true) setImporting(true)
await addProfileItem({ await addProfileItem({ name: '', type: 'remote', url, useProxy })
name: '',
type: 'remote',
url,
useProxy,
authToken: authToken || undefined
})
setUrl('') setUrl('')
setAuthToken('')
setImporting(false) setImporting(false)
} }
const pageRef = useRef<HTMLDivElement>(null) const pageRef = useRef<HTMLDivElement>(null)
@ -235,79 +225,68 @@ const Profiles: React.FC = () => {
} }
> >
<div className="sticky profiles-sticky top-0 z-40 bg-background"> <div className="sticky profiles-sticky top-0 z-40 bg-background">
<div className="flex flex-col gap-2 p-2"> <div className="flex p-2">
<div className="flex gap-2"> <Input
<Input size="sm"
size="sm" placeholder={t('profiles.input.placeholder')}
placeholder={t('profiles.input.placeholder')} value={url}
value={url} onValueChange={setUrl}
onValueChange={setUrl} onKeyUp={handleInputKeyUp}
onKeyUp={handleInputKeyUp} endContent={
className="flex-1" <>
endContent={ <Button
<> size="md"
<Button isIconOnly
size="md" variant="light"
isIconOnly onPress={() => {
variant="light" navigator.clipboard.readText().then((text) => {
onPress={() => { setUrl(text)
navigator.clipboard.readText().then((text) => { })
setUrl(text) }}
}) className="mr-2"
}} >
className="mr-2" <MdContentPaste className="text-lg" />
> </Button>
<MdContentPaste className="text-lg" /> <Checkbox
</Button> className="whitespace-nowrap"
<Checkbox checked={useProxy}
className="whitespace-nowrap" onValueChange={setUseProxy}
checked={useProxy} >
onValueChange={setUseProxy} {t('profiles.useProxy')}
> </Checkbox>
{t('profiles.useProxy')} </>
</Checkbox> }
</> />
}
/>
<Tooltip content={t('profiles.editInfo.authToken')} placement="bottom"> <Button
<Button size="sm"
size="sm" color="primary"
variant={showAdvanced ? 'solid' : 'light'} className="ml-2"
isIconOnly isDisabled={isUrlEmpty}
onPress={() => setShowAdvanced(!showAdvanced)} isLoading={importing}
> onPress={handleImport}
🔑 >
</Button> {t('profiles.import')}
</Tooltip> </Button>
<Button {useSubStore && (
size="sm" <Dropdown
color="primary" onOpenChange={() => {
isDisabled={isUrlEmpty} mutateSubs()
isLoading={importing} mutateCollections()
onPress={handleImport} }}
> >
{t('profiles.import')} <DropdownTrigger>
</Button> <Button
{useSubStore && ( isLoading={subStoreImporting}
<Dropdown title="Sub-Store"
onOpenChange={() => { className="ml-2 substore-import"
mutateSubs() size="sm"
mutateCollections() isIconOnly
}} color="primary"
> >
<DropdownTrigger> <SubStoreIcon className="text-lg" />
<Button </Button>
isLoading={subStoreImporting} </DropdownTrigger>
title="Sub-Store"
className="substore-import"
size="sm"
isIconOnly
color="primary"
>
<SubStoreIcon className="text-lg" />
</Button>
</DropdownTrigger>
<DropdownMenu <DropdownMenu
className="max-h-[calc(100vh-200px)] overflow-y-auto" className="max-h-[calc(100vh-200px)] overflow-y-auto"
onAction={async (key) => { onAction={async (key) => {
@ -363,53 +342,40 @@ const Profiles: React.FC = () => {
</DropdownItem> </DropdownItem>
))} ))}
</DropdownMenu> </DropdownMenu>
</Dropdown>
)}
<Dropdown>
<DropdownTrigger>
<Button className="new-profile" size="sm" isIconOnly color="primary">
<FaPlus />
</Button>
</DropdownTrigger>
<DropdownMenu
onAction={async (key) => {
if (key === 'open') {
try {
const files = await getFilePath(['yml', 'yaml'])
if (files?.length) {
const content = await readTextFile(files[0])
const fileName = files[0].split('/').pop()?.split('\\').pop()
await addProfileItem({ name: fileName, type: 'local', file: content })
}
} catch (e) {
alert(e)
}
} else if (key === 'new') {
await addProfileItem({
name: t('profiles.newProfile'),
type: 'local',
file: 'proxies: []\nproxy-groups: []\nrules: []'
})
}
}}
>
<DropdownItem key="open">{t('profiles.open')}</DropdownItem>
<DropdownItem key="new">{t('profiles.new')}</DropdownItem>
</DropdownMenu>
</Dropdown> </Dropdown>
</div>
{showAdvanced && (
<Input
size="sm"
type="password"
placeholder={t('profiles.editInfo.authTokenPlaceholder')}
value={authToken}
onValueChange={setAuthToken}
onKeyUp={handleInputKeyUp}
className="w-full"
startContent={<span className="text-default-400 text-sm">🔑</span>}
/>
)} )}
<Dropdown>
<DropdownTrigger>
<Button className="ml-2 new-profile" size="sm" isIconOnly color="primary">
<FaPlus />
</Button>
</DropdownTrigger>
<DropdownMenu
onAction={async (key) => {
if (key === 'open') {
try {
const files = await getFilePath(['yml', 'yaml'])
if (files?.length) {
const content = await readTextFile(files[0])
const fileName = files[0].split('/').pop()?.split('\\').pop()
await addProfileItem({ name: fileName, type: 'local', file: content })
}
} catch (e) {
alert(e)
}
} else if (key === 'new') {
await addProfileItem({
name: t('profiles.newProfile'),
type: 'local',
file: 'proxies: []\nproxy-groups: []\nrules: []'
})
}
}}
>
<DropdownItem key="open">{t('profiles.open')}</DropdownItem>
<DropdownItem key="new">{t('profiles.new')}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div> </div>
<Divider /> <Divider />
</div> </div>

View File

@ -492,7 +492,6 @@ interface IProfileItem {
substore?: boolean substore?: boolean
allowFixedInterval?: boolean allowFixedInterval?: boolean
autoUpdate?: boolean autoUpdate?: boolean
authToken?: string
} }
interface ISubStoreSub { interface ISubStoreSub {