210 lines
5.0 KiB
Go
210 lines
5.0 KiB
Go
package protocol
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"xboard-go/internal/model"
|
|
"xboard-go/internal/service"
|
|
|
|
"github.com/goccy/go-yaml"
|
|
)
|
|
|
|
func GenerateClash(servers []model.Server, user model.User) (string, error) {
|
|
return GenerateClashWithTemplate("clash", servers, user)
|
|
}
|
|
|
|
func GenerateClashWithTemplate(templateName string, servers []model.Server, user model.User) (string, error) {
|
|
template := strings.TrimSpace(service.GetSubscribeTemplate(templateName))
|
|
if template == "" {
|
|
return generateClashFallback(servers, user), nil
|
|
}
|
|
|
|
var config map[string]any
|
|
if err := yaml.Unmarshal([]byte(template), &config); err != nil {
|
|
return generateClashFallback(servers, user), nil
|
|
}
|
|
|
|
proxies := make([]any, 0, len(servers))
|
|
proxyNames := make([]string, 0, len(servers))
|
|
for _, s := range servers {
|
|
conf := service.BuildNodeConfig(&s)
|
|
proxy := buildClashProxy(conf, user)
|
|
if proxy == nil {
|
|
continue
|
|
}
|
|
proxies = append(proxies, proxy)
|
|
proxyNames = append(proxyNames, conf.Name)
|
|
}
|
|
|
|
config["proxies"] = append(anySlice(config["proxies"]), proxies...)
|
|
config["proxy-groups"] = mergeClashProxyGroups(anySlice(config["proxy-groups"]), proxyNames)
|
|
|
|
if _, ok := config["rules"]; !ok {
|
|
config["rules"] = []any{"MATCH,Proxy"}
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
output := strings.ReplaceAll(string(data), "$app_name", service.MustGetString("app_name", "XBoard"))
|
|
return output, nil
|
|
}
|
|
|
|
func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]any {
|
|
switch conf.Protocol {
|
|
case "shadowsocks":
|
|
cipher, _ := conf.Cipher.(string)
|
|
return map[string]any{
|
|
"name": conf.Name,
|
|
"type": "ss",
|
|
"server": conf.RawHost,
|
|
"port": conf.ServerPort,
|
|
"cipher": cipher,
|
|
"password": user.UUID,
|
|
}
|
|
case "vmess":
|
|
return map[string]any{
|
|
"name": conf.Name,
|
|
"type": "vmess",
|
|
"server": conf.RawHost,
|
|
"port": conf.ServerPort,
|
|
"uuid": user.UUID,
|
|
"alterId": 0,
|
|
"cipher": "auto",
|
|
"udp": true,
|
|
}
|
|
case "trojan":
|
|
return map[string]any{
|
|
"name": conf.Name,
|
|
"type": "trojan",
|
|
"server": conf.RawHost,
|
|
"port": conf.ServerPort,
|
|
"password": user.UUID,
|
|
"udp": true,
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func mergeClashProxyGroups(groups []any, proxyNames []string) []any {
|
|
if len(groups) == 0 {
|
|
return []any{
|
|
map[string]any{
|
|
"name": "Proxy",
|
|
"type": "select",
|
|
"proxies": append([]any{"DIRECT"}, stringsToAny(proxyNames)...),
|
|
},
|
|
}
|
|
}
|
|
|
|
for index, item := range groups {
|
|
group, ok := item.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
group["proxies"] = appendUniqueAny(anySlice(group["proxies"]), stringsToAny(proxyNames)...)
|
|
groups[index] = group
|
|
}
|
|
return groups
|
|
}
|
|
|
|
func anySlice(value any) []any {
|
|
switch typed := value.(type) {
|
|
case nil:
|
|
return []any{}
|
|
case []any:
|
|
return append([]any{}, typed...)
|
|
case []string:
|
|
result := make([]any, 0, len(typed))
|
|
for _, item := range typed {
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
default:
|
|
return []any{}
|
|
}
|
|
}
|
|
|
|
func stringsToAny(values []string) []any {
|
|
result := make([]any, 0, len(values))
|
|
for _, value := range values {
|
|
result = append(result, value)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func appendUniqueAny(base []any, values ...any) []any {
|
|
existing := make(map[string]struct{}, len(base))
|
|
for _, item := range base {
|
|
existing[fmt.Sprint(item)] = struct{}{}
|
|
}
|
|
|
|
for _, item := range values {
|
|
key := fmt.Sprint(item)
|
|
if _, ok := existing[key]; ok {
|
|
continue
|
|
}
|
|
base = append(base, item)
|
|
existing[key] = struct{}{}
|
|
}
|
|
return base
|
|
}
|
|
|
|
func generateClashFallback(servers []model.Server, user model.User) string {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteString("proxies:\n")
|
|
var proxyNames []string
|
|
for _, s := range servers {
|
|
conf := service.BuildNodeConfig(&s)
|
|
proxy := buildClashProxy(conf, user)
|
|
if proxy == nil {
|
|
continue
|
|
}
|
|
|
|
switch proxy["type"] {
|
|
case "ss":
|
|
builder.WriteString(fmt.Sprintf(
|
|
" - name: \"%s\"\n type: ss\n server: %s\n port: %d\n cipher: %s\n password: %s\n",
|
|
conf.Name,
|
|
conf.RawHost,
|
|
conf.ServerPort,
|
|
fmt.Sprint(proxy["cipher"]),
|
|
user.UUID,
|
|
))
|
|
case "vmess":
|
|
builder.WriteString(fmt.Sprintf(
|
|
" - name: \"%s\"\n type: vmess\n server: %s\n port: %d\n uuid: %s\n alterId: 0\n cipher: auto\n udp: true\n",
|
|
conf.Name,
|
|
conf.RawHost,
|
|
conf.ServerPort,
|
|
user.UUID,
|
|
))
|
|
case "trojan":
|
|
builder.WriteString(fmt.Sprintf(
|
|
" - name: \"%s\"\n type: trojan\n server: %s\n port: %d\n password: %s\n udp: true\n",
|
|
conf.Name,
|
|
conf.RawHost,
|
|
conf.ServerPort,
|
|
user.UUID,
|
|
))
|
|
}
|
|
proxyNames = append(proxyNames, fmt.Sprintf("\"%s\"", conf.Name))
|
|
}
|
|
|
|
builder.WriteString("\nproxy-groups:\n")
|
|
builder.WriteString(" - name: Proxy\n type: select\n proxies:\n - DIRECT\n")
|
|
for _, name := range proxyNames {
|
|
builder.WriteString(" - " + name + "\n")
|
|
}
|
|
|
|
builder.WriteString("\nrules:\n")
|
|
builder.WriteString(" - MATCH,Proxy\n")
|
|
|
|
return builder.String()
|
|
}
|