- Auto-extract protocol list from mihomo converter.go switch statements - Auto-extract protocol aliases from case declarations (hy2->hysteria2, etc) - Auto-scan all protocol files and build protocol->file->struct mappings - Auto-extract parameters from XXXOption structs via AST parsing - Generate param_compat.h with complete compatibility database - Integrate smart parameter application in subexport.cpp - Add mihomo_schemes.h and param_compat.h generation to build process - Support 15 protocols with zero hardcoded protocol lists - Fix http/https protocol filtering (were incorrectly excluded) - Add intelligent protocol name matching for abbreviations (ss->shadowsocks) This achieves ~95% automation - only 3 naming convention mappings remain. All protocol and alias information is now extracted directly from mihomo source code.
189 lines
4.4 KiB
Go
189 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
const headerTemplate = `// Auto-generated by scripts/generate_schemes.go
|
|
// DO NOT EDIT MANUALLY
|
|
// Based on mihomo version: {{.Version}}
|
|
|
|
#pragma once
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
namespace mihomo {
|
|
const std::vector<std::string> SUPPORTED_SCHEMES = {
|
|
{{- range .Schemes}}
|
|
"{{.}}",
|
|
{{- end}}
|
|
};
|
|
}
|
|
`
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Println("Usage: go run generate_schemes.go <output_path>")
|
|
os.Exit(1)
|
|
}
|
|
outputPath := os.Args[1]
|
|
|
|
// Determine module root
|
|
moduleRoot := "."
|
|
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
|
|
// try specific known paths
|
|
if _, err := os.Stat("../bridge/go.mod"); err == nil {
|
|
moduleRoot = "../bridge"
|
|
} else if _, err := os.Stat("bridge/go.mod"); err == nil {
|
|
moduleRoot = "bridge"
|
|
}
|
|
}
|
|
fmt.Printf("Using module root: %s\n", moduleRoot)
|
|
|
|
// 1. Get mihomo source path
|
|
cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", "github.com/metacubex/mihomo")
|
|
cmd.Dir = moduleRoot // execute in correct directory
|
|
|
|
// Capture stderr for debugging
|
|
var stderr strings.Builder
|
|
cmd.Stderr = &stderr
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
fmt.Printf("Error finding mihomo source: %v\nStderr: %s\n", err, stderr.String())
|
|
os.Exit(1)
|
|
}
|
|
|
|
mihomoPath := strings.TrimSpace(string(output))
|
|
if mihomoPath == "" {
|
|
fmt.Printf("Error: go list returned empty path! Stderr: %s\n", stderr.String())
|
|
|
|
// Fallback Debug: Get full JSON info
|
|
debugCmd := exec.Command("go", "list", "-m", "-json", "github.com/metacubex/mihomo")
|
|
debugCmd.Dir = moduleRoot
|
|
debugOut, _ := debugCmd.CombinedOutput()
|
|
fmt.Printf("Full go list JSON output:\n%s\n", string(debugOut))
|
|
|
|
// Try to fallback to GOPATH mode if we can guess it
|
|
if moduleRoot == "." || moduleRoot == "../bridge" {
|
|
// If we are in module root, maybe we can just find it?
|
|
// But if go list failed, probably finding it is hard.
|
|
}
|
|
|
|
os.Exit(1)
|
|
}
|
|
|
|
targetFile := filepath.Join(mihomoPath, "common/convert/converter.go")
|
|
|
|
fmt.Printf("Analyzing %s...\n", targetFile)
|
|
|
|
// 2. Parse AST
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, targetFile, nil, parser.ParseComments)
|
|
if err != nil {
|
|
fmt.Printf("Error parsing file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
schemes := make([]string, 0)
|
|
seen := make(map[string]bool)
|
|
|
|
// 3. Walk AST
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
fn, ok := n.(*ast.FuncDecl)
|
|
if !ok || fn.Name.Name != "ConvertsV2Ray" {
|
|
return true
|
|
}
|
|
|
|
fmt.Println("Found ConvertsV2Ray function")
|
|
|
|
ast.Inspect(fn.Body, func(n ast.Node) bool {
|
|
switchStmt, ok := n.(*ast.SwitchStmt)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Optional: check Tag
|
|
if ident, ok := switchStmt.Tag.(*ast.Ident); ok {
|
|
if ident.Name != "scheme" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Found switch statement with %d cases\n", len(switchStmt.Body.List))
|
|
|
|
for _, caseClause := range switchStmt.Body.List {
|
|
cc, ok := caseClause.(*ast.CaseClause)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Optional Debug: fmt.Printf(" Case %d: %d expressions\n", i, len(cc.List))
|
|
|
|
for _, expr := range cc.List {
|
|
switch v := expr.(type) {
|
|
case *ast.BasicLit:
|
|
if v.Kind == token.STRING {
|
|
val := strings.Trim(v.Value, "\"")
|
|
|
|
// Filter out transport-layer protocols (not standalone proxy protocols)
|
|
// ws, grpc, h2, httpupgrade are transport layers for vmess/vless/trojan
|
|
transportProtocols := []string{"ws", "grpc", "h2", "httpupgrade"}
|
|
isTransport := false
|
|
for _, tp := range transportProtocols {
|
|
if val == tp {
|
|
isTransport = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Include all standalone proxy protocols (including http/https)
|
|
if !isTransport && !seen[val] {
|
|
schemes = append(schemes, val)
|
|
seen[val] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return false
|
|
})
|
|
|
|
if len(schemes) == 0 {
|
|
fmt.Println("Warning: No schemes found!")
|
|
} else {
|
|
fmt.Printf("Total schemes found: %d\n", len(schemes))
|
|
}
|
|
|
|
// 4. Generate Output
|
|
data := struct {
|
|
Version string
|
|
Schemes []string
|
|
}{
|
|
Version: "latest",
|
|
Schemes: schemes,
|
|
}
|
|
|
|
f, err := os.Create(outputPath)
|
|
if err != nil {
|
|
fmt.Printf("Error creating output file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer f.Close()
|
|
|
|
tmpl := template.Must(template.New("header").Parse(headerTemplate))
|
|
tmpl.Execute(f, data)
|
|
fmt.Printf("Successfully generated %s\n", outputPath)
|
|
}
|