feat: support user agent config per sub (#1712)

* feat: add user agent support in profile configuration

* Introduced a new user agent field in profile settings, allowing users to specify a custom user agent.
* Updated relevant components and localization files to support the new user agent feature.
* Added a new pnpm workspace configuration for built dependencies.

* feat: enhance profile settings with user agent input and toggle functionality

* Added a user agent input field to the advanced profile settings.
* Implemented toggle icons for showing/hiding advanced settings.
* Updated layout to accommodate the new user agent input alongside the existing auth token field.

* chore: remove pnpm workspace configuration for built dependencies
This commit is contained in:
StarHeart 2026-03-29 18:54:56 +08:00 committed by GitHub
parent 07dd85db84
commit 2a4b9f4e5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 53 additions and 14 deletions

View File

@ -248,6 +248,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
allowFixedInterval: item.allowFixedInterval || false,
autoUpdate: item.autoUpdate ?? false,
authToken: item.authToken,
userAgent: item.userAgent,
updated: new Date().getTime(),
updateTimeout: item.updateTimeout || 5
}
@ -268,7 +269,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
url: item.url,
mixedPort,
userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
userAgent: item.userAgent || userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
authToken: item.authToken,
substore: newItem.substore || false
}

View File

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

View File

@ -496,6 +496,8 @@
"profiles.editInfo.url": "Subscription URL",
"profiles.editInfo.authToken": "Authorization Token",
"profiles.editInfo.authTokenPlaceholder": "Bearer token or other auth header value",
"profiles.editInfo.userAgent": "User Agent",
"profiles.editInfo.userAgentPlaceholder": "Leave empty to use global User Agent",
"profiles.editInfo.useProxy": "Use Proxy to Update",
"profiles.editInfo.interval": "Upd. Interval",
"profiles.editInfo.intervalPlaceholder": "e.g.: 30 or '0 * * * *'",

View File

@ -471,6 +471,8 @@
"profiles.editInfo.title": "ویرایش اطلاعات",
"profiles.editInfo.name": "نام",
"profiles.editInfo.url": "آدرس اشتراک",
"profiles.editInfo.userAgent": "User Agent",
"profiles.editInfo.userAgentPlaceholder": "برای استفاده از User Agent جهانی خالی بگذارید",
"profiles.editInfo.useProxy": "استفاده از پراکسی برای به‌روزرسانی",
"profiles.editInfo.interval": "فاصله به‌روزرسانی",
"profiles.editInfo.fixedInterval": "فاصله به‌روزرسانی ثابت",

View File

@ -473,6 +473,8 @@
"profiles.editInfo.title": "Редактировать информацию",
"profiles.editInfo.name": "Имя",
"profiles.editInfo.url": "URL подписки",
"profiles.editInfo.userAgent": "User Agent",
"profiles.editInfo.userAgentPlaceholder": "Оставьте пустым для глобального User Agent",
"profiles.editInfo.useProxy": "Использовать прокси для обновления",
"profiles.editInfo.interval": "Интервал обн.",
"profiles.editInfo.fixedInterval": "Фиксированный интервал обновления",

View File

@ -501,6 +501,8 @@
"profiles.editInfo.url": "订阅地址",
"profiles.editInfo.authToken": "授权令牌",
"profiles.editInfo.authTokenPlaceholder": "Bearer token 或其他认证头值",
"profiles.editInfo.userAgent": "User Agent",
"profiles.editInfo.userAgentPlaceholder": "留空则使用全局 User Agent",
"profiles.editInfo.useProxy": "使用代理更新",
"profiles.editInfo.interval": "更新间隔",
"profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'",

View File

@ -501,6 +501,8 @@
"profiles.editInfo.url": "訂閱地址",
"profiles.editInfo.authToken": "授權令牌",
"profiles.editInfo.authTokenPlaceholder": "Bearer token 或其他認證頭值",
"profiles.editInfo.userAgent": "User Agent",
"profiles.editInfo.userAgentPlaceholder": "留空則使用全局 User Agent",
"profiles.editInfo.useProxy": "使用代理更新",
"profiles.editInfo.interval": "更新間隔",
"profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'",

View File

@ -18,7 +18,7 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
import { getFilePath, readTextFile, subStoreCollections, subStoreSubs } from '@renderer/utils/ipc'
import type { KeyboardEvent } from 'react'
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { MdContentPaste } from 'react-icons/md'
import { MdContentPaste, MdUnfoldMore, MdUnfoldLess } from 'react-icons/md'
import {
DndContext,
closestCenter,
@ -53,6 +53,7 @@ const Profiles: React.FC = () => {
const [sortedItems, setSortedItems] = useState(items)
const [useProxy, setUseProxy] = useState(false)
const [authToken, setAuthToken] = useState('')
const [userAgent, setUserAgent] = useState('')
const [showAdvanced, setShowAdvanced] = useState(false)
const [subStoreImporting, setSubStoreImporting] = useState(false)
const [importing, setImporting] = useState(false)
@ -135,10 +136,12 @@ const Profiles: React.FC = () => {
type: 'remote',
url,
useProxy,
authToken: authToken || undefined
authToken: authToken || undefined,
userAgent: userAgent || undefined
})
setUrl('')
setAuthToken('')
setUserAgent('')
setImporting(false)
}
const pageRef = useRef<HTMLDivElement>(null)
@ -300,7 +303,11 @@ const Profiles: React.FC = () => {
isIconOnly
onPress={() => setShowAdvanced(!showAdvanced)}
>
🔑
{showAdvanced ? (
<MdUnfoldLess className="text-lg" />
) : (
<MdUnfoldMore className="text-lg" />
)}
</Button>
</Tooltip>
<Button
@ -426,16 +433,25 @@ const Profiles: React.FC = () => {
</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>}
/>
<div className="flex gap-2">
<Input
size="sm"
type="password"
placeholder={t('profiles.editInfo.authTokenPlaceholder')}
value={authToken}
onValueChange={setAuthToken}
onKeyUp={handleInputKeyUp}
className="flex-1"
/>
<Input
size="sm"
placeholder={t('profiles.editInfo.userAgentPlaceholder')}
value={userAgent}
onValueChange={setUserAgent}
onKeyUp={handleInputKeyUp}
className="flex-1"
/>
</div>
)}
</div>
<Divider />

View File

@ -519,6 +519,7 @@ interface IProfileItem {
allowFixedInterval?: boolean
autoUpdate?: boolean
authToken?: string
userAgent?: string
updateTimeout?: number
}