github.com/yaling888/clash@v1.53.0/adapter/outboundgroup/parser.go (about)

     1  package outboundgroup
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	regexp "github.com/dlclark/regexp2"
     9  
    10  	"github.com/yaling888/clash/adapter/outbound"
    11  	"github.com/yaling888/clash/adapter/provider"
    12  	"github.com/yaling888/clash/common/structure"
    13  	C "github.com/yaling888/clash/constant"
    14  	types "github.com/yaling888/clash/constant/provider"
    15  )
    16  
    17  var (
    18  	errFormat            = errors.New("format error")
    19  	errType              = errors.New("unsupported type")
    20  	errMissProxy         = errors.New("`use` or `proxies` missing")
    21  	errMissHealthCheck   = errors.New("`url` or `interval` missing")
    22  	errDuplicateProvider = errors.New("duplicate provider name")
    23  )
    24  
    25  type GroupCommonOption struct {
    26  	outbound.BasicOption
    27  	Name       string        `group:"name"`
    28  	Type       string        `group:"type"`
    29  	Proxies    []string      `group:"proxies,omitempty"`
    30  	Use        []string      `group:"use,omitempty"`
    31  	URL        string        `group:"url,omitempty"`
    32  	Interval   time.Duration `group:"interval,omitempty"`
    33  	Lazy       bool          `group:"lazy,omitempty"`
    34  	DisableUDP bool          `group:"disable-udp,omitempty"`
    35  	DisableDNS bool          `group:"disable-dns,omitempty"`
    36  	Filter     string        `group:"filter,omitempty"`
    37  }
    38  
    39  func ParseProxyGroup(
    40  	config map[string]any,
    41  	proxyMap map[string]C.Proxy,
    42  	providersMap map[string]types.ProxyProvider,
    43  ) (C.ProxyAdapter, error) {
    44  	decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
    45  
    46  	groupOption := &GroupCommonOption{
    47  		Lazy: true,
    48  	}
    49  
    50  	if err := decoder.Decode(config, groupOption); err != nil {
    51  		return nil, errFormat
    52  	}
    53  
    54  	if groupOption.Type == "" || groupOption.Name == "" {
    55  		return nil, errFormat
    56  	}
    57  
    58  	groupName := groupOption.Name
    59  
    60  	if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
    61  		return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
    62  	}
    63  
    64  	if _, ok := providersMap[groupName]; ok {
    65  		return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
    66  	}
    67  
    68  	var filterRegx *regexp.Regexp
    69  	if groupOption.Filter != "" {
    70  		regx, err := regexp.Compile(groupOption.Filter, 0)
    71  		if err != nil {
    72  			return nil, fmt.Errorf("%s: invalid filter regex: %w", groupName, err)
    73  		}
    74  		filterRegx = regx
    75  	}
    76  
    77  	var hc *provider.HealthCheck
    78  
    79  	// select and relay don't need health check
    80  	if groupOption.Type == "select" || groupOption.Type == "relay" {
    81  		hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true)
    82  	} else {
    83  		if groupOption.URL == "" || groupOption.Interval <= 0 {
    84  			return nil, fmt.Errorf("%s: %w", groupName, errMissHealthCheck)
    85  		}
    86  		hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, groupOption.Interval, groupOption.Lazy)
    87  	}
    88  
    89  	pd, err := provider.NewCompatibleProvider(groupName, hc, filterRegx)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("%s: %w", groupName, err)
    92  	}
    93  
    94  	if len(groupOption.Proxies) != 0 {
    95  		ps, err := getProxies(proxyMap, groupOption.Proxies)
    96  		if err != nil {
    97  			pd.Finalize()
    98  			return nil, fmt.Errorf("%s: %w", groupName, err)
    99  		}
   100  
   101  		pd.SetProxies(ps)
   102  	}
   103  
   104  	if len(groupOption.Use) != 0 {
   105  		list, err := getProviders(providersMap, groupOption.Use)
   106  		if err != nil {
   107  			pd.Finalize()
   108  			return nil, fmt.Errorf("%s: %w", groupName, err)
   109  		}
   110  
   111  		pd.SetProviders(list)
   112  	}
   113  
   114  	providers := []types.ProxyProvider{pd}
   115  	providersMap[groupName] = pd
   116  
   117  	var group C.ProxyAdapter
   118  	switch groupOption.Type {
   119  	case "url-test":
   120  		opts := parseURLTestOption(config)
   121  		group = NewURLTest(groupOption, providers, opts...)
   122  	case "select":
   123  		group = NewSelector(groupOption, providers)
   124  	case "fallback":
   125  		group = NewFallback(groupOption, providers)
   126  	case "load-balance":
   127  		strategy := parseStrategy(config)
   128  		group, err = NewLoadBalance(groupOption, providers, strategy)
   129  		if err != nil {
   130  			pd.Finalize()
   131  			return nil, fmt.Errorf("%s: %w", groupName, err)
   132  		}
   133  	case "relay":
   134  		group = NewRelay(groupOption, providers)
   135  	default:
   136  		pd.Finalize()
   137  		return nil, fmt.Errorf("%s %w: %s", groupName, errType, groupOption.Type)
   138  	}
   139  
   140  	return group, nil
   141  }
   142  
   143  func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
   144  	var ps []C.Proxy
   145  	for _, name := range list {
   146  		p, ok := mapping[name]
   147  		if !ok {
   148  			return nil, fmt.Errorf("'%s' not found", name)
   149  		}
   150  		ps = append(ps, p)
   151  	}
   152  	return ps, nil
   153  }
   154  
   155  func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
   156  	var ps []types.ProxyProvider
   157  	for _, name := range list {
   158  		p, ok := mapping[name]
   159  		if !ok {
   160  			return nil, fmt.Errorf("'%s' not found", name)
   161  		}
   162  		if p.VehicleType() == types.Compatible {
   163  			return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
   164  		}
   165  		ps = append(ps, p)
   166  	}
   167  	return ps, nil
   168  }