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
321 lines
9.8 KiB
Go
321 lines
9.8 KiB
Go
package generator
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/subconverter-go/internal/logging"
|
|
"github.com/subconverter-go/internal/parser"
|
|
)
|
|
|
|
// SurfboardGenerator Surfboard格式生成器
|
|
// 实现Surfboard代理配置的生成功能
|
|
type SurfboardGenerator struct {
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewSurfboardGenerator 创建新的Surfboard生成器
|
|
// 返回初始化好的SurfboardGenerator实例
|
|
func NewSurfboardGenerator(logger *logging.Logger) *SurfboardGenerator {
|
|
return &SurfboardGenerator{
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Generate 生成Surfboard配置
|
|
func (g *SurfboardGenerator) Generate(configs []*parser.ProxyConfig, options *GenerationOptions) (string, error) {
|
|
g.logger.Debugf("Generating Surfboard configuration")
|
|
|
|
// 生成Surfboard配置文本
|
|
var builder strings.Builder
|
|
|
|
// 生成头部信息
|
|
g.generateHeader(&builder, options)
|
|
|
|
// 生成代理配置
|
|
g.generateProxies(&builder, configs)
|
|
|
|
// 生成代理组
|
|
g.generateProxyGroups(&builder, configs, options)
|
|
|
|
// 生成规则
|
|
g.generateRules(&builder, options)
|
|
|
|
return builder.String(), nil
|
|
}
|
|
|
|
// ValidateOptions 验证Surfboard生成选项
|
|
func (g *SurfboardGenerator) ValidateOptions(options *GenerationOptions) error {
|
|
g.logger.Debugf("Validating Surfboard 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 *SurfboardGenerator) GetSupportedFormats() []string {
|
|
return []string{"surfboard"}
|
|
}
|
|
|
|
// generateHeader 生成配置头部信息
|
|
func (g *SurfboardGenerator) generateHeader(builder *strings.Builder, options *GenerationOptions) {
|
|
builder.WriteString("# Surfboard 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("fallback-dns-server = 223.5.5.5, 119.29.29.29\n")
|
|
builder.WriteString("ipv6 = false\n")
|
|
builder.WriteString("prefer-ipv6 = false\n")
|
|
builder.WriteString("wifi-assist = false\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")
|
|
builder.WriteString("dns-server = 119.29.29.29, 223.5.5.5, 1.1.1.1\n")
|
|
builder.WriteString("exclude-simple-hostnames = true\n")
|
|
builder.WriteString("all-proxy-available = false\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))
|
|
}
|
|
|
|
if options.IPv6 {
|
|
builder.WriteString("ipv6 = true\n")
|
|
}
|
|
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
// generateProxies 生成代理配置
|
|
func (g *SurfboardGenerator) 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 转换代理配置为Surfboard格式
|
|
func (g *SurfboardGenerator) 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 *SurfboardGenerator) generateSSProxy(config *parser.ProxyConfig) string {
|
|
method, _ := config.Settings["method"].(string)
|
|
password, _ := config.Settings["password"].(string)
|
|
|
|
return fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s",
|
|
config.Name, config.Server, config.Port, method, password)
|
|
}
|
|
|
|
// generateSSRProxy 生成ShadowsocksR代理配置
|
|
func (g *SurfboardGenerator) 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 *SurfboardGenerator) 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("%s:%d", config.Server, config.Port))
|
|
opts = append(opts, fmt.Sprintf("%s", uuid))
|
|
opts = append(opts, fmt.Sprintf("%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",
|
|
config.Name, strings.Join(opts, ", "))
|
|
}
|
|
|
|
// generateTrojanProxy 生成Trojan代理配置
|
|
func (g *SurfboardGenerator) generateTrojanProxy(config *parser.ProxyConfig) string {
|
|
password, _ := config.Settings["password"].(string)
|
|
sni, _ := config.Settings["sni"].(string)
|
|
|
|
var opts []string
|
|
opts = append(opts, fmt.Sprintf("%s:%d", config.Server, config.Port))
|
|
opts = append(opts, fmt.Sprintf("%s", password))
|
|
|
|
if sni != "" {
|
|
opts = append(opts, fmt.Sprintf("sni=%s", sni))
|
|
}
|
|
|
|
return fmt.Sprintf("%s = trojan, %s",
|
|
config.Name, strings.Join(opts, ", "))
|
|
}
|
|
|
|
// generateHTTPProxy 生成HTTP代理配置
|
|
func (g *SurfboardGenerator) 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 *SurfboardGenerator) 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)
|
|
}
|
|
|
|
// generateProxyGroups 生成代理组
|
|
func (g *SurfboardGenerator) 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\n")
|
|
}
|
|
|
|
// generateRules 生成规则
|
|
func (g *SurfboardGenerator) 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")
|
|
}
|
|
} |