From 2a98e7c1f00d004a4f0fe804f44df8667dca7259 Mon Sep 17 00:00:00 2001 From: nite Date: Sat, 23 May 2026 18:41:55 +1000 Subject: [PATCH] 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 --- README.md | 3 ++- api/handler/convert.go | 1 + cmd/convert.go | 6 ++++++ common/convert.go | 13 +++++++------ model/convert.go | 1 + util/fetch.go | 14 +++++++++++--- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3adc0df..484813e 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ "group-type": "selector", "sort": "name", "sort-type": "asc", - "output": "./config.json" + "output": "./config.json", + "user-agent": "自定义 User-Agent" } ``` diff --git a/api/handler/convert.go b/api/handler/convert.go index a737cca..d11d901 100755 --- a/api/handler/convert.go +++ b/api/handler/convert.go @@ -57,6 +57,7 @@ func Convert(c *gin.Context) { data.SortKey, data.SortType, groupRules, + data.UserAgent, ) if err != nil { c.JSON(400, gin.H{ diff --git a/cmd/convert.go b/cmd/convert.go index fe01348..1433c2a 100755 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -25,6 +25,7 @@ var ( sortType string config string groupRules string + userAgent string ) func init() { @@ -40,6 +41,7 @@ func init() { convertCmd.Flags().StringVarP(&sortType, "sort-type", "T", "", "sort type, asc or desc") convertCmd.Flags().StringVarP(&config, "config", "c", "", "configuration file path") 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) } @@ -71,6 +73,7 @@ func convertRun(cmd *cobra.Command, args []string) { sortKey, sortType, groupRulesMap, + userAgent, ) if err != nil { fmt.Println("Conversion error:", err) @@ -152,4 +155,7 @@ func mergeConfig(cfg model.ConvertRequest) { if output == "" { output = cfg.Output } + if userAgent == "" { + userAgent = cfg.UserAgent + } } diff --git a/common/convert.go b/common/convert.go index 1df7993..069b87d 100755 --- a/common/convert.go +++ b/common/convert.go @@ -36,6 +36,7 @@ func Convert( sortKey string, sortType string, groupRules map[string][]string, + userAgent string, ) (string, error) { result := "" var err error @@ -44,7 +45,7 @@ func Convert( groupType = C.TypeSelector } - outbounds, err := ConvertSubscriptionsToSProxy(subscriptions) + outbounds, err := ConvertSubscriptionsToSProxy(subscriptions, userAgent) if err != nil { return "", err } @@ -104,7 +105,7 @@ func Convert( outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType, groupRules) } if templatePath != "" { - templateData, err := ReadTemplate(templatePath) + templateData, err := ReadTemplate(templatePath, userAgent) if err != nil { return "", err } @@ -226,12 +227,12 @@ func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string, return append(proxies, groups...) } -func ReadTemplate(template string) (string, error) { +func ReadTemplate(template string, userAgent string) (string, error) { var data string var err error isNetworkFile, _ := regexp.MatchString(`^https?://`, template) if isNetworkFile { - data, err = util.Fetch(template, 3) + data, err = util.Fetch(template, 3, userAgent) if err != nil { return "", err } @@ -320,10 +321,10 @@ func ConvertCProxyToSProxy(proxy string) (model.Outbound, error) { 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) for _, url := range urls { - data, err := util.Fetch(url, 3) + data, err := util.Fetch(url, 3, userAgent) if err != nil { return nil, err } diff --git a/model/convert.go b/model/convert.go index 4b88c20..61ede15 100755 --- a/model/convert.go +++ b/model/convert.go @@ -12,4 +12,5 @@ type ConvertRequest struct { SortType string `form:"sort-type" json:"sort-type"` Output string `json:"output"` GroupRules string `form:"group-rules" json:"group-rules"` + UserAgent string `form:"user-agent" json:"user-agent"` } diff --git a/util/fetch.go b/util/fetch.go index 89f9eb1..d71b90c 100755 --- a/util/fetch.go +++ b/util/fetch.go @@ -5,23 +5,31 @@ import ( "net/http" ) -func Fetch(url string, maxRetryTimes int) (string, error) { +func Fetch(url string, maxRetryTimes int, userAgent string) (string, error) { retryTime := 0 var err error var resp *http.Response 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 { retryTime++ continue } var data []byte data, err = io.ReadAll(resp.Body) + resp.Body.Close() if err != nil { retryTime++ continue } - return string(data), err + return string(data), nil } return "", err }