package generator import ( "fmt" "sort" "strings" "github.com/subconverter-go/internal/logging" "github.com/subconverter-go/internal/parser" ) // QuantumultXGenerator Quantumult X格式生成器 // 实现Quantumult X代理配置的生成功能 type QuantumultXGenerator struct { logger *logging.Logger } // NewQuantumultXGenerator 创建新的Quantumult X生成器 // 返回初始化好的QuantumultXGenerator实例 func NewQuantumultXGenerator(logger *logging.Logger) *QuantumultXGenerator { return &QuantumultXGenerator{ logger: logger, } } // Generate 生成Quantumult X配置 func (g *QuantumultXGenerator) Generate(configs []*parser.ProxyConfig, options *GenerationOptions) (string, error) { g.logger.Debugf("Generating Quantumult X configuration") // 生成Quantumult X配置文本 var builder strings.Builder // 生成头部信息 g.generateHeader(&builder, options) // 生成服务器配置 g.generateServers(&builder, configs) // 生成远程服务器提供者 g.generateRemoteServers(&builder, options) // 生成策略组 g.generatePolicyGroups(&builder, configs, options) // 生成规则 g.generateRules(&builder, options) return builder.String(), nil } // ValidateOptions 验证Quantumult X生成选项 func (g *QuantumultXGenerator) ValidateOptions(options *GenerationOptions) error { g.logger.Debugf("Validating Quantumult X 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 *QuantumultXGenerator) GetSupportedFormats() []string { return []string{"quantumultx"} } // generateHeader 生成配置头部信息 func (g *QuantumultXGenerator) generateHeader(builder *strings.Builder, options *GenerationOptions) { builder.WriteString("[General]\n") builder.WriteString("network_check_url = http://www.gstatic.com/generate_204\n") builder.WriteString("ipv6 = false\n") builder.WriteString("prefer_ipv6 = false\n") builder.WriteString("wifi_assist = false\n") builder.WriteString("enable_policy = true\n") builder.WriteString("exclude_simple_hostnames = true\n") builder.WriteString("all_proxy_available = 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") if options.IPv6 { builder.WriteString("ipv6 = true\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("\n") } // generateServers 生成服务器配置 func (g *QuantumultXGenerator) generateServers(builder *strings.Builder, configs []*parser.ProxyConfig) { builder.WriteString("[Server]\n") for i, config := range configs { server := g.convertProxyConfig(config) builder.WriteString(server) if i < len(configs)-1 { builder.WriteString("\n") } } builder.WriteString("\n") } func (g *QuantumultXGenerator) generateRemoteServers(builder *strings.Builder, options *GenerationOptions) { providers := g.resolveProviders(options) if len(providers) == 0 { return } builder.WriteString("[Server Remote]\n") for _, def := range providers { builder.WriteString(g.formatRemoteServer(def)) builder.WriteString("\n") } builder.WriteString("\n") } // convertProxyConfig 转换代理配置为Quantumult X格式 func (g *QuantumultXGenerator) 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 *QuantumultXGenerator) 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("shadowsocks=%s:%d, method=%s, password=%s, plugin=%s, %s", config.Server, config.Port, method, password, plugin, g.generatePluginOpts(config)) } return fmt.Sprintf("shadowsocks=%s:%d, method=%s, password=%s", config.Server, config.Port, method, password) } // generateSSRProxy 生成ShadowsocksR代理配置 func (g *QuantumultXGenerator) 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("shadowsocks=%s:%d, method=%s, password=%s, ssr-protocol=%s, ssr-protocol-param=%s, obfs=%s, obfs-host=%s", config.Server, config.Port, method, password, protocol, protocolParam, obfs, obfsParam) } // generateVMessProxy 生成VMess代理配置 func (g *QuantumultXGenerator) generateVMessProxy(config *parser.ProxyConfig) string { uuid, _ := config.Settings["uuid"].(string) _, _ = 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("method=%s", "chacha20-ietf-poly1305")) // Quantumult X默认加密方式 opts = append(opts, fmt.Sprintf("password=%s", uuid)) if network == "ws" { opts = append(opts, fmt.Sprintf("obfs=ws")) if host != "" { opts = append(opts, fmt.Sprintf("obfs-host=%s", host)) } if path != "" { opts = append(opts, fmt.Sprintf("obfs-uri=%s", path)) } } if tls != "" { opts = append(opts, fmt.Sprintf("tls-host=%s", host)) opts = append(opts, fmt.Sprintf("over-tls=true")) } return fmt.Sprintf("vmess=%s", strings.Join(opts, ", ")) } // generateTrojanProxy 生成Trojan代理配置 func (g *QuantumultXGenerator) 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("%s:%d", config.Server, config.Port)) opts = append(opts, fmt.Sprintf("password=%s", password)) if network == "ws" { opts = append(opts, fmt.Sprintf("obfs=ws")) if sni != "" { opts = append(opts, fmt.Sprintf("obfs-host=%s", sni)) } } if sni != "" { opts = append(opts, fmt.Sprintf("tls-host=%s", sni)) opts = append(opts, fmt.Sprintf("over-tls=true")) } return fmt.Sprintf("trojan=%s", strings.Join(opts, ", ")) } // generateHTTPProxy 生成HTTP代理配置 func (g *QuantumultXGenerator) 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("https=%s:%d, username=%s, password=%s", config.Server, config.Port, username, password) } return fmt.Sprintf("http=%s:%d, username=%s, password=%s", config.Server, config.Port, username, password) } if tls { return fmt.Sprintf("https=%s:%d", config.Server, config.Port) } return fmt.Sprintf("http=%s:%d", config.Server, config.Port) } // generateSocks5Proxy 生成Socks5代理配置 func (g *QuantumultXGenerator) generateSocks5Proxy(config *parser.ProxyConfig) string { username, _ := config.Settings["username"].(string) password, _ := config.Settings["password"].(string) if username != "" && password != "" { return fmt.Sprintf("socks5=%s:%d, username=%s, password=%s", config.Server, config.Port, username, password) } return fmt.Sprintf("socks5=%s:%d", config.Server, config.Port) } // generatePluginOpts 生成插件选项 func (g *QuantumultXGenerator) generatePluginOpts(config *parser.ProxyConfig) string { // 这里可以根据具体插件生成相应的选项 return "" } // generatePolicyGroups 生成策略组 func (g *QuantumultXGenerator) generatePolicyGroups(builder *strings.Builder, configs []*parser.ProxyConfig, options *GenerationOptions) { builder.WriteString("[Policy]\n") // 创建代理名称列表 proxyNames := make([]string, 0) for _, config := range configs { proxyNames = append(proxyNames, config.Name) } // 直连策略 builder.WriteString("static=Direct, direct\n") // 拒绝策略 builder.WriteString("static=Reject, reject\n") // 选择策略 builder.WriteString(fmt.Sprintf("static=Proxy, %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, ", "))) if len(options.GroupDefinitions) > 0 { for _, def := range options.GroupDefinitions { builder.WriteString(g.formatPolicyGroup(def, proxyNames)) builder.WriteString("\n") } } builder.WriteString("\n") } // generateRules 生成规则 func (g *QuantumultXGenerator) 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 *QuantumultXGenerator) 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 *QuantumultXGenerator) formatRemoteServer(def *ProviderDefinition) string { parts := []string{def.URL, fmt.Sprintf("tag=%s", def.Name)} parts = append(parts, fmt.Sprintf("path=%s", def.Path)) if def.Interval != nil { parts = append(parts, fmt.Sprintf("update-interval=%d", *def.Interval)) } parts = append(parts, "opt-parser=true", "enabled=true") 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])) } } return fmt.Sprintf("%s = %s", def.Name, strings.Join(parts, ", ")) } func (g *QuantumultXGenerator) formatPolicyGroup(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...) } typeToken := g.policyTypeToken(def.Type) items := make([]string, 0, len(proxies)) items = append(items, proxies...) if len(def.UseProviders) > 0 { items = append(items, fmt.Sprintf("server-tag-regex=%s", g.providerRegex(def.UseProviders))) } 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])) } } joined := strings.Join(items, ", ") if joined != "" { joined = ", " + joined } return fmt.Sprintf("%s=%s%s", typeToken, def.Name, joined) } func (g *QuantumultXGenerator) policyTypeToken(groupType string) string { switch strings.ToLower(groupType) { case "select": return "static" case "url-test": return "url-test" case "fallback": return "fallback" case "load-balance": return "available" default: return "static" } } func (g *QuantumultXGenerator) providerRegex(providers []string) string { if len(providers) == 1 { return fmt.Sprintf("^%s$", providers[0]) } return fmt.Sprintf("^(%s)$", strings.Join(providers, "|")) }