1
0
mirror of https://github.com/bestnite/sub2sing-box.git synced 2026-06-08 16:04:43 +00:00

feat: add custom User-Agent support for subscription and template requests

Add --user-agent / -u CLI flag and user-agent config field to allow
customizing the User-Agent header when fetching subscriptions and
remote templates. This is useful for providers that require a specific
UA to access their subscription links.

Closes #23
This commit is contained in:
2026-05-23 18:41:55 +10:00
parent b63662347a
commit 2a98e7c1f0
6 changed files with 28 additions and 10 deletions
+2 -1
View File
@@ -20,7 +20,8 @@
"group-type": "selector", "group-type": "selector",
"sort": "name", "sort": "name",
"sort-type": "asc", "sort-type": "asc",
"output": "./config.json" "output": "./config.json",
"user-agent": "自定义 User-Agent"
} }
``` ```
+1
View File
@@ -57,6 +57,7 @@ func Convert(c *gin.Context) {
data.SortKey, data.SortKey,
data.SortType, data.SortType,
groupRules, groupRules,
data.UserAgent,
) )
if err != nil { if err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
+6
View File
@@ -25,6 +25,7 @@ var (
sortType string sortType string
config string config string
groupRules string groupRules string
userAgent string
) )
func init() { func init() {
@@ -40,6 +41,7 @@ func init() {
convertCmd.Flags().StringVarP(&sortType, "sort-type", "T", "", "sort type, asc or desc") convertCmd.Flags().StringVarP(&sortType, "sort-type", "T", "", "sort type, asc or desc")
convertCmd.Flags().StringVarP(&config, "config", "c", "", "configuration file path") convertCmd.Flags().StringVarP(&config, "config", "c", "", "configuration file path")
convertCmd.Flags().StringVarP(&groupRules, "group-rules", "R", "", "group rules") convertCmd.Flags().StringVarP(&groupRules, "group-rules", "R", "", "group rules")
convertCmd.Flags().StringVarP(&userAgent, "user-agent", "u", "", "custom User-Agent for fetching subscriptions and remote templates")
RootCmd.AddCommand(convertCmd) RootCmd.AddCommand(convertCmd)
} }
@@ -71,6 +73,7 @@ func convertRun(cmd *cobra.Command, args []string) {
sortKey, sortKey,
sortType, sortType,
groupRulesMap, groupRulesMap,
userAgent,
) )
if err != nil { if err != nil {
fmt.Println("Conversion error:", err) fmt.Println("Conversion error:", err)
@@ -152,4 +155,7 @@ func mergeConfig(cfg model.ConvertRequest) {
if output == "" { if output == "" {
output = cfg.Output output = cfg.Output
} }
if userAgent == "" {
userAgent = cfg.UserAgent
}
} }
+7 -6
View File
@@ -36,6 +36,7 @@ func Convert(
sortKey string, sortKey string,
sortType string, sortType string,
groupRules map[string][]string, groupRules map[string][]string,
userAgent string,
) (string, error) { ) (string, error) {
result := "" result := ""
var err error var err error
@@ -44,7 +45,7 @@ func Convert(
groupType = C.TypeSelector groupType = C.TypeSelector
} }
outbounds, err := ConvertSubscriptionsToSProxy(subscriptions) outbounds, err := ConvertSubscriptionsToSProxy(subscriptions, userAgent)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -104,7 +105,7 @@ func Convert(
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType, groupRules) outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType, groupRules)
} }
if templatePath != "" { if templatePath != "" {
templateData, err := ReadTemplate(templatePath) templateData, err := ReadTemplate(templatePath, userAgent)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -226,12 +227,12 @@ func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string,
return append(proxies, groups...) return append(proxies, groups...)
} }
func ReadTemplate(template string) (string, error) { func ReadTemplate(template string, userAgent string) (string, error) {
var data string var data string
var err error var err error
isNetworkFile, _ := regexp.MatchString(`^https?://`, template) isNetworkFile, _ := regexp.MatchString(`^https?://`, template)
if isNetworkFile { if isNetworkFile {
data, err = util.Fetch(template, 3) data, err = util.Fetch(template, 3, userAgent)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -320,10 +321,10 @@ func ConvertCProxyToSProxy(proxy string) (model.Outbound, error) {
return model.Outbound{}, errors.New("unknown proxy format") return model.Outbound{}, errors.New("unknown proxy format")
} }
func ConvertSubscriptionsToSProxy(urls []string) ([]model.Outbound, error) { func ConvertSubscriptionsToSProxy(urls []string, userAgent string) ([]model.Outbound, error) {
proxyList := make([]model.Outbound, 0) proxyList := make([]model.Outbound, 0)
for _, url := range urls { for _, url := range urls {
data, err := util.Fetch(url, 3) data, err := util.Fetch(url, 3, userAgent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1
View File
@@ -12,4 +12,5 @@ type ConvertRequest struct {
SortType string `form:"sort-type" json:"sort-type"` SortType string `form:"sort-type" json:"sort-type"`
Output string `json:"output"` Output string `json:"output"`
GroupRules string `form:"group-rules" json:"group-rules"` GroupRules string `form:"group-rules" json:"group-rules"`
UserAgent string `form:"user-agent" json:"user-agent"`
} }
+11 -3
View File
@@ -5,23 +5,31 @@ import (
"net/http" "net/http"
) )
func Fetch(url string, maxRetryTimes int) (string, error) { func Fetch(url string, maxRetryTimes int, userAgent string) (string, error) {
retryTime := 0 retryTime := 0
var err error var err error
var resp *http.Response var resp *http.Response
for retryTime < maxRetryTimes { for retryTime < maxRetryTimes {
resp, err = http.Get(url) req, reqErr := http.NewRequest("GET", url, nil)
if reqErr != nil {
return "", reqErr
}
if userAgent != "" {
req.Header.Set("User-Agent", userAgent)
}
resp, err = http.DefaultClient.Do(req)
if err != nil { if err != nil {
retryTime++ retryTime++
continue continue
} }
var data []byte var data []byte
data, err = io.ReadAll(resp.Body) data, err = io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil { if err != nil {
retryTime++ retryTime++
continue continue
} }
return string(data), err return string(data), nil
} }
return "", err return "", err
} }