diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 2df4d2892..0f75922a9 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -69,6 +69,9 @@ "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -522,6 +525,7 @@ "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", @@ -704,6 +708,9 @@ "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -1181,6 +1188,7 @@ "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 2df4d2892..0f75922a9 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -69,6 +69,9 @@ "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -522,6 +525,7 @@ "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", @@ -704,6 +708,9 @@ "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -1181,6 +1188,7 @@ "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 4ee0bb42b..b8c4ba516 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -13,6 +13,7 @@ export const EXAMPLES: Record = { "discordMemory": 0, "discordRunTime": "", "expireDiff": 0, + "externalSubUserAgent": "", "externalTrafficInformEnable": false, "externalTrafficInformURI": "", "happLinkEnable": false, @@ -163,6 +164,7 @@ export const EXAMPLES: Record = { "discordMemory": 0, "discordRunTime": "", "expireDiff": 0, + "externalSubUserAgent": "", "externalTrafficInformEnable": false, "externalTrafficInformURI": "", "happLinkEnable": false, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 4daed56e2..e0e32f275 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -43,6 +43,9 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -496,6 +499,7 @@ export const SCHEMAS: Record = { "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", @@ -678,6 +682,9 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, + "externalSubUserAgent": { + "type": "string" + }, "externalTrafficInformEnable": { "type": "boolean" }, @@ -1155,6 +1162,7 @@ export const SCHEMAS: Record = { "discordMemory", "discordRunTime", "expireDiff", + "externalSubUserAgent", "externalTrafficInformEnable", "externalTrafficInformURI", "happLinkEnable", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 3ebb9b6c0..690da30a4 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -21,6 +21,7 @@ export interface AllSetting { discordMemory: number; discordRunTime: string; expireDiff: number; + externalSubUserAgent: string; externalTrafficInformEnable: boolean; externalTrafficInformURI: string; happLinkEnable: boolean; @@ -172,6 +173,7 @@ export interface AllSettingView { discordMemory: number; discordRunTime: string; expireDiff: number; + externalSubUserAgent: string; externalTrafficInformEnable: boolean; externalTrafficInformURI: string; happLinkEnable: boolean; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index 5aa85f891..a52f6ce96 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -37,6 +37,7 @@ export const AllSettingSchema = z.object({ discordMemory: z.number().int().min(0).max(100), discordRunTime: z.string(), expireDiff: z.number().int().min(0), + externalSubUserAgent: z.string(), externalTrafficInformEnable: z.boolean(), externalTrafficInformURI: z.string(), happLinkEnable: z.boolean(), @@ -189,6 +190,7 @@ export const AllSettingViewSchema = z.object({ discordMemory: z.number().int().min(0).max(100), discordRunTime: z.string(), expireDiff: z.number().int().min(0), + externalSubUserAgent: z.string(), externalTrafficInformEnable: z.boolean(), externalTrafficInformURI: z.string(), happLinkEnable: z.boolean(), diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index 65598828c..73311d1aa 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -66,6 +66,7 @@ export class AllSetting { restartXrayOnClientDisable = true; subCertFile = ''; subKeyFile = ''; + externalSubUserAgent = 'v2rayNG/1.8.5'; subUpdates = 12; subEncrypt = true; subURI = ''; diff --git a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx index a7a28ce63..e73133f7f 100644 --- a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx +++ b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx @@ -247,6 +247,26 @@ export default function SubscriptionGeneralTab({ onChange={onNumber((v) => updateSetting({ subUpdates: v }))} /> + + + } + description={t('pages.settings.externalSubUserAgentDesc')} + > + updateSetting({ externalSubUserAgent: e.target.value })} + /> + ), }, diff --git a/frontend/src/schemas/setting.ts b/frontend/src/schemas/setting.ts index 250d108ac..fd25644fb 100644 --- a/frontend/src/schemas/setting.ts +++ b/frontend/src/schemas/setting.ts @@ -72,6 +72,7 @@ export const AllSettingSchema = z restartXrayOnClientDisable: z.boolean().optional(), subCertFile: z.string().optional(), subKeyFile: z.string().optional(), + externalSubUserAgent: z.string().max(512).optional(), subUpdates: z.number().int().min(0).max(525600).optional(), subEncrypt: z.boolean().optional(), subURI: z.string().optional(), diff --git a/internal/sub/external_subscription.go b/internal/sub/external_subscription.go index e6122edcf..f775fb6b2 100644 --- a/internal/sub/external_subscription.go +++ b/internal/sub/external_subscription.go @@ -148,7 +148,7 @@ func doFetchSubscriptionLinks(rawURL string) ([]string, error) { return nil, err } // Some providers gate the link body on a known client User-Agent. - req.Header.Set("User-Agent", "v2rayNG/1.8.5") + req.Header.Set("User-Agent", externalSubUserAgent()) // A 3x-ui donor with an HWID limit answers 404 when the header is empty (#6559). if hwid := service.ExternalSubscriptionHwid(); hwid != "" { req.Header.Set("X-HWID", hwid) @@ -176,6 +176,19 @@ var ( errSubscriptionBodyTooLarge = &subError{"subscription response body exceeds size limit"} ) +// externalSubUserAgent returns the panel setting for external subscription +// fetches, or the historical client UA when it is unset or the DB is unreachable. +func externalSubUserAgent() string { + if database.GetDB() == nil { + return service.DefaultExternalSubUserAgent + } + ua, err := (&service.SettingService{}).GetExternalSubUserAgent() + if err != nil { + return service.DefaultExternalSubUserAgent + } + return ua +} + type subError struct{ msg string } func (e *subError) Error() string { return e.msg } diff --git a/internal/sub/external_subscription_test.go b/internal/sub/external_subscription_test.go index 18de7a9d7..1e887cb6d 100644 --- a/internal/sub/external_subscription_test.go +++ b/internal/sub/external_subscription_test.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" "net/http/httptest" + "path/filepath" "strconv" "strings" "sync" @@ -298,3 +299,57 @@ func TestExpandEntryCacheHitWritesNothing(t *testing.T) { t.Fatalf("cache hit wrote fetch status: %#v", after) } } + +func TestFetchUsesConfiguredExternalSubUserAgent(t *testing.T) { + resetSubscriptionCache(t) + if err := database.InitDB(filepath.Join(t.TempDir(), "ua.db")); err != nil { + t.Fatalf("InitDB: %v", err) + } + t.Cleanup(func() { _ = database.CloseDB() }) + + const customUA = "Happ/4.2.1" + if err := database.GetDB().Create(&model.Setting{ + Key: "externalSubUserAgent", + Value: customUA, + }).Error; err != nil { + t.Fatalf("save setting: %v", err) + } + + var gotUA string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUA = r.Header.Get("User-Agent") + _, _ = w.Write([]byte("vless://uuid@host:443?security=none#x")) + })) + defer srv.Close() + + res := fetchSubscriptionLinks(srv.URL) + if res.err != nil { + t.Fatalf("fetch: %v", res.err) + } + if gotUA != customUA { + t.Fatalf("User-Agent = %q, want %q", gotUA, customUA) + } +} + +func TestFetchFallsBackToDefaultExternalSubUserAgent(t *testing.T) { + resetSubscriptionCache(t) + if err := database.InitDB(filepath.Join(t.TempDir(), "ua-default.db")); err != nil { + t.Fatalf("InitDB: %v", err) + } + t.Cleanup(func() { _ = database.CloseDB() }) + + var gotUA string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUA = r.Header.Get("User-Agent") + _, _ = w.Write([]byte("vless://uuid@host:443?security=none#x")) + })) + defer srv.Close() + + res := fetchSubscriptionLinks(srv.URL) + if res.err != nil { + t.Fatalf("fetch: %v", res.err) + } + if gotUA != "v2rayNG/1.8.5" { + t.Fatalf("User-Agent = %q, want default v2rayNG/1.8.5", gotUA) + } +} diff --git a/internal/web/entity/entity.go b/internal/web/entity/entity.go index a4aecd6a7..441b2791e 100644 --- a/internal/web/entity/entity.go +++ b/internal/web/entity/entity.go @@ -107,6 +107,7 @@ type AllSetting struct { SubDomain string `json:"subDomain" form:"subDomain"` SubCertFile string `json:"subCertFile" form:"subCertFile"` SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` + ExternalSubUserAgent string `json:"externalSubUserAgent" form:"externalSubUserAgent"` SubUpdates int `json:"subUpdates" form:"subUpdates" validate:"gte=0,lte=525600"` ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"` ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"` diff --git a/internal/web/service/setting.go b/internal/web/service/setting.go index aeee1e877..07027babe 100644 --- a/internal/web/service/setting.go +++ b/internal/web/service/setting.go @@ -36,6 +36,7 @@ var xrayTemplateConfig string const ( DefaultSubClashUserAgentRegex = `(?i)(clash|mihomo)` + DefaultExternalSubUserAgent = "v2rayNG/1.8.5" DefaultSubJsonUserAgentRegex = `` DefaultRemarkTemplate = "{{INBOUND}}-{{EMAIL}}|📊{{TRAFFIC_LEFT}}|⏳{{DAYS_LEFT}}D" DefaultSubExpiredTemplate = "⛔ {{EMAIL}} | Expired: {{EXPIRE_DATE}}" @@ -146,6 +147,7 @@ var defaultValueMap = map[string]string{ "subDomain": "", "subCertFile": "", "subKeyFile": "", + "externalSubUserAgent": DefaultExternalSubUserAgent, "subUpdates": "12", "subEncrypt": "true", "subURI": "", @@ -1052,6 +1054,17 @@ func (s *SettingService) GetSubUpdates() (string, error) { return s.getString("subUpdates") } +func (s *SettingService) GetExternalSubUserAgent() (string, error) { + value, err := s.getString("externalSubUserAgent") + if err != nil { + return DefaultExternalSubUserAgent, err + } + if value = strings.TrimSpace(value); value == "" { + return DefaultExternalSubUserAgent, nil + } + return value, nil +} + func (s *SettingService) GetSubEncrypt() (bool, error) { return s.getBool("subEncrypt") } diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index 2e3290ab5..d097db920 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs). كمان بيتستخدم كدومين افتراضي لرابط الاشتراك اللي بيظهر لو حقل «مسار البروكسي العكسي» فاضي — املا «مسار البروكسي العكسي» لو اللوحة والاشتراك بيتفتحوا من دومينات مختلفة (زي لما يكونوا ورا بروكسي عكسي).", "subUpdates": "فترات التحديث", "subUpdatesDesc": "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)", + "externalSubUserAgent": "User-Agent للاشتراك الخارجي", + "externalSubUserAgentDesc": "ترويسة User-Agent عند جلب رابط الاشتراك الخارجي للعميل. اتركه فارغًا لاستخدام الافتراضي (v2rayNG/1.8.5). بعض المزودين يرفضون العملاء غير المعروفين.", "subEncrypt": "تشفير", "subEncryptDesc": "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64.", "subURI": "مسار البروكسي العكسي", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index fa677cbb4..cbb086056 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1395,6 +1395,8 @@ "subDomainDesc": "The domain name for the subscription service. (leave blank to listen on all domains and IPs). Also used as the fallback domain for the displayed subscription link when \"Reverse Proxy URI\" is empty — set \"Reverse Proxy URI\" if the panel and the subscription are reached through different domains (e.g. behind a reverse proxy).", "subUpdates": "Update Intervals", "subUpdatesDesc": "The update intervals of the subscription URL in the client apps. (unit: hour)", + "externalSubUserAgent": "External subscription User-Agent", + "externalSubUserAgentDesc": "User-Agent header sent when the panel fetches a client external subscription URL. Leave blank to use the default (v2rayNG/1.8.5). Some providers reject unknown clients.", "subEncrypt": "Encode", "subEncryptDesc": "The returned content of subscription service will be Base64 encoded.", "subURI": "Reverse Proxy URI", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index 3b3c62417..f3d6aac69 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Dejar en blanco por defecto para monitorear todos los dominios e IPs. También se usa como dominio de reserva para el enlace de suscripción mostrado cuando «URI de proxy inverso» está vacío — configure «URI de proxy inverso» si el panel y la suscripción se acceden por dominios diferentes (por ejemplo, detrás de un proxy inverso).", "subUpdates": "Intervalos de Actualización de Suscripción", "subUpdatesDesc": "Horas de intervalo entre actualizaciones en la aplicación del cliente.", + "externalSubUserAgent": "User-Agent de suscripción externa", + "externalSubUserAgentDesc": "Cabecera User-Agent al obtener la URL de suscripción externa del cliente. Déjelo vacío para usar el valor predeterminado (v2rayNG/1.8.5). Algunos proveedores rechazan clientes desconocidos.", "subEncrypt": "Codificar", "subEncryptDesc": "Encriptar las configuraciones devueltas en la suscripción.", "subURI": "URI de proxy inverso", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index a64e7b2b3..8880dc3b7 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -1277,6 +1277,8 @@ "subDomainDesc": "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید. همچنین وقتی «مسیر پراکسی معکوس» خالی باشد، به عنوان دامنه پیش‌فرض لینک سابسکریپشن نمایش‌داده‌شده استفاده می‌شود — اگر پنل و سابسکریپشن از دامنه‌های متفاوتی در دسترس هستند (مثلاً پشت یک پراکسی معکوس)، «مسیر پراکسی معکوس» را پر کنید.", "subUpdates": "فاصله بروزرسانی‌ سابسکریپشن", "subUpdatesDesc": "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت", + "externalSubUserAgent": "User-Agent اشتراک خارجی", + "externalSubUserAgentDesc": "هدر User-Agent هنگام واکشی URL اشتراک خارجی کلاینت. خالی بگذارید تا پیش‌فرض (v2rayNG/1.8.5) استفاده شود. بعضی ارائه‌دهنده‌ها کلاینت ناشناس را رد می‌کنند.", "subEncrypt": "انکود", "subEncryptDesc": "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه", "subURI": "پروکسی معکوس URI مسیر", diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 39732b796..77734beea 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP). Juga digunakan sebagai domain cadangan untuk tautan langganan yang ditampilkan ketika \"URI Proxy Terbalik\" kosong — atur \"URI Proxy Terbalik\" jika panel dan langganan diakses melalui domain yang berbeda (misalnya di belakang reverse proxy).", "subUpdates": "Interval Pembaruan", "subUpdatesDesc": "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)", + "externalSubUserAgent": "User-Agent langganan eksternal", + "externalSubUserAgentDesc": "Header User-Agent saat panel mengambil URL langganan eksternal klien. Kosongkan untuk memakai bawaan (v2rayNG/1.8.5). Beberapa penyedia menolak klien yang tidak dikenal.", "subEncrypt": "Encode", "subEncryptDesc": "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64.", "subURI": "URI Proxy Terbalik", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 314e8d8b3..696b03694 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)。「リバースプロキシURI」が空の場合、表示されるサブスクリプションリンクのフォールバックドメインとしても使われます — パネルとサブスクリプションが異なるドメイン(例: リバースプロキシの背後)でアクセスされる場合は「リバースプロキシURI」を設定してください。", "subUpdates": "更新間隔", "subUpdatesDesc": "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)", + "externalSubUserAgent": "外部サブスクリプションの User-Agent", + "externalSubUserAgentDesc": "クライアントの外部サブスクリプション URL を取得するときに送る User-Agent。空欄の場合はデフォルト(v2rayNG/1.8.5)を使用します。一部のプロバイダーは未知のクライアントを拒否します。", "subEncrypt": "エンコード", "subEncryptDesc": "サブスクリプションサービスが返す内容をBase64エンコードする", "subURI": "リバースプロキシURI", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index ed664eab0..9d49960f4 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs). Também é usado como domínio de fallback para o link de assinatura exibido quando \"URI de Proxy Reverso\" estiver vazio — configure \"URI de Proxy Reverso\" se o painel e a assinatura forem acessados por domínios diferentes (por exemplo, atrás de um proxy reverso).", "subUpdates": "Intervalos de Atualização", "subUpdatesDesc": "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)", + "externalSubUserAgent": "User-Agent de assinatura externa", + "externalSubUserAgentDesc": "Cabeçalho User-Agent ao buscar a URL de assinatura externa do cliente. Deixe em branco para usar o padrão (v2rayNG/1.8.5). Alguns provedores rejeitam clientes desconhecidos.", "subEncrypt": "Codificar", "subEncryptDesc": "O conteúdo retornado pelo serviço de assinatura será codificado em Base64.", "subURI": "URI de Proxy Reverso", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index c0fadaa9f..89e493f8e 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса. Также используется как домен по умолчанию для отображаемой ссылки подписки, если поле «URI обратного прокси» пустое — заполните «URI обратного прокси», если панель и подписка доступны на разных доменах (например, за reverse-proxy).", "subUpdates": "Интервалы обновления подписки", "subUpdatesDesc": "Интервал между обновлениями в клиентском приложении (в часах)", + "externalSubUserAgent": "User-Agent внешней подписки", + "externalSubUserAgentDesc": "Заголовок User-Agent при загрузке внешней подписки клиента. Оставьте пустым для значения по умолчанию (v2rayNG/1.8.5). Некоторые провайдеры отклоняют неизвестных клиентов.", "subEncrypt": "Кодировать", "subEncryptDesc": "Шифровать возвращенные конфиги в подписке", "subURI": "URI обратного прокси", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 08ec9e9aa..6e30365c3 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın). Ayrıca, \"Ters Proxy URI\" boşsa gösterilen abonelik bağlantısı için yedek alan adı olarak da kullanılır — panel ve abonelik farklı alan adları üzerinden erişiliyorsa (örneğin bir ters proxy arkasında) \"Ters Proxy URI\"yi ayarlayın.", "subUpdates": "Güncelleme Aralıkları", "subUpdatesDesc": "İstemci uygulamalarındaki abonelik URL'sinin güncellenme aralığı. (birim: saat)", + "externalSubUserAgent": "Harici abonelik User-Agent", + "externalSubUserAgentDesc": "Panel istemci harici abonelik URL’sini çekerken gönderilen User-Agent. Boş bırakılırsa varsayılan (v2rayNG/1.8.5) kullanılır. Bazı sağlayıcılar bilinmeyen istemcileri reddeder.", "subEncrypt": "Kodla", "subEncryptDesc": "Abonelik hizmetinin döndürülen içeriğini Base64 ile şifreler.", "subURI": "Ters Proxy URI", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index f15c4ea2e..733b6621f 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси). Також використовується як резервний домен для показаного посилання підписки, коли «URI зворотного проксі» порожнє — вкажіть «URI зворотного проксі», якщо панель і підписка доступні через різні домени (наприклад, за зворотним проксі).", "subUpdates": "Інтервали оновлення", "subUpdatesDesc": "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)", + "externalSubUserAgent": "User-Agent зовнішньої підписки", + "externalSubUserAgentDesc": "Заголовок User-Agent під час завантаження зовнішньої підписки клієнта. Залиште порожнім для значення за замовчуванням (v2rayNG/1.8.5). Деякі провайдери відхиляють невідомих клієнтів.", "subEncrypt": "Кодувати", "subEncryptDesc": "Повернений вміст послуги підписки матиме кодування Base64.", "subURI": "URI зворотного проксі", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index fb682d6bf..c4530ca4b 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "Mặc định để trống để nghe tất cả các tên miền và IP. Cũng được dùng làm tên miền dự phòng cho liên kết đăng ký hiển thị khi \"URI proxy trung gian\" để trống — hãy đặt \"URI proxy trung gian\" nếu bảng điều khiển và đăng ký được truy cập qua các tên miền khác nhau (ví dụ: đứng sau proxy trung gian).", "subUpdates": "Khoảng thời gian cập nhật gói đăng ký", "subUpdatesDesc": "Số giờ giữa các cập nhật trong ứng dụng khách", + "externalSubUserAgent": "User-Agent đăng ký bên ngoài", + "externalSubUserAgentDesc": "Header User-Agent khi panel tải URL đăng ký bên ngoài của client. Để trống để dùng mặc định (v2rayNG/1.8.5). Một số nhà cung cấp từ chối client lạ.", "subEncrypt": "Mã hóa", "subEncryptDesc": "Mã hóa các cấu hình được trả về trong gói đăng ký", "subURI": "URI proxy trung gian", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index 64fa3d658..1960277a8 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "订阅服务监听的域名(留空表示监听所有域名和 IP)。当「反向代理 URI」为空时,也会作为显示的订阅链接的回退域名——如果面板和订阅通过不同的域名访问(例如位于反向代理之后),请设置「反向代理 URI」。", "subUpdates": "更新间隔", "subUpdatesDesc": "客户端应用中订阅 URL 的更新间隔(单位:小时)", + "externalSubUserAgent": "外部订阅 User-Agent", + "externalSubUserAgentDesc": "面板拉取客户端外部订阅 URL 时发送的 User-Agent。留空则使用默认值(v2rayNG/1.8.5)。部分服务商会拒绝未知客户端。", "subEncrypt": "编码", "subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码", "subURI": "反向代理 URI", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index 76fb198c8..aaaf593e8 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -1273,6 +1273,8 @@ "subDomainDesc": "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)。當「反向代理 URI」為空時,也會作為顯示的訂閱連結的備援域名——如果面板和訂閱透過不同的域名存取(例如位於反向代理之後),請設定「反向代理 URI」。", "subUpdates": "更新間隔", "subUpdatesDesc": "客戶端應用中訂閱 URL 的更新間隔(單位:小時)", + "externalSubUserAgent": "外部訂閱 User-Agent", + "externalSubUserAgentDesc": "面板擷取用戶外部訂閱 URL 時傳送的 User-Agent。留空則使用預設值(v2rayNG/1.8.5)。部分服務商會拒絕未知用戶端。", "subEncrypt": "編碼", "subEncryptDesc": "訂閱服務返回的內容將採用 Base64 編碼", "subURI": "反向代理 URI",