Files
subconverter-go/internal/generator/surge.go
Rogee 7fcabe0225
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
first commit
2025-09-28 10:05:07 +08:00

490 lines
15 KiB
Go

package generator
import (
"fmt"
"sort"
"strings"
"github.com/subconverter-go/internal/logging"
"github.com/subconverter-go/internal/parser"
)
// SurgeGenerator Surge格式生成器
// 实现Surge代理配置的生成功能
type SurgeGenerator struct {
logger *logging.Logger
}
// NewSurgeGenerator 创建新的Surge生成器
// 返回初始化好的SurgeGenerator实例
func NewSurgeGenerator(logger *logging.Logger) *SurgeGenerator {
return &SurgeGenerator{
logger: logger,
}
}
// Generate 生成Surge配置
func (g *SurgeGenerator) Generate(configs []*parser.ProxyConfig, options *GenerationOptions) (string, error) {
g.logger.Debugf("Generating Surge configuration")
// 生成Surge配置文本
var builder strings.Builder
// 生成头部信息
g.generateHeader(&builder, options)
// 生成代理提供者
g.generateProviders(&builder, options)
// 生成代理配置
g.generateProxies(&builder, configs)
// 生成代理组
g.generateProxyGroups(&builder, configs, options)
// 生成规则
g.generateRules(&builder, options)
return builder.String(), nil
}
// ValidateOptions 验证Surge生成选项
func (g *SurgeGenerator) ValidateOptions(options *GenerationOptions) error {
g.logger.Debugf("Validating Surge generation options")
// 验证基本信息
if options.Name == "" {
return fmt.Errorf("configuration name is required")
}
// 验证端口
if options.MixedPort < 0 || options.MixedPort > 65535 {
return fmt.Errorf("invalid mixed port: %d", options.MixedPort)
}
return nil
}
// GetSupportedFormats 获取支持的格式
func (g *SurgeGenerator) GetSupportedFormats() []string {
return []string{"surge"}
}
// generateHeader 生成配置头部信息
func (g *SurgeGenerator) generateHeader(builder *strings.Builder, options *GenerationOptions) {
builder.WriteString("# Surge Configuration\n")
builder.WriteString("# Generated by SubConverter-Go\n")
builder.WriteString(fmt.Sprintf("# Name: %s\n\n", options.Name))
// 基本配置
builder.WriteString("[General]\n")
builder.WriteString("skip-proxy = 127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12\n")
if options.MixedPort > 0 {
builder.WriteString(fmt.Sprintf("http-port = %d\n", options.MixedPort))
builder.WriteString(fmt.Sprintf("socks5-port = %d\n", options.MixedPort+1))
}
builder.WriteString("allow-wifi-access = false\n")
builder.WriteString("wifi-access-http-port = 6152\n")
builder.WriteString("wifi-access-socks5-port = 6153\n")
if options.IPv6 {
builder.WriteString("ipv6 = true\n")
} else {
builder.WriteString("ipv6 = false\n")
}
builder.WriteString("prefer-ipv6 = false\n")
builder.WriteString("dns-server = 119.29.29.29, 223.5.5.5, 1.1.1.1, 8.8.8.8\n")
builder.WriteString("fallback-dns-server = 223.5.5.5, 119.29.29.29\n")
builder.WriteString("exclude-simple-hostnames = true\n")
builder.WriteString("show-error-page-for-reject = true\n")
builder.WriteString("always-real-ip = true\n")
if options.EnableLan {
builder.WriteString("enhanced-mode-by-rule = true\n")
} else {
builder.WriteString("enhanced-mode-by-rule = false\n")
}
builder.WriteString("\n")
}
func (g *SurgeGenerator) generateProviders(builder *strings.Builder, options *GenerationOptions) {
providers := g.resolveProviders(options)
if len(providers) == 0 {
return
}
builder.WriteString("[Proxy Provider]\n")
for _, def := range providers {
builder.WriteString(g.formatProviderLine(def))
builder.WriteString("\n")
}
builder.WriteString("\n")
}
// generateProxies 生成代理配置
func (g *SurgeGenerator) generateProxies(builder *strings.Builder, configs []*parser.ProxyConfig) {
builder.WriteString("[Proxy]\n")
for i, config := range configs {
proxy := g.convertProxyConfig(config)
builder.WriteString(proxy)
if i < len(configs)-1 {
builder.WriteString("\n")
}
}
builder.WriteString("\n")
}
// convertProxyConfig 转换代理配置为Surge格式
func (g *SurgeGenerator) convertProxyConfig(proxyConfig *parser.ProxyConfig) string {
switch proxyConfig.Protocol {
case "ss":
return g.generateSSProxy(proxyConfig)
case "ssr":
return g.generateSSRProxy(proxyConfig)
case "vmess":
return g.generateVMessProxy(proxyConfig)
case "trojan":
return g.generateTrojanProxy(proxyConfig)
case "http", "https":
return g.generateHTTPProxy(proxyConfig)
case "socks5":
return g.generateSocks5Proxy(proxyConfig)
default:
return ""
}
}
// generateSSProxy 生成Shadowsocks代理配置
func (g *SurgeGenerator) generateSSProxy(config *parser.ProxyConfig) string {
method, _ := config.Settings["method"].(string)
password, _ := config.Settings["password"].(string)
plugin, _ := config.Settings["plugin"].(string)
if plugin != "" {
return fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s, plugin=%s, plugin-opts=%s",
config.Name, config.Server, config.Port, method, password, plugin, g.generatePluginOpts(config))
}
return fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s",
config.Name, config.Server, config.Port, method, password)
}
// generateSSRProxy 生成ShadowsocksR代理配置
func (g *SurgeGenerator) generateSSRProxy(config *parser.ProxyConfig) string {
method, _ := config.Settings["method"].(string)
password, _ := config.Settings["password"].(string)
protocol, _ := config.Settings["protocol"].(string)
protocolParam, _ := config.Settings["protocol-param"].(string)
obfs, _ := config.Settings["obfs"].(string)
obfsParam, _ := config.Settings["obfs-param"].(string)
return fmt.Sprintf("%s = ssr, %s, %d, encrypt-method=%s, password=%s, protocol=%s, protocol-param=%s, obfs=%s, obfs-param=%s",
config.Name, config.Server, config.Port, method, password, protocol, protocolParam, obfs, obfsParam)
}
// generateVMessProxy 生成VMess代理配置
func (g *SurgeGenerator) generateVMessProxy(config *parser.ProxyConfig) string {
uuid, _ := config.Settings["uuid"].(string)
alterId, _ := config.Settings["alterId"].(int)
network, _ := config.Settings["network"].(string)
tls, _ := config.Settings["tls"].(string)
host, _ := config.Settings["host"].(string)
path, _ := config.Settings["path"].(string)
var opts []string
opts = append(opts, fmt.Sprintf("uuid=%s", uuid))
opts = append(opts, fmt.Sprintf("alterId=%d", alterId))
if network != "" {
opts = append(opts, fmt.Sprintf("network=%s", network))
}
if tls != "" {
opts = append(opts, fmt.Sprintf("tls=%s", tls))
}
if host != "" {
opts = append(opts, fmt.Sprintf("ws-host=%s", host))
}
if path != "" {
opts = append(opts, fmt.Sprintf("ws-path=%s", path))
}
return fmt.Sprintf("%s = vmess, %s, %d, %s",
config.Name, config.Server, config.Port, strings.Join(opts, ", "))
}
// generateTrojanProxy 生成Trojan代理配置
func (g *SurgeGenerator) generateTrojanProxy(config *parser.ProxyConfig) string {
password, _ := config.Settings["password"].(string)
sni, _ := config.Settings["sni"].(string)
network, _ := config.Settings["network"].(string)
var opts []string
opts = append(opts, fmt.Sprintf("password=%s", password))
if sni != "" {
opts = append(opts, fmt.Sprintf("sni=%s", sni))
}
if network != "" {
opts = append(opts, fmt.Sprintf("network=%s", network))
}
return fmt.Sprintf("%s = trojan, %s, %d, %s",
config.Name, config.Server, config.Port, strings.Join(opts, ", "))
}
// generateHTTPProxy 生成HTTP代理配置
func (g *SurgeGenerator) generateHTTPProxy(config *parser.ProxyConfig) string {
username, _ := config.Settings["username"].(string)
password, _ := config.Settings["password"].(string)
tls, _ := config.Settings["tls"].(bool)
if username != "" && password != "" {
if tls {
return fmt.Sprintf("%s = https, %s, %d, username=%s, password=%s",
config.Name, config.Server, config.Port, username, password)
}
return fmt.Sprintf("%s = http, %s, %d, username=%s, password=%s",
config.Name, config.Server, config.Port, username, password)
}
if tls {
return fmt.Sprintf("%s = https, %s, %d",
config.Name, config.Server, config.Port)
}
return fmt.Sprintf("%s = http, %s, %d",
config.Name, config.Server, config.Port)
}
// generateSocks5Proxy 生成Socks5代理配置
func (g *SurgeGenerator) generateSocks5Proxy(config *parser.ProxyConfig) string {
username, _ := config.Settings["username"].(string)
password, _ := config.Settings["password"].(string)
if username != "" && password != "" {
return fmt.Sprintf("%s = socks5, %s, %d, username=%s, password=%s",
config.Name, config.Server, config.Port, username, password)
}
return fmt.Sprintf("%s = socks5, %s, %d",
config.Name, config.Server, config.Port)
}
// generatePluginOpts 生成插件选项
func (g *SurgeGenerator) generatePluginOpts(config *parser.ProxyConfig) string {
// 这里可以根据具体插件生成相应的选项
return ""
}
// generateProxyGroups 生成代理组
func (g *SurgeGenerator) generateProxyGroups(builder *strings.Builder, configs []*parser.ProxyConfig, options *GenerationOptions) {
builder.WriteString("[Proxy Group]\n")
// 创建代理名称列表
proxyNames := make([]string, 0)
for _, config := range configs {
proxyNames = append(proxyNames, config.Name)
}
// 选择代理组
builder.WriteString(fmt.Sprintf("Proxy = select, %s\n", strings.Join(proxyNames, ", ")))
// URL测试代理组
if options.ProxyTest {
testURL := options.ProxyURL
if testURL == "" {
testURL = "http://www.gstatic.com/generate_204"
}
builder.WriteString(fmt.Sprintf("URL-Test = url-test, %s, url=%s, interval=300\n",
strings.Join(proxyNames, ", "), testURL))
}
// 故障转移代理组
builder.WriteString(fmt.Sprintf("Fallback = fallback, %s, url=http://www.gstatic.com/generate_204, interval=300\n",
strings.Join(proxyNames, ", ")))
// 直连和拒绝代理组
builder.WriteString("Direct = select, DIRECT\n")
builder.WriteString("Reject = select, REJECT\n")
if len(options.GroupDefinitions) > 0 {
for _, def := range options.GroupDefinitions {
builder.WriteString(g.formatCustomGroup(def, proxyNames))
builder.WriteString("\n")
}
}
builder.WriteString("\n")
}
// generateRules 生成规则
func (g *SurgeGenerator) generateRules(builder *strings.Builder, options *GenerationOptions) {
builder.WriteString("[Rule]\n")
// 添加默认规则
if len(options.Rules) > 0 {
builder.WriteString(strings.Join(options.Rules, "\n"))
} else {
// 添加默认规则集
defaultRules := []string{
"DOMAIN-SUFFIX,bilibili.com,DIRECT",
"DOMAIN-SUFFIX,baidu.com,DIRECT",
"DOMAIN-SUFFIX,cn,DIRECT",
"DOMAIN-KEYWORD,google,Proxy",
"DOMAIN-KEYWORD,github,Proxy",
"DOMAIN-KEYWORD,twitter,Proxy",
"DOMAIN-KEYWORD,facebook,Proxy",
"DOMAIN-KEYWORD,youtube,Proxy",
"DOMAIN-KEYWORD,instagram,Proxy",
"DOMAIN-KEYWORD,telegram,Proxy",
"GEOIP,CN,DIRECT",
"IP-CIDR,127.0.0.0/8,DIRECT",
"IP-CIDR,192.168.0.0/16,DIRECT",
"IP-CIDR,10.0.0.0/8,DIRECT",
"IP-CIDR,172.16.0.0/12,DIRECT",
"FINAL,Proxy",
}
builder.WriteString(strings.Join(defaultRules, "\n"))
}
// 启用IPv6支持
if options.IPv6 {
builder.WriteString("\n# IPv6 Rules\n")
builder.WriteString("IP-CIDR6,::1/128,DIRECT\n")
builder.WriteString("IP-CIDR6,fc00::/7,DIRECT\n")
}
// 启用局域网支持
if options.EnableLan {
builder.WriteString("\n# LAN Rules\n")
builder.WriteString("SRC-IP-CIDR,192.168.0.0/16,DIRECT\n")
builder.WriteString("SRC-IP-CIDR,10.0.0.0/8,DIRECT\n")
builder.WriteString("SRC-IP-CIDR,172.16.0.0/12,DIRECT\n")
}
}
func (g *SurgeGenerator) resolveProviders(options *GenerationOptions) []*ProviderDefinition {
if len(options.Providers) > 0 {
return options.Providers
}
if len(options.CustomProviders) == 0 {
return nil
}
providers := make([]*ProviderDefinition, 0, len(options.CustomProviders))
for _, entry := range options.CustomProviders {
parsed, err := ParseProviderDefinition(entry)
if err != nil {
g.logger.WithError(err).Warnf("Failed to parse custom provider: %s", entry)
continue
}
providers = append(providers, parsed)
}
return providers
}
func (g *SurgeGenerator) formatProviderLine(def *ProviderDefinition) string {
parts := []string{strings.ToLower(def.Type), def.URL}
parts = append(parts, fmt.Sprintf("path=%s", def.Path))
if def.Interval != nil {
parts = append(parts, fmt.Sprintf("interval=%d", *def.Interval))
}
if len(def.Flags) > 0 {
keys := make([]string, 0, len(def.Flags))
for k := range def.Flags {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%t", k, def.Flags[k]))
}
}
if len(def.Fields) > 0 {
keys := make([]string, 0, len(def.Fields))
for k := range def.Fields {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%s", k, def.Fields[k]))
}
}
if def.Health != nil {
if def.Health.Enable != nil {
parts = append(parts, fmt.Sprintf("health-check=%t", *def.Health.Enable))
}
if def.Health.URL != "" {
parts = append(parts, fmt.Sprintf("health-check-url=%s", def.Health.URL))
}
if def.Health.Interval != nil {
parts = append(parts, fmt.Sprintf("health-check-interval=%d", *def.Health.Interval))
}
if def.Health.Lazy != nil {
parts = append(parts, fmt.Sprintf("health-check-lazy=%t", *def.Health.Lazy))
}
if def.Health.Tolerance != nil {
parts = append(parts, fmt.Sprintf("health-check-tolerance=%d", *def.Health.Tolerance))
}
if def.Health.Timeout != nil {
parts = append(parts, fmt.Sprintf("health-check-timeout=%d", *def.Health.Timeout))
}
if def.Health.Method != "" {
parts = append(parts, fmt.Sprintf("health-check-method=%s", def.Health.Method))
}
if def.Health.Headers != "" {
parts = append(parts, fmt.Sprintf("health-check-headers=%s", def.Health.Headers))
}
}
return fmt.Sprintf("%s = %s", def.Name, strings.Join(parts, ", "))
}
func (g *SurgeGenerator) formatCustomGroup(def *GroupDefinition, defaultProxies []string) string {
proxies := make([]string, 0, len(def.Proxies))
proxies = append(proxies, def.Proxies...)
if len(proxies) == 0 && len(def.UseProviders) == 0 {
proxies = append(proxies, defaultProxies...)
}
items := make([]string, 0, 1+len(proxies)+len(def.UseProviders))
items = append(items, def.Type)
items = append(items, proxies...)
for _, provider := range def.UseProviders {
items = append(items, fmt.Sprintf("use-provider=%s", provider))
}
if def.URL != "" {
items = append(items, fmt.Sprintf("url=%s", def.URL))
}
if def.Interval != nil {
items = append(items, fmt.Sprintf("interval=%d", *def.Interval))
}
if def.Timeout != nil {
items = append(items, fmt.Sprintf("timeout=%d", *def.Timeout))
}
if def.Tolerance != nil {
items = append(items, fmt.Sprintf("tolerance=%d", *def.Tolerance))
}
if len(def.Extras) > 0 {
keys := make([]string, 0, len(def.Extras))
for k := range def.Extras {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
items = append(items, fmt.Sprintf("%s=%s", k, def.Extras[k]))
}
}
return fmt.Sprintf("%s = %s", def.Name, strings.Join(items, ", "))
}