diff --git a/app/cache.go b/app/cache.go index 4627cd5..54c6a43 100644 --- a/app/cache.go +++ b/app/cache.go @@ -1,11 +1,11 @@ package app import ( + "log" "time" - "github.com/zu1k/proxypool/proxy" - "github.com/patrickmn/go-cache" + "github.com/zu1k/proxypool/proxy" ) var c = cache.New(cache.NoExpiration, 10*time.Minute) @@ -13,8 +13,10 @@ var c = cache.New(cache.NoExpiration, 10*time.Minute) func GetProxies() []proxy.Proxy { result, found := c.Get("proxies") if found { + log.Println(len(result.([]proxy.Proxy))) return result.([]proxy.Proxy) } + log.Println("Cache not found") return nil } diff --git a/app/task.go b/app/task.go index dc8555a..35daedc 100644 --- a/app/task.go +++ b/app/task.go @@ -1,21 +1,20 @@ package app import ( - "fmt" - "github.com/zu1k/proxypool/getter" "github.com/zu1k/proxypool/proxy" ) func CrawlTGChannel() { node := make([]proxy.Proxy, 0) - node = append(node, getter.NewTGSsrlistGetter("https://t.me/s/ssrList", 200).Get()...) - node = append(node, getter.NewTGSsrlistGetter("https://t.me/s/SSRSUB", 200).Get()...) - node = append(node, getter.NewTGSsrlistGetter("https://t.me/s/FreeSSRNode", 200).Get()...) - node = append(node, getter.NewTGSsrlistGetter("https://t.me/s/ssrlists", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/ssrList", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/SSRSUB", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/FreeSSRNode", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/ssrlists", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/ssrshares", 200).Get()...) + node = append(node, getter.NewTGChannelGetter("https://t.me/s/V2List", 200).Get()...) node = append(node, GetProxies()...) node = proxy.Deduplication(node) - fmt.Println(len(node)) SetProxies(node) } diff --git a/getter/tgchannel.go b/getter/tgchannel.go index eb2272e..9311d10 100644 --- a/getter/tgchannel.go +++ b/getter/tgchannel.go @@ -2,6 +2,7 @@ package getter import ( "fmt" + "strings" "github.com/gocolly/colly" "github.com/zu1k/proxypool/proxy" @@ -14,7 +15,7 @@ type TGChannelGetter struct { Url string } -func NewTGSsrlistGetter(url string, numNeeded int) *TGChannelGetter { +func NewTGChannelGetter(url string, numNeeded int) *TGChannelGetter { if numNeeded <= 0 { numNeeded = 200 } @@ -30,6 +31,7 @@ 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)...) }) // 找到之前消息页面的链接,加入访问队列 @@ -46,12 +48,17 @@ func (g TGChannelGetter) Get() []proxy.Proxy { } results := make([]proxy.Proxy, 0) + var data proxy.Proxy for _, link := range g.Results { - data, err := proxy.ParseSSRLink(link) + 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, data) } return results } diff --git a/provider/clash.go b/provider/clash.go index 2f26430..28bb438 100644 --- a/provider/clash.go +++ b/provider/clash.go @@ -11,11 +11,6 @@ type Clash struct { } func (c Clash) Provide() string { - //data, err := yaml.Marshal(c) - //if err != nil { - // - //} - //return string(data) var resultBuilder strings.Builder resultBuilder.WriteString("proxies:\n") @@ -32,14 +27,18 @@ func checkClashSupport(p proxy.Proxy) bool { switch p.(type) { case proxy.ShadowsocksR: ssr := p.(proxy.ShadowsocksR) - if checkInList(cipherList, ssr.Cipher) && checkInList(protocolList, ssr.Protocol) && checkInList(obfsList, ssr.Obfs) { + if checkInList(ssrCipherList, ssr.Cipher) && checkInList(ssrProtocolList, ssr.Protocol) && checkInList(ssrObfsList, ssr.Obfs) { + return true + } + case proxy.Vmess: + vmess := p.(proxy.Vmess) + if checkInList(vmessCipherList, vmess.Cipher) { return true - } else { - return false } default: return false } + return false } func checkInList(list []string, item string) bool { @@ -51,7 +50,7 @@ func checkInList(list []string, item string) bool { return false } -var cipherList = []string{ +var ssrCipherList = []string{ "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", @@ -75,7 +74,7 @@ var cipherList = []string{ "seed-cfb", } -var obfsList = []string{ +var ssrObfsList = []string{ "plain", "http_simple", "http_post", @@ -84,7 +83,7 @@ var obfsList = []string{ "tls1.2_ticket_fastauth", } -var protocolList = []string{ +var ssrProtocolList = []string{ "origin", "verify_deflate", "verify_sha1", @@ -96,3 +95,10 @@ var protocolList = []string{ "auth_chain_a", "auth_chain_b", } + +var vmessCipherList = []string{ + "auto", + "aes-128-gcm", + "chacha20-poly1305", + "none", +} diff --git a/proxy/base.go b/proxy/base.go index cca092b..5e8cabb 100644 --- a/proxy/base.go +++ b/proxy/base.go @@ -1,7 +1,5 @@ package proxy -import "C" - type Base struct { Name string `yaml:"name" json:"name"` Server string `yaml:"server" json:"server"` diff --git a/proxy/shadowsocksr.go b/proxy/shadowsocksr.go index 2f451d2..c40dc3d 100644 --- a/proxy/shadowsocksr.go +++ b/proxy/shadowsocksr.go @@ -33,10 +33,6 @@ type ShadowsocksR struct { Group string `yaml:"group,omitempty" json:"group,omitempty"` } -func (ssr *ShadowsocksR) ParseFromLink(ssrlink string) { - ssr, _ = ParseSSRLink(ssrlink) -} - func (ssr ShadowsocksR) Identifier() string { return net.JoinHostPort(ssr.Server, strconv.Itoa(ssr.Port)) + ssr.ProtocolParam } @@ -57,28 +53,28 @@ func (ssr ShadowsocksR) ToClash() string { return "- " + string(data) } -func ParseSSRLink(ssrlink string) (*ShadowsocksR, error) { - if !strings.HasPrefix(ssrlink, "ssr") { - return nil, ErrorNotSSRLink +func ParseSSRLink(link string) (ShadowsocksR, error) { + if !strings.HasPrefix(link, "ssr") { + return ShadowsocksR{}, ErrorNotSSRLink } - ssrmix := strings.SplitN(ssrlink, "://", 2) + ssrmix := strings.SplitN(link, "://", 2) if len(ssrmix) < 2 { - return nil, ErrorNotSSRLink + return ShadowsocksR{}, ErrorNotSSRLink } linkPayloadBase64 := ssrmix[1] payload, err := tool.Base64DecodeString(linkPayloadBase64) if err != nil { - return nil, ErrorMissingQuery + return ShadowsocksR{}, ErrorMissingQuery } infoPayload := strings.SplitN(payload, "/?", 2) if len(infoPayload) < 2 { - return nil, ErrorNotSSRLink + return ShadowsocksR{}, ErrorNotSSRLink } ssrpath := strings.Split(infoPayload[0], ":") if len(ssrpath) < 6 { - return nil, ErrorPathNotComplete + return ShadowsocksR{}, ErrorPathNotComplete } // base info server := strings.ToLower(ssrpath[0]) @@ -88,7 +84,7 @@ func ParseSSRLink(ssrlink string) (*ShadowsocksR, error) { obfs := strings.ToLower(ssrpath[4]) password, err := tool.Base64DecodeString(ssrpath[5]) if err != nil { - return nil, ErrorPasswordParseFail + return ShadowsocksR{}, ErrorPasswordParseFail } moreInfo, _ := url.ParseQuery(infoPayload[1]) @@ -110,7 +106,7 @@ func ParseSSRLink(ssrlink string) (*ShadowsocksR, error) { // protocol param protocolParam, err := tool.Base64DecodeString(moreInfo.Get("protoparam")) if err != nil { - return nil, ErrorProtocolParamParseFail + return ShadowsocksR{}, ErrorProtocolParamParseFail } if strings.HasSuffix(protocolParam, "_compatible") { protocolParam = strings.ReplaceAll(protocolParam, "_compatible", "") @@ -119,7 +115,7 @@ func ParseSSRLink(ssrlink string) (*ShadowsocksR, error) { // obfs param obfsParam, err := tool.Base64DecodeString(moreInfo.Get("obfsparam")) if err != nil { - return nil, ErrorObfsParamParseFail + return ShadowsocksR{}, ErrorObfsParamParseFail } if strings.HasSuffix(obfsParam, "_compatible") { obfsParam = strings.ReplaceAll(obfsParam, "_compatible", "") @@ -131,7 +127,7 @@ func ParseSSRLink(ssrlink string) (*ShadowsocksR, error) { //} group := "" - return &ShadowsocksR{ + return ShadowsocksR{ Base: Base{ Name: remarks + "_" + strconv.Itoa(rand.Int()), Server: server, diff --git a/proxy/vmess.go b/proxy/vmess.go new file mode 100644 index 0000000..2324abb --- /dev/null +++ b/proxy/vmess.go @@ -0,0 +1,218 @@ +package proxy + +import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "net" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/zu1k/proxypool/tool" +) + +var ( + ErrorNotVmessLink = errors.New("not a correct vmess link") + ErrorVmessPayloadParseFail = errors.New("vmess link payload parse failed") + //ErrorPasswordParseFail = errors.New("password parse failed") + //ErrorPathNotComplete = errors.New("path not complete") + //ErrorMissingQuery = errors.New("link missing query") + //ErrorProtocolParamParseFail = errors.New("protocol param parse failed") + //ErrorObfsParamParseFail = errors.New("obfs param parse failed") +) + +type Vmess struct { + Base + UUID string `yaml:"uuid" json:"uuid"` + AlterID int `yaml:"alterId" json:"alterId"` + Cipher string `yaml:"cipher" json:"cipher"` + TLS bool `yaml:"tls,omitempty" json:"tls,omitempty"` + Network string `yaml:"network,omitempty" json:"network,omitempty"` + HTTPOpts HTTPOptions `yaml:"http-opts,omitempty" json:"http-opts,omitempty"` + WSPath string `yaml:"ws-path,omitempty" json:"ws-path,omitempty"` + WSHeaders map[string]string `yaml:"ws-headers,omitempty" json:"ws-headers,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty" json:"skip-cert-verify,omitempty"` + ServerName string `yaml:"servername,omitempty" json:"servername,omitempty"` +} + +type HTTPOptions struct { + Method string `yaml:"method,omitempty" json:"method,omitempty"` + Path []string `yaml:"path,omitempty" json:"path,omitempty"` + Headers map[string][]string `yaml:"headers,omitempty" json:"headers,omitempty"` +} + +func (v Vmess) Identifier() string { + return net.JoinHostPort(v.Server, strconv.Itoa(v.Port)) + v.Cipher +} + +func (v Vmess) String() string { + data, err := json.Marshal(v) + if err != nil { + return "" + } + return string(data) +} + +func (v Vmess) ToClash() string { + data, err := json.Marshal(v) + if err != nil { + return "" + } + return "- " + string(data) +} + +type vmessLinkJson struct { + Add string `json:"add"` + V string `json:"v"` + Ps string `json:"ps"` + Port interface{} `json:"port"` + Id string `json:"id"` + Aid string `json:"aid"` + Net string `json:"net"` + Type string `json:"type"` + Host string `json:"host"` + Path string `json:"path"` + Tls string `json:"tls"` +} + +func ParseVmessLink(link string) (Vmess, error) { + if !strings.HasPrefix(link, "vmess") { + return Vmess{}, ErrorNotVmessLink + } + + vmessmix := strings.SplitN(link, "://", 2) + if len(vmessmix) < 2 { + return Vmess{}, ErrorNotVmessLink + } + linkPayload := vmessmix[1] + if strings.Contains(linkPayload, "?") { + // 使用第二种解析方法 + var infoPayloads []string + if strings.Contains(linkPayload, "/?") { + infoPayloads = strings.SplitN(linkPayload, "/?", 2) + } else { + infoPayloads = strings.SplitN(linkPayload, "?", 2) + } + if len(infoPayloads) < 2 { + return Vmess{}, ErrorNotVmessLink + } + + baseInfo, err := tool.Base64DecodeString(infoPayloads[0]) + if err != nil { + return Vmess{}, ErrorVmessPayloadParseFail + } + fmt.Println(baseInfo) + baseInfoPath := strings.Split(baseInfo, ":") + if len(baseInfoPath) < 3 { + return Vmess{}, ErrorPathNotComplete + } + // base info + cipher := baseInfoPath[0] + mixInfo := strings.SplitN(baseInfoPath[1], "@", 2) + if len(mixInfo) < 2 { + return Vmess{}, ErrorVmessPayloadParseFail + } + uuid := mixInfo[0] + server := mixInfo[1] + portStr := baseInfoPath[2] + port, err := strconv.Atoi(portStr) + if err != nil { + return Vmess{}, ErrorVmessPayloadParseFail + } + + moreInfo, _ := url.ParseQuery(infoPayloads[1]) + fmt.Println(moreInfo) + remarks := moreInfo.Get("remarks") + obfs := moreInfo.Get("obfs") + network := "tcp" + if obfs == "websocket" { + network = "ws" + } + //obfsParam := moreInfo.Get("obfsParam") + path := moreInfo.Get("path") + tls := moreInfo.Get("tls") == "1" + + wsHeaders := make(map[string]string) + return Vmess{ + Base: Base{ + Name: remarks + "_" + strconv.Itoa(rand.Int()), + Server: server, + Port: port, + Type: "vmess", + UDP: false, + }, + UUID: uuid, + AlterID: 0, + Cipher: cipher, + TLS: tls, + Network: network, + HTTPOpts: HTTPOptions{}, + WSPath: path, + WSHeaders: wsHeaders, + SkipCertVerify: true, + ServerName: server, + }, nil + } else { + payload, err := tool.Base64DecodeString(linkPayload) + if err != nil { + return Vmess{}, ErrorVmessPayloadParseFail + } + vmessJson := vmessLinkJson{} + err = json.Unmarshal([]byte(payload), &vmessJson) + if err != nil { + return Vmess{}, err + } + port := 443 + portInterface := vmessJson.Port + if i, ok := portInterface.(int); ok { + port = i + } else if s, ok := portInterface.(string); ok { + port, _ = strconv.Atoi(s) + } + + alterId, err := strconv.Atoi(vmessJson.Aid) + if err != nil { + alterId = 0 + } + tls := vmessJson.Tls == "tls" + + wsHeaders := make(map[string]string) + wsHeaders["HOST"] = vmessJson.Host + + return Vmess{ + Base: Base{ + Name: vmessJson.Ps + "_" + strconv.Itoa(rand.Int()), + Server: vmessJson.Add, + Port: port, + Type: "vmess", + UDP: false, + }, + UUID: vmessJson.Id, + AlterID: alterId, + Cipher: "auto", + TLS: tls, + Network: vmessJson.Net, + HTTPOpts: HTTPOptions{}, + WSPath: vmessJson.Path, + WSHeaders: wsHeaders, + SkipCertVerify: true, + ServerName: vmessJson.Host, + }, nil + } +} + +var ( + vmessPlainRe = regexp.MustCompile("vmess://([A-Za-z0-9+/_-])+") +) + +func GrepVmessLinkFromString(text string) []string { + results := make([]string, 0) + texts := strings.Split(text, "vmess://") + for _, text := range texts { + results = append(results, vmessPlainRe.FindAllString("vmess://"+text, -1)...) + } + return results +} diff --git a/proxy/vmess_test.go b/proxy/vmess_test.go new file mode 100644 index 0000000..a79c1e7 --- /dev/null +++ b/proxy/vmess_test.go @@ -0,0 +1,13 @@ +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)) +}