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">
+
+ {{ i18n "pages.settings.subAdditionalURIs"}}
+ {{ i18n
+ "pages.settings.subAdditionalURIsDesc"}}
+
+
+
+
@@ -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 информации о внешнем трафике"