github.com/igoogolx/clash@v1.19.8/adapter/outboundgroup/parser.go (about)

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