package surge import ( "bytes" "embed" "fmt" "github.com/perfect-panel/server/pkg/tool" "net/url" "strings" "text/template" "time" "github.com/perfect-panel/server/pkg/adapter/proxy" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/traffic" ) //go:embed *.tpl var configFiles embed.FS type UserInfo struct { UUID string Upload int64 Download int64 TotalTraffic int64 ExpiredDate time.Time SubscribeURL string } type Surge struct { Adapter proxy.Adapter UUID string User UserInfo } func NewSurge(adapter proxy.Adapter) *Surge { return &Surge{ Adapter: adapter, } } func (m *Surge) Build(uuid, siteName string, user UserInfo) []byte { var proxies, proxyGroup, rules string var removed []string for _, p := range m.Adapter.Proxies { switch p.Protocol { case "shadowsocks": proxies += buildShadowsocks(p, uuid) case "trojan": proxies += buildTrojan(p, uuid) case "hysteria2": proxies += buildHysteria2(p, uuid) case "vmess": proxies += buildVMess(p, uuid) default: removed = append(removed, p.Name) } } for _, group := range m.Adapter.Group { if len(removed) > 0 { group.Proxies = tool.RemoveStringElement(group.Proxies, removed...) } if group.Type == proxy.GroupTypeSelect { proxyGroup += fmt.Sprintf("%s = select, %s", group.Name, strings.Join(group.Proxies, ", ")) + "\r\n" } else if group.Type == proxy.GroupTypeURLTest { proxyGroup += fmt.Sprintf("%s = url-test, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" } else if group.Type == proxy.GroupTypeFallback { proxyGroup += fmt.Sprintf("%s = fallback, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" } else { logger.Errorf("[BuildSurfboard] unknown group type: %s", group.Type) } } for _, rule := range m.Adapter.Rules { if rule == "" { continue } rules += rule + "\r\n" } //final rule rules += "\r\n" + "FINAL,手动选择,dns-failed" file, err := configFiles.ReadFile("default.tpl") if err != nil { logger.Errorf("read default surfboard config error: %v", err.Error()) return nil } // replace template tpl, err := template.New("default").Parse(string(file)) if err != nil { logger.Errorf("read default surfboard config error: %v", err.Error()) return nil } var buf bytes.Buffer var expiredAt string if user.ExpiredDate.Before(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) { expiredAt = "长期有效" } else { expiredAt = user.ExpiredDate.Format("2006-01-02 15:04:05") } // convert traffic upload := traffic.AutoConvert(user.Upload, false) download := traffic.AutoConvert(user.Download, false) total := traffic.AutoConvert(user.TotalTraffic, false) unusedTraffic := traffic.AutoConvert(user.TotalTraffic-user.Upload-user.Download, false) // query Host urlParse, err := url.Parse(user.SubscribeURL) if err != nil { return nil } if err := tpl.Execute(&buf, map[string]interface{}{ "Proxies": proxies, "ProxyGroup": proxyGroup, "SubscribeURL": user.SubscribeURL, "SubscribeInfo": fmt.Sprintf("title=%s订阅信息, content=上传流量:%s\\n下载流量:%s\\n剩余流量: %s\\n套餐流量:%s\\n到期时间:%s", siteName, upload, download, unusedTraffic, total, expiredAt), "SubscribeDomain": urlParse.Host, "Rules": rules, }); err != nil { logger.Errorf("build Surge config error: %v", err.Error()) return nil } return buf.Bytes() }