package surfboard import ( "bytes" "embed" "fmt" "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/tool" "github.com/perfect-panel/server/pkg/traffic" ) //go:embed *.tpl var configFiles embed.FS var shadowsocksSupportMethod = []string{"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"} func BuildSurfboard(servers proxy.Adapter, siteName string, user UserInfo) []byte { var proxies, proxyGroup string var removed []string for _, node := range servers.Proxies { if uri := buildProxy(node, user.UUID); uri != "" { proxies += uri } else { removed = append(removed, node.Name) } } for _, group := range servers.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) } } var rules string for _, rule := range servers.Rules { if rule == "" { continue } rules += rule + "\r\n" } //final rule rules += "FINAL, 手动选择" 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 surfboard config error: %v", err.Error()) return nil } return buf.Bytes() } func buildProxy(data proxy.Proxy, uuid string) string { var p string switch data.Protocol { case "vmess": p = buildVMess(data, uuid) case "shadowsocks": if !tool.Contains(shadowsocksSupportMethod, data.Option.(proxy.Shadowsocks).Method) { return "" } p = buildShadowsocks(data, uuid) case "trojan": p = buildTrojan(data, uuid) } return p }