feat: proxy provider 支持V2ray格式订阅链接

This commit is contained in:
gVisor bot 2022-06-05 16:54:56 +08:00
parent 8479c2fd4c
commit 0e8c87474b
2 changed files with 69 additions and 61 deletions

View file

@ -16,42 +16,35 @@ var (
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
) )
type parser = func([]byte) (any, error) type parser[V any] func([]byte) (V, error)
type fetcher struct { type fetcher[V any] struct {
name string name string
vehicle types.Vehicle vehicle types.Vehicle
updatedAt *time.Time updatedAt *time.Time
ticker *time.Ticker ticker *time.Ticker
done chan struct{} done chan struct{}
hash [16]byte hash [16]byte
parser parser parser parser[V]
interval time.Duration interval time.Duration
onUpdate func(any) onUpdate func(V)
} }
func (f *fetcher) Name() string { func (f *fetcher[V]) Name() string {
return f.name return f.name
} }
func (f *fetcher) VehicleType() types.VehicleType { func (f *fetcher[V]) VehicleType() types.VehicleType {
return f.vehicle.Type() return f.vehicle.Type()
} }
func (f *fetcher) Initial() (any, error) { func (f *fetcher[V]) Initial() (V, error) {
var ( var (
buf []byte buf []byte
err error err error
isLocal bool isLocal bool
) )
defer func() {
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
}()
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path()) buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime() modTime := stat.ModTime()
@ -60,7 +53,7 @@ func (f *fetcher) Initial() (any, error) {
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) { if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
defer func() { defer func() {
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name()) log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
go f.update() go f.Update()
}() }()
} }
} else { } else {
@ -68,24 +61,24 @@ func (f *fetcher) Initial() (any, error) {
} }
if err != nil { if err != nil {
return nil, err return getZero[V](), err
} }
proxies, err := f.parser(buf) proxies, err := f.parser(buf)
if err != nil { if err != nil {
if !isLocal { if !isLocal {
return nil, err return getZero[V](), err
} }
// parse local file error, fallback to remote // parse local file error, fallback to remote
buf, err = f.vehicle.Read() buf, err = f.vehicle.Read()
if err != nil { if err != nil {
return nil, err return getZero[V](), err
} }
proxies, err = f.parser(buf) proxies, err = f.parser(buf)
if err != nil { if err != nil {
return nil, err return getZero[V](), err
} }
isLocal = false isLocal = false
@ -93,19 +86,24 @@ func (f *fetcher) Initial() (any, error) {
if f.vehicle.Type() != types.File && !isLocal { if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err return getZero[V](), err
} }
} }
f.hash = md5.Sum(buf) f.hash = md5.Sum(buf)
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
return proxies, nil return proxies, nil
} }
func (f *fetcher) Update() (any, bool, error) { func (f *fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read() buf, err := f.vehicle.Read()
if err != nil { if err != nil {
return nil, false, err return getZero[V](), false, err
} }
now := time.Now() now := time.Now()
@ -113,17 +111,17 @@ func (f *fetcher) Update() (any, bool, error) {
if bytes.Equal(f.hash[:], hash[:]) { if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now) os.Chtimes(f.vehicle.Path(), now, now)
return nil, true, nil return getZero[V](), true, nil
} }
proxies, err := f.parser(buf) proxies, err := f.parser(buf)
if err != nil { if err != nil {
return nil, false, err return getZero[V](), false, err
} }
if f.vehicle.Type() != types.File { if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err return getZero[V](), false, err
} }
} }
@ -133,22 +131,32 @@ func (f *fetcher) Update() (any, bool, error) {
return proxies, false, nil return proxies, false, nil
} }
func (f *fetcher) Destroy() error { func (f *fetcher[V]) Destroy() error {
if f.ticker != nil { if f.ticker != nil {
f.done <- struct{}{} f.done <- struct{}{}
} }
return nil return nil
} }
func (f *fetcher) pullLoop() { func (f *fetcher[V]) pullLoop() {
for { for {
select { select {
case <-f.ticker.C: case <-f.ticker.C:
same, err := f.update() elm, same, err := f.Update()
if same || err != nil { if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue continue
} }
if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
continue
}
log.Infoln("[Provider] %s's proxies update", f.Name())
if f.onUpdate != nil {
f.onUpdate(elm)
}
case <-f.done: case <-f.done:
f.ticker.Stop() f.ticker.Stop()
return return
@ -156,26 +164,6 @@ func (f *fetcher) pullLoop() {
} }
} }
func (f *fetcher) update() (same bool, err error) {
elm, same, err := f.Update()
if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
return
}
if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
return
}
if f.onUpdate != nil {
f.onUpdate(elm)
}
log.Infoln("[Provider] %s's proxies update", f.Name())
return
}
func safeWrite(path string, buf []byte) error { func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path) dir := filepath.Dir(path)
@ -188,19 +176,23 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode) return os.WriteFile(path, buf, fileMode)
} }
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher { func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {
ticker = time.NewTicker(interval) ticker = time.NewTicker(interval)
} }
return &fetcher{ return &fetcher[V]{
name: name, name: name,
ticker: ticker, ticker: ticker,
vehicle: vehicle, vehicle: vehicle,
parser: parser, parser: parser,
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
onUpdate: onUpdate, onUpdate: onUpdate,
interval: interval,
} }
} }
func getZero[V any]() V {
var result V
return result
}

View file

@ -4,12 +4,13 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/common/convert"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"math"
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
@ -36,10 +37,6 @@ type proxySetProvider struct {
version uint version uint
} }
func (pp *proxySetProvider) Version() uint {
return pp.version
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": pp.Name(), "name": pp.Name(),
@ -50,6 +47,10 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
}) })
} }
func (pp *proxySetProvider) Version() uint {
return pp.version
}
func (pp *proxySetProvider) Name() string { func (pp *proxySetProvider) Name() string {
return pp.name return pp.name
} }
@ -71,7 +72,6 @@ func (pp *proxySetProvider) Initial() error {
if err != nil { if err != nil {
return err return err
} }
pp.onUpdate(elm) pp.onUpdate(elm)
return nil return nil
} }
@ -93,7 +93,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() { if pp.healthCheck.auto() {
go pp.healthCheck.check() defer func() { go pp.healthCheck.check() }()
} }
} }
@ -134,6 +134,7 @@ type compatibleProvider struct {
name string name string
healthCheck *HealthCheck healthCheck *HealthCheck
proxies []C.Proxy proxies []C.Proxy
version uint
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
@ -145,6 +146,10 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
}) })
} }
func (cp *compatibleProvider) Version() uint {
return cp.version
}
func (cp *compatibleProvider) Name() string { func (cp *compatibleProvider) Name() string {
return cp.name return cp.name
} }
@ -205,6 +210,11 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) { return func(elm []C.Proxy) {
pd.setProxies(elm) pd.setProxies(elm)
if pd.version == math.MaxUint {
pd.version = 0
} else {
pd.version++
}
} }
} }
@ -213,7 +223,11 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.
schema := &ProxySchema{} schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil { if err := yaml.Unmarshal(buf, schema); err != nil {
return nil, err proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil {
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
}
schema.Proxies = proxies
} }
if schema.Proxies == nil { if schema.Proxies == nil {
@ -222,7 +236,9 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { name, ok := mapping["name"]
mat, _ := filterReg.FindStringMatch(name.(string))
if ok && len(filter) > 0 && mat == nil {
continue continue
} }
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)