From e6c935f13fbd5e42b4dd665a2a0c3f99cf1fec3a Mon Sep 17 00:00:00 2001 From: zu1k Date: Wed, 12 Aug 2020 17:09:11 +0800 Subject: [PATCH] add shadowsocks and subscribe support --- app/task.go | 5 ++ getter/base.go | 25 +++++---- getter/subscribe.go | 38 ++++++++++++-- getter/tgchannel.go | 3 +- getter/web_fuzz.go | 7 +-- getter/web_test.go | 5 ++ provider/clash.go | 22 ++++++++ proxy/shadowsocks.go | 121 +++++++++++++++++++++++++++++++++++++++++++ proxy/vmess_test.go | 13 ----- 9 files changed, 202 insertions(+), 37 deletions(-) create mode 100644 proxy/shadowsocks.go delete mode 100644 proxy/vmess_test.go diff --git a/app/task.go b/app/task.go index d7205bb..77c6622 100644 --- a/app/task.go +++ b/app/task.go @@ -36,6 +36,11 @@ func CrawlTGChannel() { proxies = append(proxies, getter.NewWebFuzz("https://www.freefq.com/d/file/free-ssr/20200811/1f3e9d0d0064f662457062712dcf1b66.txt").Get()...) proxies = append(proxies, getter.NewWebFuzz("https://merlinblog.xyz/wiki/freess.html").Get()...) + // 订阅链接 + proxies = append(proxies, getter.NewSubscribe("https://raw.githubusercontent.com/ssrsub/ssr/master/v2ray").Get()...) + proxies = append(proxies, getter.NewSubscribe("https://raw.githubusercontent.com/ssrsub/ssr/master/ssrsub").Get()...) + proxies = append(proxies, getter.NewSubscribe("https://raw.githubusercontent.com/ssrsub/ssr/master/ss-sub").Get()...) + proxies = append(proxies, cache.GetProxies()...) proxies = proxy.Deduplication(proxies) diff --git a/getter/base.go b/getter/base.go index 92e5793..34cb807 100644 --- a/getter/base.go +++ b/getter/base.go @@ -17,6 +17,8 @@ func String2Proxy(link string) proxy.Proxy { data, err = proxy.ParseSSRLink(link) } else if strings.HasPrefix(link, "vmess://") { data, err = proxy.ParseVmessLink(link) + } else if strings.HasPrefix(link, "ss://") { + data, err = proxy.ParseSSLink(link) } if err != nil { return nil @@ -25,19 +27,20 @@ func String2Proxy(link string) proxy.Proxy { } func StringArray2ProxyArray(origin []string) []proxy.Proxy { - var err error results := make([]proxy.Proxy, 0) for _, link := range origin { - var data proxy.Proxy - if strings.HasPrefix(link, "ssr://") { - data, err = proxy.ParseSSRLink(link) - } else if strings.HasPrefix(link, "vmess://") { - data, err = proxy.ParseVmessLink(link) - } - if err != nil { - continue - } - results = append(results, data) + results = append(results, String2Proxy(link)) } return results } + +func GrepLinksFromString(text string) []string { + results := proxy.GrepSSRLinkFromString(text) + results = append(results, proxy.GrepVmessLinkFromString(text)...) + results = append(results, proxy.GrepSSLinkFromString(text)...) + return results +} + +func FuzzParseProxyFromString(text string) []proxy.Proxy { + return StringArray2ProxyArray(GrepLinksFromString(text)) +} diff --git a/getter/subscribe.go b/getter/subscribe.go index 69c6dc1..8d5cd11 100644 --- a/getter/subscribe.go +++ b/getter/subscribe.go @@ -1,13 +1,41 @@ package getter -import "github.com/zu1k/proxypool/proxy" +import ( + "io/ioutil" + "net/http" + "strings" + + "github.com/zu1k/proxypool/proxy" + "github.com/zu1k/proxypool/tool" +) type Subscribe struct { - NumNeeded int - Results []string - Url string + Url string } func (s Subscribe) Get() []proxy.Proxy { - return nil + resp, err := http.Get(s.Url) + if err != nil { + return nil + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil + } + + nodesString, err := tool.Base64DecodeString(string(body)) + if err != nil { + return nil + } + nodesString = strings.ReplaceAll(nodesString, "\t", "") + + nodes := strings.Split(nodesString, "\n") + return StringArray2ProxyArray(nodes) +} + +func NewSubscribe(url string) *Subscribe { + return &Subscribe{ + Url: url, + } } diff --git a/getter/tgchannel.go b/getter/tgchannel.go index 8d07c49..3f3b91f 100644 --- a/getter/tgchannel.go +++ b/getter/tgchannel.go @@ -29,8 +29,7 @@ func NewTGChannelGetter(url string, numNeeded int) *TGChannelGetter { func (g TGChannelGetter) Get() []proxy.Proxy { // 找到所有的文字消息 g.c.OnHTML("div.tgme_widget_message_text", func(e *colly.HTMLElement) { - g.Results = append(g.Results, proxy.GrepSSRLinkFromString(e.Text)...) - g.Results = append(g.Results, proxy.GrepVmessLinkFromString(e.Text)...) + g.Results = append(g.Results, GrepLinksFromString(e.Text)...) }) // 找到之前消息页面的链接,加入访问队列 diff --git a/getter/web_fuzz.go b/getter/web_fuzz.go index f66c791..478c5e8 100644 --- a/getter/web_fuzz.go +++ b/getter/web_fuzz.go @@ -21,12 +21,7 @@ func (w WebFuzz) Get() []proxy.Proxy { if err != nil { return nil } - text := string(body) - - results := proxy.GrepSSRLinkFromString(text) - results = append(results, proxy.GrepVmessLinkFromString(text)...) - - return StringArray2ProxyArray(results) + return FuzzParseProxyFromString(string(body)) } func NewWebFuzz(url string) *WebFuzz { diff --git a/getter/web_test.go b/getter/web_test.go index aaf4a50..6d4e6c8 100644 --- a/getter/web_test.go +++ b/getter/web_test.go @@ -16,3 +16,8 @@ func TestWebFreessrXyz_Get(t *testing.T) { func TestWebFuzz_Get(t *testing.T) { fmt.Println(NewWebFuzz("https://merlinblog.xyz/wiki/freess.html").Get()) } + +func TestSubscribe_Get(t *testing.T) { + fmt.Println(NewSubscribe("https://raw.githubusercontent.com/ssrsub/ssr/master/v2ray").Get()) + fmt.Println(NewSubscribe("https://raw.githubusercontent.com/ssrsub/ssr/master/ssrsub").Get()) +} diff --git a/provider/clash.go b/provider/clash.go index 0c927a7..c889d2a 100644 --- a/provider/clash.go +++ b/provider/clash.go @@ -35,6 +35,11 @@ func checkClashSupport(p proxy.Proxy) bool { if checkInList(vmessCipherList, vmess.Cipher) { return true } + case *proxy.Shadowsocks: + ss := p.(*proxy.Shadowsocks) + if checkInList(ssCipherList, ss.Cipher) { + return true + } default: return false } @@ -102,3 +107,20 @@ var vmessCipherList = []string{ "chacha20-poly1305", "none", } + +var ssCipherList = []string{ + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "rc4-md5", + "chacha20-ietf", + "xchacha20", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", +} diff --git a/proxy/shadowsocks.go b/proxy/shadowsocks.go new file mode 100644 index 0000000..c04c913 --- /dev/null +++ b/proxy/shadowsocks.go @@ -0,0 +1,121 @@ +package proxy + +import ( + "encoding/json" + "errors" + "math/rand" + "net" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/zu1k/proxypool/tool" +) + +var ( + ErrorNotSSLink = errors.New("not a correct ss link") +) + +type Shadowsocks struct { + Base + Password string `yaml:"password" json:"password"` + Cipher string `yaml:"cipher" json:"cipher"` + Plugin string `yaml:"plugin,omitempty" json:"plugin,omitempty"` + PluginOpts map[string]interface{} `yaml:"plugin-opts,omitempty" json:"plugin-opts,omitempty"` +} + +func (ss Shadowsocks) Identifier() string { + return net.JoinHostPort(ss.Server, strconv.Itoa(ss.Port)) + ss.Password +} + +func (ss Shadowsocks) String() string { + data, err := json.Marshal(ss) + if err != nil { + return "" + } + return string(data) +} + +func (ss Shadowsocks) ToClash() string { + data, err := json.Marshal(ss) + if err != nil { + return "" + } + return "- " + string(data) +} + +func (ss *Shadowsocks) SetName(name string) { + ss.Name = name +} + +func ParseSSLink(link string) (*Shadowsocks, error) { + if !strings.HasPrefix(link, "ss://") { + return nil, ErrorNotSSRLink + } + + uri, err := url.Parse(link) + if err != nil { + return nil, ErrorNotSSLink + } + + server := uri.Hostname() + port, _ := strconv.Atoi(uri.Port()) + + cipherInfoString, err := tool.Base64DecodeString(uri.User.Username()) + if err != nil { + return nil, ErrorPasswordParseFail + } + cipherInfo := strings.SplitN(cipherInfoString, ":", 2) + if len(cipherInfo) < 2 { + return nil, ErrorPasswordParseFail + } + cipher := strings.ToLower(cipherInfo[0]) + password := cipherInfo[1] + + moreInfos := uri.Query() + pluginString := moreInfos.Get("plugin") + plugin := "" + pluginOpts := make(map[string]interface{}) + if strings.Contains(pluginString, ";") { + pluginInfos, err := url.ParseQuery(pluginString) + if err == nil { + if strings.Contains(pluginString, "obfs") { + plugin = "obfs" + pluginOpts["mode"] = pluginInfos.Get("obfs") + pluginOpts["host"] = pluginInfos.Get("obfs-host") + } else if strings.Contains(pluginString, "v2ray") { + plugin = "v2ray-plugin" + pluginOpts["mode"] = pluginInfos.Get("mode") + pluginOpts["host"] = pluginInfos.Get("host") + pluginOpts["tls"] = strings.Contains(pluginString, "tls") + } + } + } + + return &Shadowsocks{ + Base: Base{ + Name: strconv.Itoa(rand.Int()), + Server: server, + Port: port, + Type: "ss", + }, + Password: password, + Cipher: cipher, + Plugin: plugin, + PluginOpts: pluginOpts, + }, nil +} + +var ( + ssPlainRe = regexp.MustCompile("ss://([A-Za-z0-9+/_&?=@:%.-])+") +) + +func GrepSSLinkFromString(text string) []string { + results := make([]string, 0) + texts := strings.Split(text, "ss://") + for _, text := range texts { + results = append(results, ssPlainRe.FindAllString("ss://"+text, -1)...) + } + return results +} diff --git a/proxy/vmess_test.go b/proxy/vmess_test.go deleted file mode 100644 index a79c1e7..0000000 --- a/proxy/vmess_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package proxy - -import ( - "fmt" - "testing" -) - -func TestParseVmessLink(t *testing.T) { - //link := "vmess://YXV0bzo5MGQ0YTM3NC1kYWU4LTExZWEtODY3Zi01NjAwMDJlY2MzNDlAZml2ZWRlbWFuZHMubWw6NDQz?remarks=%E6%97%A5%E6%9C%AC5%EF%BC%9A%E7%94%B5%E6%8A%A5%E9%A2%91%E9%81%93%EF%BC%9A" - - link := "vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIkBTU1JTVUItVjI5LeS7mOi0ueaOqOiNkDp0LmNuL0VHSkl5cmwiLA0KICAiYWRkIjogImJ1cmdlcmtpbmdnb29kLm1sIiwNCiAgInBvcnQiOiAiNDQzIiwNCiAgImlkIjogIjRiNWJhMzkwLWRhZjQtMTFlYS04ODEwLTU2MDAwMmVjYzk2NyIsDQogICJhaWQiOiAiNDYiLA0KICAibmV0IjogIndzIiwNCiAgInR5cGUiOiAibm9uZSIsDQogICJob3N0IjogImJ1cmdlcmtpbmdnb29kLm1sIiwNCiAgInBhdGgiOiAiL0M2bGt2UzNLLyIsDQogICJ0bHMiOiAidGxzIg0KfQ==" - fmt.Println(ParseVmessLink(link)) -}