From 13dec8f952308d361bf7a14de93d9f2c4da59c27 Mon Sep 17 00:00:00 2001 From: Vladislav Kasperov Date: Sun, 3 May 2026 13:37:49 +0300 Subject: [PATCH] Add additional subscription base URLs support --- sub/subService.go | 44 +++++++++++++++++++ web/assets/js/model/setting.js | 3 +- web/entity/entity.go | 1 + .../settings/panel/subscription/general.html | 12 ++++- web/service/setting.go | 5 +++ web/translation/translate.en_US.toml | 2 + web/translation/translate.ru_RU.toml | 2 + 7 files changed, 67 insertions(+), 2 deletions(-) diff --git a/sub/subService.go b/sub/subService.go index 19ca21d9..1634bb1d 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -3,8 +3,10 @@ package sub import ( "encoding/base64" "fmt" + "io" "maps" "net" + "net/http" "net/url" "slices" "strings" @@ -88,6 +90,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C } } } + result = append(result, s.getAdditionalSubs(subId)...) // Prepare statistics for index, clientTraffic := range clientTraffics { @@ -114,6 +117,47 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C return result, lastOnline, traffic, nil } +func (s *SubService) getAdditionalSubs(subID string) []string { + additionalURIs, err := s.settingService.GetSubAdditionalURIs() + if err != nil || strings.TrimSpace(additionalURIs) == "" { + return nil + } + client := &http.Client{Timeout: 8 * time.Second} + var result []string + for _, baseURI := range strings.Split(additionalURIs, ",") { + baseURI = strings.TrimSpace(baseURI) + if baseURI == "" { + continue + } + if !strings.HasSuffix(baseURI, "/") { + baseURI += "/" + } + remoteSubURL := baseURI + subID + resp, reqErr := client.Get(remoteSubURL) + if reqErr != nil { + logger.Warningf("SubService - additional sub request failed for %s: %v", remoteSubURL, reqErr) + continue + } + body, readErr := io.ReadAll(resp.Body) + resp.Body.Close() + if readErr != nil || resp.StatusCode != http.StatusOK { + continue + } + rawBody := strings.TrimSpace(string(body)) + decoded, decodeErr := base64.StdEncoding.DecodeString(rawBody) + if decodeErr == nil { + rawBody = string(decoded) + } + for _, line := range strings.Split(rawBody, "\n") { + line = strings.TrimSpace(line) + if line != "" { + result = append(result, line) + } + } + } + return result +} + func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index d61d4b8e..ea37cd1e 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -49,6 +49,7 @@ class AllSetting { this.subEncrypt = true; this.subShowInfo = true; this.subURI = ""; + this.subAdditionalURIs = ""; this.subJsonURI = ""; this.subClashURI = ""; this.subJsonFragment = ""; @@ -89,4 +90,4 @@ class AllSetting { equals(other) { return ObjectUtil.equals(this, other); } -} \ No newline at end of file +} diff --git a/web/entity/entity.go b/web/entity/entity.go index 7f37f564..6dea553b 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -74,6 +74,7 @@ type AllSetting struct { SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` // Encrypt subscription responses SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` // Show client information in subscriptions SubURI string `json:"subURI" form:"subURI"` // Subscription server URI + SubAdditionalURIs string `json:"subAdditionalURIs" form:"subAdditionalURIs"` // Comma-separated list of additional subscription base URLs SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI SubClashEnable bool `json:"subClashEnable" form:"subClashEnable"` // Enable Clash/Mihomo subscription endpoint diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html index 725a9359..d10cc113 100644 --- a/web/html/settings/panel/subscription/general.html +++ b/web/html/settings/panel/subscription/general.html @@ -72,6 +72,16 @@ v-model="allSetting.subURI"> + + + + + @@ -176,4 +186,4 @@ -{{end}} \ No newline at end of file +{{end}} diff --git a/web/service/setting.go b/web/service/setting.go index 560dce3a..1463ae08 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -69,6 +69,7 @@ var defaultValueMap = map[string]string{ "subEncrypt": "true", "subShowInfo": "true", "subURI": "", + "subAdditionalURIs": "", "subJsonPath": "/json/", "subJsonURI": "", "subClashEnable": "true", @@ -564,6 +565,10 @@ func (s *SettingService) GetSubJsonURI() (string, error) { return s.getString("subJsonURI") } +func (s *SettingService) GetSubAdditionalURIs() (string, error) { + return s.getString("subAdditionalURIs") +} + func (s *SettingService) GetSubClashEnable() (bool, error) { return s.getBool("subClashEnable") } diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 49c9f952..21798a23 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -473,6 +473,8 @@ "subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." "subURI" = "Reverse Proxy URI" "subURIDesc" = "The URI path of the subscription URL for use behind proxies." +"subAdditionalURIs" = "Extra Subscription Base URLs" +"subAdditionalURIsDesc" = "Comma-separated base URLs (e.g. https://x2.example.com:2096/sbp/). They work only when the same subscription ID exists on that server." "externalTrafficInformEnable" = "External Traffic Inform" "externalTrafficInformEnableDesc" = "Inform external API on every traffic update." "externalTrafficInformURI" = "External Traffic Inform URI" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index b3ec617d..c7e09f28 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -473,6 +473,8 @@ "subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации" "subURI" = "URI обратного прокси" "subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" +"subAdditionalURIs" = "Дополнительные базовые URL подписки" +"subAdditionalURIsDesc" = "Базовые URL через запятую (например, https://x2.example.com:2096/sbp/). Работает только если на удалённом сервере существует подписка с тем же SubID." "externalTrafficInformEnable" = "Информация о внешнем трафике" "externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика" "externalTrafficInformURI" = "URI информации о внешнем трафике"