.
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NAME=proxypool
2 | BINDIR=bin
3 | VERSION=$(shell git describe --tags || echo "unknown version")
4 | GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-w -s'
5 |
6 | PLATFORM_LIST = \
7 | darwin-amd64 \
8 | linux-386 \
9 | linux-amd64 \
10 | linux-armv5 \
11 | linux-armv6 \
12 | linux-armv7 \
13 | linux-armv8 \
14 | linux-mips-softfloat \
15 | linux-mips-hardfloat \
16 | linux-mipsle-softfloat \
17 | linux-mipsle-hardfloat \
18 | linux-mips64 \
19 | linux-mips64le \
20 | freebsd-386 \
21 | freebsd-amd64
22 |
23 |
24 | all: linux-amd64 darwin-amd64
25 |
26 | docker:
27 | $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
28 |
29 | darwin-amd64:
30 | GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
31 |
32 | linux-386:
33 | GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
34 |
35 | linux-amd64:
36 | GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
37 |
38 | linux-armv5:
39 | GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
40 |
41 | linux-armv6:
42 | GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
43 |
44 | linux-armv7:
45 | GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
46 |
47 | linux-armv8:
48 | GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
49 |
50 | linux-mips-softfloat:
51 | GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
52 |
53 | linux-mips-hardfloat:
54 | GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
55 |
56 | linux-mipsle-softfloat:
57 | GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
58 |
59 | linux-mipsle-hardfloat:
60 | GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
61 |
62 | linux-mips64:
63 | GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
64 |
65 | linux-mips64le:
66 | GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
67 |
68 | freebsd-386:
69 | GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
70 |
71 | freebsd-amd64:
72 | GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
73 |
74 | gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
75 |
76 | $(gz_releases): %.gz : %
77 | chmod +x $(BINDIR)/$(NAME)-$(basename $@)
78 | gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@)
79 |
80 | all-arch: $(PLATFORM_LIST)
81 |
82 | releases: $(gz_releases)
83 | clean:
84 | rm $(BINDIR)/*
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
proxypool
3 |
4 |
5 | 自动抓取tg频道、订阅地址、公开互联网上的ss、ssr、vmess、trojan节点信息,聚合去重测试可用性后提供节点列表
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## 支持
20 |
21 | - 支持ss、ssr、vmess、trojan多种类型
22 | - Telegram频道抓取
23 | - 订阅地址抓取解析
24 | - 公开互联网页面模糊抓取
25 | - 定时抓取自动更新
26 | - 通过配置文件设置抓取源
27 | - 自动检测节点可用性
28 |
29 | ## 安装
30 |
31 | ### 使用Heroku
32 |
33 | 点击按钮进入部署页面,填写基本信息然后运行
34 |
35 | 其中 `DOMAIN` 需要填写为你需要绑定的域名,`CONFIG_FILE` 需要填写你的配置文件路径,配置文件模板见 config/config.yaml 文件
36 |
37 | `CF` 开头的选项暂不需要填写,不影响程序运行
38 |
39 | [](https://heroku.com/deploy)
40 |
41 | > 因为爬虫程序需要持续运行,所以至少选择 $7/月 的配置
42 | > 免费配置长时间无人访问会被heroku强制停止
43 |
44 | ### 从源码编译
45 |
46 | 需要安装Golang
47 |
48 | ```sh
49 | $ go get -u -v github.com/zu1k/proxypool
50 | ```
51 |
52 | ### 下载预编译程序
53 |
54 | 从这里下载预编译好的程序 [release](https://github.com/zu1k/proxypool/releases)
55 |
56 | ### 使用docker
57 |
58 | ```sh
59 | docker pull docker.pkg.github.com/zu1k/proxypool/proxypool:latest
60 | ```
61 |
62 | ## 使用
63 |
64 | ### 修改配置文件
65 |
66 | 首先修改 config.yaml 中的必要配置信息,cf开头的选项不需要填写
67 |
68 | source.yaml 文件中定义了抓取源,需要定期手动维护更新
69 |
70 | ### 启动程序
71 |
72 | 使用 `-c` 参数指定配置文件路径,支持http链接
73 |
74 | ```shell
75 | proxypool -c config.yaml
76 | ```
77 |
78 | ## 截图
79 |
80 | 
81 |
82 | 
83 |
84 | ## 声明
85 |
86 | 本项目遵循 GNU General Public License v3.0 开源,在此基础上,所有使用本项目提供服务者都必须在网站首页保留指向本项目的链接
87 |
88 | 禁止使用本项目进行营利和做其他违法事情,产生的一切后果本项目概不负责
89 |
--------------------------------------------------------------------------------
/api/router.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "html/template"
5 | "net/http"
6 | "os"
7 | "strconv"
8 |
9 | "github.com/gin-gonic/gin"
10 | _ "github.com/heroku/x/hmetrics/onload"
11 | "github.com/zu1k/proxypool/config"
12 | binhtml "github.com/zu1k/proxypool/internal/bindata/html"
13 | "github.com/zu1k/proxypool/internal/cache"
14 | "github.com/zu1k/proxypool/pkg/provider"
15 | )
16 |
17 | const version = "v0.3.8"
18 |
19 | var router *gin.Engine
20 |
21 | func setupRouter() {
22 | gin.SetMode(gin.ReleaseMode)
23 | router = gin.New()
24 | router.Use(gin.Recovery())
25 | temp, err := loadTemplate()
26 | if err != nil {
27 | panic(err)
28 | }
29 | router.SetHTMLTemplate(temp)
30 |
31 | router.GET("/", func(c *gin.Context) {
32 | c.HTML(http.StatusOK, "assets/html/index.html", gin.H{
33 | "domain": config.Config.Domain,
34 | "getters_count": cache.GettersCount,
35 | "all_proxies_count": cache.AllProxiesCount,
36 | "ss_proxies_count": cache.SSProxiesCount,
37 | "ssr_proxies_count": cache.SSRProxiesCount,
38 | "vmess_proxies_count": cache.VmessProxiesCount,
39 | "trojan_proxies_count": cache.TrojanProxiesCount,
40 | "useful_proxies_count": cache.UsefullProxiesCount,
41 | "last_crawl_time": cache.LastCrawlTime,
42 | "version": version,
43 | })
44 | })
45 |
46 | router.GET("/clash", func(c *gin.Context) {
47 | c.HTML(http.StatusOK, "assets/html/clash.html", gin.H{
48 | "domain": config.Config.Domain,
49 | })
50 | })
51 |
52 | router.GET("/surge", func(c *gin.Context) {
53 | c.HTML(http.StatusOK, "assets/html/surge.html", gin.H{
54 | "domain": config.Config.Domain,
55 | })
56 | })
57 |
58 | router.GET("/clash/config", func(c *gin.Context) {
59 | c.HTML(http.StatusOK, "assets/html/clash-config.yaml", gin.H{
60 | "domain": config.Config.Domain,
61 | })
62 | })
63 |
64 | router.GET("/surge/config", func(c *gin.Context) {
65 | c.HTML(http.StatusOK, "assets/html/surge.conf", gin.H{
66 | "domain": config.Config.Domain,
67 | })
68 | })
69 |
70 | router.GET("/clash/proxies", func(c *gin.Context) {
71 | proxyTypes := c.DefaultQuery("type", "")
72 | proxyCountry := c.DefaultQuery("c", "")
73 | proxyNotCountry := c.DefaultQuery("nc", "")
74 | text := ""
75 | if proxyTypes == "" && proxyCountry == "" && proxyNotCountry == "" {
76 | text = cache.GetString("clashproxies")
77 | if text == "" {
78 | proxies := cache.GetProxies("proxies")
79 | clash := provider.Clash{
80 | provider.Base{
81 | Proxies: &proxies,
82 | },
83 | }
84 | text = clash.Provide()
85 | cache.SetString("clashproxies", text)
86 | }
87 | } else if proxyTypes == "all" {
88 | proxies := cache.GetProxies("allproxies")
89 | clash := provider.Clash{
90 | provider.Base{
91 | Proxies: &proxies,
92 | Types: proxyTypes,
93 | Country: proxyCountry,
94 | NotCountry: proxyNotCountry,
95 | },
96 | }
97 | text = clash.Provide()
98 | } else {
99 | proxies := cache.GetProxies("proxies")
100 | clash := provider.Clash{
101 | provider.Base{
102 | Proxies: &proxies,
103 | Types: proxyTypes,
104 | Country: proxyCountry,
105 | NotCountry: proxyNotCountry,
106 | },
107 | }
108 | text = clash.Provide()
109 | }
110 | c.String(200, text)
111 | })
112 | router.GET("/surge/proxies", func(c *gin.Context) {
113 | proxyTypes := c.DefaultQuery("type", "")
114 | proxyCountry := c.DefaultQuery("c", "")
115 | proxyNotCountry := c.DefaultQuery("nc", "")
116 | text := ""
117 | if proxyTypes == "" && proxyCountry == "" && proxyNotCountry == "" {
118 | text = cache.GetString("surgeproxies")
119 | if text == "" {
120 | proxies := cache.GetProxies("proxies")
121 | surge := provider.Surge{
122 | provider.Base{
123 | Proxies: &proxies,
124 | },
125 | }
126 | text = surge.Provide()
127 | cache.SetString("surgeproxies", text)
128 | }
129 | } else if proxyTypes == "all" {
130 | proxies := cache.GetProxies("allproxies")
131 | surge := provider.Surge{
132 | provider.Base{
133 | Proxies: &proxies,
134 | Types: proxyTypes,
135 | Country: proxyCountry,
136 | NotCountry: proxyNotCountry,
137 | },
138 | }
139 | text = surge.Provide()
140 | } else {
141 | proxies := cache.GetProxies("proxies")
142 | surge := provider.Surge{
143 | provider.Base{
144 | Proxies: &proxies,
145 | Types: proxyTypes,
146 | Country: proxyCountry,
147 | NotCountry: proxyNotCountry,
148 | },
149 | }
150 | text = surge.Provide()
151 | }
152 | c.String(200, text)
153 | })
154 |
155 | router.GET("/ss/sub", func(c *gin.Context) {
156 | proxies := cache.GetProxies("proxies")
157 | ssSub := provider.SSSub{
158 | provider.Base{
159 | Proxies: &proxies,
160 | Types: "ss",
161 | },
162 | }
163 | c.String(200, ssSub.Provide())
164 | })
165 | router.GET("/ssr/sub", func(c *gin.Context) {
166 | proxies := cache.GetProxies("proxies")
167 | ssrSub := provider.SSRSub{
168 | provider.Base{
169 | Proxies: &proxies,
170 | Types: "ssr",
171 | },
172 | }
173 | c.String(200, ssrSub.Provide())
174 | })
175 | router.GET("/vmess/sub", func(c *gin.Context) {
176 | proxies := cache.GetProxies("proxies")
177 | vmessSub := provider.VmessSub{
178 | provider.Base{
179 | Proxies: &proxies,
180 | Types: "vmess",
181 | },
182 | }
183 | c.String(200, vmessSub.Provide())
184 | })
185 | router.GET("/link/:id", func(c *gin.Context) {
186 | idx := c.Param("id")
187 | proxies := cache.GetProxies("allproxies")
188 | id, err := strconv.Atoi(idx)
189 | if err != nil {
190 | c.String(500, err.Error())
191 | }
192 | if id >= proxies.Len() {
193 | c.String(500, "id too big")
194 | }
195 | c.String(200, proxies[id].Link())
196 | })
197 | }
198 |
199 | func Run() {
200 | setupRouter()
201 | port := os.Getenv("PORT")
202 | if port == "" {
203 | port = "8080"
204 | }
205 | router.Run(":" + port)
206 | }
207 |
208 | func loadTemplate() (t *template.Template, err error) {
209 | _ = binhtml.RestoreAssets("", "assets/html")
210 | t = template.New("")
211 | for _, fileName := range binhtml.AssetNames() {
212 | data := binhtml.MustAsset(fileName)
213 | t, err = t.New(fileName).Parse(string(data))
214 | if err != nil {
215 | return nil, err
216 | }
217 | }
218 | return t, nil
219 | }
220 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proxypool",
3 | "description": "自动抓取tg频道、订阅地址、公开互联网上的ss、ssr、vmess节点信息,聚合去重测试可用性后提供节点列表",
4 | "website": "https://proxy.tgbot.co/",
5 | "repository": "https://github.com/zu1k/proxypool",
6 | "success_url": "/",
7 | "logo": "https://raw.githubusercontent.com/zu1k/proxypool/heroku/assets/proxy.jpg",
8 | "keywords": ["golang", "ss", "ssr", "vmess", "shadowsocks", "shadowsocksr", "trojan"],
9 | "env": {
10 | "CONFIG_FILE": {
11 | "description": "Path to config file, could be a url."
12 | },
13 | "DOMAIN": {
14 | "description": "Domain to use."
15 | },
16 | "CF_API_EMAIL": {
17 | "description": "Cloudflare Email.",
18 | "required": false
19 | },
20 | "CF_API_KEY": {
21 | "description": "Cloudflare API key.",
22 | "required": false
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/assets/GeoLite2-City.mmdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yourp112/proxypool/0e3a17a9f23ddfc23c8caf0fd5d285cd5a0dbd58/assets/GeoLite2-City.mmdb
--------------------------------------------------------------------------------
/assets/proxy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yourp112/proxypool/0e3a17a9f23ddfc23c8caf0fd5d285cd5a0dbd58/assets/proxy.jpg
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "errors"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 |
9 | "github.com/ghodss/yaml"
10 | "github.com/zu1k/proxypool/pkg/tool"
11 | )
12 |
13 | var configFilePath = "config.yaml"
14 |
15 | type ConfigOptions struct {
16 | Domain string `json:"domain" yaml:"domain"`
17 | DatabaseUrl string `json:"database_url" yaml:"database_url"`
18 | CFEmail string `json:"cf_email" yaml:"cf_email"`
19 | CFKey string `json:"cf_key" yaml:"cf_key"`
20 | SourceFiles []string `json:"source-files" yaml:"source-files"`
21 | }
22 |
23 | // Config 配置
24 | var Config ConfigOptions
25 |
26 | // Parse 解析配置文件,支持本地文件系统和网络链接
27 | func Parse(path string) error {
28 | if path == "" {
29 | path = configFilePath
30 | } else {
31 | configFilePath = path
32 | }
33 | fileData, err := ReadFile(path)
34 | if err != nil {
35 | return err
36 | }
37 | Config = ConfigOptions{}
38 | err = yaml.Unmarshal(fileData, &Config)
39 | if err != nil {
40 | return err
41 | }
42 |
43 | // 部分配置环境变量优先
44 | if domain := os.Getenv("DOMAIN"); domain != "" {
45 | Config.Domain = domain
46 | }
47 | if cfEmail := os.Getenv("CF_API_EMAIL"); cfEmail != "" {
48 | Config.CFEmail = cfEmail
49 | }
50 | if cfKey := os.Getenv("CF_API_KEY"); cfKey != "" {
51 | Config.CFKey = cfKey
52 | }
53 |
54 | return nil
55 | }
56 |
57 | // 从本地文件或者http链接读取配置文件内容
58 | func ReadFile(path string) ([]byte, error) {
59 | if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
60 | resp, err := tool.GetHttpClient().Get(path)
61 | if err != nil {
62 | return nil, errors.New("config file http get fail")
63 | }
64 | defer resp.Body.Close()
65 | return ioutil.ReadAll(resp.Body)
66 | } else {
67 | if _, err := os.Stat(path); os.IsNotExist(err) {
68 | return nil, err
69 | }
70 | return ioutil.ReadFile(path)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/config/config.yaml:
--------------------------------------------------------------------------------
1 | # your domain
2 | domain: example.com
3 |
4 | # database url
5 | database_url: ""
6 |
7 | # cloudflare api
8 | cf_email: ""
9 | cf_key: ""
10 |
11 | # source definition file
12 | source-files:
13 | # use local file
14 | - source.yaml
15 | # use web file
16 | - https://example.com/source.yaml
--------------------------------------------------------------------------------
/config/source.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/zu1k/proxypool/pkg/tool"
4 |
5 | type Source struct {
6 | Type string `json:"type" yaml:"type"`
7 | Options tool.Options `json:"options" yaml:"options"`
8 | }
9 |
--------------------------------------------------------------------------------
/config/source.yaml:
--------------------------------------------------------------------------------
1 | # 模糊抓取订阅链接
2 | - type: webfuzzsub
3 | options:
4 | url: https://raw.githubusercontent.com/du5/free/master/sub.list
5 |
6 | # 订阅链接
7 | - type: subscribe
8 | options:
9 | url: https://raw.githubusercontent.com/ssrsub/ssr/master/v2ray
10 |
11 | # 网页模糊抓取
12 | - type: webfuzz
13 | options:
14 | url: https://merlinblog.xyz/wiki/freess.html
15 |
16 | # tg频道抓取
17 | - type: tgchannel
18 | options:
19 | channel: ssrList
20 | num: 200
21 |
22 | # 翻墙党论坛抓取
23 | - type: web-fanqiangdang
24 | options:
25 | url: https://fanqiangdang.com/forum-48-1.html
26 |
27 | # 某个网站抓取
28 | - type: web-freessrxyz
29 | options:
30 |
--------------------------------------------------------------------------------
/docs/fast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yourp112/proxypool/0e3a17a9f23ddfc23c8caf0fd5d285cd5a0dbd58/docs/fast.png
--------------------------------------------------------------------------------
/docs/genbindata.sh:
--------------------------------------------------------------------------------
1 | go-bindata -o internal/bindata/html/html.go -pkg binhtml assets/html/
2 | go-bindata -o internal/bindata/geoip/geoip.go -pkg bingeoip assets/GeoLite2-City.mmdb assets/flags.json
3 |
--------------------------------------------------------------------------------
/docs/speedtest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yourp112/proxypool/0e3a17a9f23ddfc23c8caf0fd5d285cd5a0dbd58/docs/speedtest.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | // +heroku goVersion go1.14
2 |
3 | module github.com/zu1k/proxypool
4 |
5 | go 1.14
6 |
7 | require (
8 | github.com/Dreamacro/clash v1.0.1-0.20200812125056-8b7c731fd629
9 | github.com/PuerkitoBio/goquery v1.5.1 // indirect
10 | github.com/andybalholm/cascadia v1.2.0 // indirect
11 | github.com/antchfx/htmlquery v1.2.3 // indirect
12 | github.com/antchfx/xmlquery v1.2.4 // indirect
13 | github.com/antchfx/xpath v1.1.8 // indirect
14 | github.com/cloudflare/cloudflare-go v0.13.2
15 | github.com/ghodss/yaml v1.0.0
16 | github.com/gin-gonic/gin v1.6.3
17 | github.com/go-playground/validator/v10 v10.3.0 // indirect
18 | github.com/gobwas/glob v0.2.3 // indirect
19 | github.com/gocolly/colly v1.2.0
20 | github.com/golang/protobuf v1.4.2 // indirect
21 | github.com/heroku/x v0.0.25
22 | github.com/ivpusic/grpool v1.0.0
23 | github.com/jackc/pgproto3/v2 v2.0.4 // indirect
24 | github.com/jasonlvhit/gocron v0.0.1
25 | github.com/json-iterator/go v1.1.10 // indirect
26 | github.com/kennygrant/sanitize v1.2.4 // indirect
27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
28 | github.com/modern-go/reflect2 v1.0.1 // indirect
29 | github.com/oschwald/geoip2-golang v1.4.0
30 | github.com/oschwald/maxminddb-golang v1.7.0 // indirect
31 | github.com/patrickmn/go-cache v2.1.0+incompatible
32 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
33 | github.com/temoto/robotstxt v1.1.1 // indirect
34 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
35 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed // indirect
36 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
37 | google.golang.org/appengine v1.6.6 // indirect
38 | google.golang.org/protobuf v1.25.0 // indirect
39 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
40 | gorm.io/driver/postgres v1.0.0
41 | gorm.io/gorm v1.20.0
42 | )
43 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6 | github.com/Dreamacro/clash v1.0.1-0.20200812125056-8b7c731fd629 h1:876g402+S9ROmgHU7wGdSssWz/TNiJJsDsphd2qsbRc=
7 | github.com/Dreamacro/clash v1.0.1-0.20200812125056-8b7c731fd629/go.mod h1:LMbDBvj4kuiE5TGhZ8JxhEdRpBtZRjQllo2r7RfUw1c=
8 | github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s=
9 | github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
10 | github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
11 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
12 | github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
13 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
14 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
15 | github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
16 | github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
17 | github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M=
18 | github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
19 | github.com/antchfx/xmlquery v1.2.4 h1:T/SH1bYdzdjTMoz2RgsfVKbM5uWh3gjDYYepFqQmFv4=
20 | github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
21 | github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
22 | github.com/antchfx/xpath v1.1.8 h1:PcL6bIX42Px5usSx6xRYw/wjB3wYGkj0MJ9MBzEKVgk=
23 | github.com/antchfx/xpath v1.1.8/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
24 | github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
25 | github.com/aws/aws-sdk-go v1.13.10/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
26 | github.com/axiomhq/hyperloglog v0.0.0-20180317131949-fe9507de0228/go.mod h1:IOXAcuKIFq/mDyuQ4wyJuJ79XLMsmLM+5RdQ+vWrL7o=
27 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
28 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
29 | github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A=
30 | github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM=
31 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
32 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
33 | github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
34 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
35 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
36 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
37 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
38 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
39 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
40 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
41 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
42 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
43 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
44 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
45 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
46 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
47 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
48 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
49 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
50 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
51 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
52 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
53 | github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
54 | github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
55 | github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
56 | github.com/go-ini/ini v1.33.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
57 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
58 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
59 | github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
60 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
61 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
62 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
63 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
64 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
65 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
66 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
67 | github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
68 | github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
69 | github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
70 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
71 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
72 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
73 | github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
74 | github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
75 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
76 | github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
77 | github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
78 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
79 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
80 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
81 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
83 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
84 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
85 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
86 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
87 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
88 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
89 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
90 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
91 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
92 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
93 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
94 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
95 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
96 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
97 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
98 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
99 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
100 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
101 | github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
102 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
103 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
104 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
106 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
107 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
108 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
109 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
110 | github.com/google/gops v0.3.8-0.20200229223415-3a98d6d24562/go.mod h1:bj0cwMmX1X4XIJFTjR99R5sCxNssNJ8HebFNvoQlmgY=
111 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
112 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
113 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
114 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
115 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
116 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
117 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
118 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
119 | github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
120 | github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
121 | github.com/grpc-ecosystem/grpc-gateway v1.9.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
122 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
123 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
124 | github.com/heroku/rollrus v0.2.0/go.mod h1:B3MwEcr9nmf4xj0Sr5l9eSht7wLKMa1C+9ajgAU79ek=
125 | github.com/heroku/x v0.0.25 h1:6/2rENs6rfUds9jOxy9oO59b0fiOx/FeTCGOyLMXcZw=
126 | github.com/heroku/x v0.0.25/go.mod h1:qE/I0jp6rIeTBBosrPYV4ygRX3OMhqmC/A6x8ewodJQ=
127 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
128 | github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
129 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
130 | github.com/ivpusic/grpool v1.0.0 h1:+FCiCo3GhfsvzfXuJWnpJUNb/VaqyYVgG8C+qvh07Rc=
131 | github.com/ivpusic/grpool v1.0.0/go.mod h1:WPmiAI5ExAn06vg+0JzyPzXMQutJmpb7TrBtyLJkOHQ=
132 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
133 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
134 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
135 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
136 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
137 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
138 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
139 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
140 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
141 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
142 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
143 | github.com/jackc/pgconn v1.6.4 h1:S7T6cx5o2OqmxdHaXLH1ZeD1SbI8jBznyYE9Ec0RCQ8=
144 | github.com/jackc/pgconn v1.6.4/go.mod h1:w2pne1C2tZgP+TvjqLpOigGzNqjBgQW9dUw/4Chex78=
145 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
146 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
147 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
148 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
149 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
150 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
151 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
152 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
153 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
154 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
155 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
156 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
157 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
158 | github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
159 | github.com/jackc/pgproto3/v2 v2.0.4 h1:RHkX5ZUD9bl/kn0f9dYUWs1N7Nwvo1wwUYvKiR26Zco=
160 | github.com/jackc/pgproto3/v2 v2.0.4/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
161 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
162 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
163 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
164 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
165 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
166 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
167 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
168 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
169 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
170 | github.com/jackc/pgtype v1.4.2 h1:t+6LWm5eWPLX1H5Se702JSBcirq6uWa4jiG4wV1rAWY=
171 | github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
172 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
173 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
174 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
175 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
176 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
177 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
178 | github.com/jackc/pgx/v4 v4.8.1 h1:SUbCLP2pXvf/Sr/25KsuI4aTxiFYIvpfk4l6aTSdyCw=
179 | github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0=
180 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
181 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
182 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
183 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
184 | github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU=
185 | github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4=
186 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
187 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
188 | github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
189 | github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
190 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
191 | github.com/joeshaw/envdecode v0.0.0-20180129163420-d5f34bca07f3/go.mod h1:Q+alOFAXgW5SrcfMPt/G4B2oN+qEcQRJjkn/f4mKL04=
192 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
193 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
194 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
195 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
196 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
197 | github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
198 | github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
199 | github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
200 | github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
201 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
202 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
203 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
204 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
205 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
206 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
207 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
208 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
209 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
210 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
211 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
212 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
213 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
214 | github.com/leesper/go_rng v0.0.0-20171009123644-5344a9259b21/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U=
215 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
216 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
217 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
218 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
219 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
220 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
221 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
222 | github.com/lstoll/grpce v1.7.0/go.mod h1:XiCWl3R+avNCT7KsTjv3qCblgsSqd0SC4ymySrH226g=
223 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
224 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
225 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
226 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
227 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
228 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
229 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
230 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
231 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
232 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
233 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
234 | github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
235 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
236 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
237 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
238 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
239 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
240 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
241 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
242 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
243 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
244 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
245 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
246 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
247 | github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
248 | github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
249 | github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
250 | github.com/oschwald/maxminddb-golang v1.7.0 h1:JmU4Q1WBv5Q+2KZy5xJI+98aUwTIrPPxZUkd5Cwr8Zc=
251 | github.com/oschwald/maxminddb-golang v1.7.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
252 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
253 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
254 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
255 | github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
256 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
257 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
258 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
259 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
260 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
261 | github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
262 | github.com/rcrowley/go-metrics v0.0.0-20160613154715-cfa5a85e9f0a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
263 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
264 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
265 | github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
266 | github.com/rollbar/rollbar-go v1.2.0/go.mod h1:czC86b8U4xdUH7W2C6gomi2jutLm8qK0OtrF5WMvpcc=
267 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
268 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
269 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
270 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
271 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
272 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
273 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
274 | github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
275 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
276 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
277 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
278 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
279 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
280 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
281 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
282 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
283 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
284 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
285 | github.com/soveran/redisurl v0.0.0-20180322091936-eb325bc7a4b8/go.mod h1:FVJ8jbHu7QrNFs3bZEsv/L5JjearIAY9N0oXh2wk+6Y=
286 | github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
287 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
288 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
289 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
290 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
291 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
292 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
293 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
294 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
295 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
296 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
297 | github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
298 | github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
299 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
300 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
301 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
302 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
303 | github.com/unrolled/secure v1.0.1/go.mod h1:R6rugAuzh4TQpbFAq69oqZggyBQxFRFQIewtz5z7Jsc=
304 | github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
305 | github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
306 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
307 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
308 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
309 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
310 | go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
311 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
312 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
313 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
314 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
315 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
316 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
317 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
318 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
319 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
320 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
321 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
322 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
323 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
324 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
325 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
326 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
327 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
328 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
329 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
330 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
331 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
332 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
333 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
334 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
335 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
336 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
337 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
338 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
339 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
340 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
341 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
342 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
343 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
344 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
345 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
346 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
347 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
348 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
349 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
350 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
351 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
352 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
353 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
354 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
355 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
356 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
357 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
358 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
359 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
360 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
361 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
362 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
363 | golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
364 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
365 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
366 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
367 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
368 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
369 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
370 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
371 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
372 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
373 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
374 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
375 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
376 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
377 | golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
378 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
379 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
380 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
381 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
382 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
383 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
384 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
385 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
386 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
387 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
388 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
389 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
390 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
391 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
392 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394 | golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
395 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
397 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
398 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
399 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
400 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
401 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
402 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
403 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
404 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
405 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
406 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
407 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
408 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
409 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
410 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
411 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
412 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
413 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
414 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
415 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
416 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
417 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
418 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
419 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
420 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
421 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
422 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
423 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
424 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
425 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
426 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
427 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
428 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
429 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
430 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
431 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
432 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
433 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
434 | gonum.org/v1/gonum v0.0.0-20190502212712-4a2eb0188cbc/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
435 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
436 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
437 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
438 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
439 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
440 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
441 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
442 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
443 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
444 | google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
445 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
446 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
447 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
448 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
449 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
450 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
451 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
452 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
453 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
454 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
455 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
456 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
457 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
458 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
459 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
460 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
461 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
462 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
463 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
464 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
465 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
466 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
467 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
468 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
469 | gopkg.in/caio/go-tdigest.v2 v2.3.0/go.mod h1:HPfh/CLN8UWDMOC76lqxVeKa5E24ypoVuTj4BLMb9cU=
470 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
471 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
472 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
473 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
474 | gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
475 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
476 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
477 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
478 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
479 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
480 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
481 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
482 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
483 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
484 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
485 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
486 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
487 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
488 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
489 | gorm.io/driver/postgres v1.0.0 h1:Yh4jyFQ0a7F+JPU0Gtiam/eKmpT/XFc1FKxotGqc6FM=
490 | gorm.io/driver/postgres v1.0.0/go.mod h1:wtMFcOzmuA5QigNsgEIb7O5lhvH1tHAF1RbWmLWV4to=
491 | gorm.io/gorm v1.9.19/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
492 | gorm.io/gorm v1.20.0 h1:qfIlyaZvrF7kMWY3jBdEBXkXJ2M5MFYMTppjILxS3fQ=
493 | gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
494 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
495 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
496 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
497 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
498 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
499 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
500 | rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
501 |
--------------------------------------------------------------------------------
/internal/app/getter.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/zu1k/proxypool/internal/cache"
8 |
9 | "github.com/ghodss/yaml"
10 |
11 | "github.com/zu1k/proxypool/config"
12 | "github.com/zu1k/proxypool/pkg/getter"
13 | )
14 |
15 | var Getters = make([]getter.Getter, 0)
16 |
17 | func InitConfigAndGetters(path string) (err error) {
18 | err = config.Parse(path)
19 | if err != nil {
20 | return
21 | }
22 | if s := config.Config.SourceFiles; len(s) == 0 {
23 | return errors.New("no sources")
24 | } else {
25 | initGetters(s)
26 | }
27 | return
28 | }
29 |
30 | func initGetters(sourceFiles []string) {
31 | Getters = make([]getter.Getter, 0)
32 | for _, path := range sourceFiles {
33 | data, err := config.ReadFile(path)
34 | if err != nil {
35 | fmt.Errorf("Init SourceFile Error: %s\n", err.Error())
36 | continue
37 | }
38 | sourceList := make([]config.Source, 0)
39 | err = yaml.Unmarshal(data, &sourceList)
40 | if err != nil {
41 | fmt.Errorf("Init SourceFile Error: %s\n", err.Error())
42 | continue
43 | }
44 | for _, source := range sourceList {
45 | g, err := getter.NewGetter(source.Type, source.Options)
46 | if err == nil && g != nil {
47 | Getters = append(Getters, g)
48 | fmt.Println("init getter:", source.Type, source.Options)
49 | }
50 | }
51 | }
52 | fmt.Println("Getter count:", len(Getters))
53 | cache.GettersCount = len(Getters)
54 | }
55 |
--------------------------------------------------------------------------------
/internal/app/task.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "log"
5 | "sync"
6 | "time"
7 |
8 | "github.com/zu1k/proxypool/internal/cache"
9 | "github.com/zu1k/proxypool/internal/database"
10 | "github.com/zu1k/proxypool/pkg/provider"
11 | "github.com/zu1k/proxypool/pkg/proxy"
12 | )
13 |
14 | var location, _ = time.LoadLocation("PRC")
15 |
16 | func CrawlGo() {
17 | wg := &sync.WaitGroup{}
18 | var pc = make(chan proxy.Proxy)
19 | for _, g := range Getters {
20 | wg.Add(1)
21 | go g.Get2Chan(pc, wg)
22 | }
23 | proxies := cache.GetProxies("allproxies")
24 | proxies = append(proxies, database.GetAllProxies()...)
25 | go func() {
26 | wg.Wait()
27 | close(pc)
28 | }()
29 | for node := range pc {
30 | if node != nil {
31 | proxies = append(proxies, node)
32 | }
33 | }
34 | // 节点去重
35 | proxies = proxies.Deduplication()
36 | log.Println("CrawlGo node count:", len(proxies))
37 | proxies = provider.Clash{
38 | provider.Base{
39 | Proxies: &proxies,
40 | },
41 | }.CleanProxies()
42 | log.Println("CrawlGo cleaned node count:", len(proxies))
43 | proxies.NameAddCounrty().Sort().NameAddIndex().NameAddTG()
44 | log.Println("Proxy rename DONE!")
45 |
46 | // 全节点存储到数据库
47 | database.SaveProxyList(proxies)
48 |
49 | cache.SetProxies("allproxies", proxies)
50 | cache.AllProxiesCount = proxies.Len()
51 | log.Println("AllProxiesCount:", cache.AllProxiesCount)
52 | cache.SSProxiesCount = proxies.TypeLen("ss")
53 | log.Println("SSProxiesCount:", cache.SSProxiesCount)
54 | cache.SSRProxiesCount = proxies.TypeLen("ssr")
55 | log.Println("SSRProxiesCount:", cache.SSRProxiesCount)
56 | cache.VmessProxiesCount = proxies.TypeLen("vmess")
57 | log.Println("VmessProxiesCount:", cache.VmessProxiesCount)
58 | cache.TrojanProxiesCount = proxies.TypeLen("trojan")
59 | log.Println("TrojanProxiesCount:", cache.TrojanProxiesCount)
60 | cache.LastCrawlTime = time.Now().In(location).Format("2006-01-02 15:04:05")
61 |
62 | // 可用性检测
63 | log.Println("Now proceed proxy health check...")
64 | proxies = proxy.CleanBadProxiesWithGrpool(proxies)
65 | log.Println("CrawlGo clash usable node count:", len(proxies))
66 | proxies.NameReIndex()
67 | cache.SetProxies("proxies", proxies)
68 | cache.UsefullProxiesCount = proxies.Len()
69 |
70 | cache.SetString("clashproxies", provider.Clash{
71 | provider.Base{
72 | Proxies: &proxies,
73 | },
74 | }.Provide())
75 | cache.SetString("surgeproxies", provider.Surge{
76 | provider.Base{
77 | Proxies: &proxies,
78 | },
79 | }.Provide())
80 | }
81 |
--------------------------------------------------------------------------------
/internal/bindata/geoip/geoip.go:
--------------------------------------------------------------------------------
1 | // Code generated for package bingeoip by go-bindata DO NOT EDIT. (@generated)
2 | // sources:
3 | // assets/GeoLite2-City.mmdb
4 | // assets/flags.json
5 | package bingeoip
6 |
7 | import (
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | )
14 |
15 | // bindataRead reads the given file from disk. It returns an error on failure.
16 | func bindataRead(path, name string) ([]byte, error) {
17 | buf, err := ioutil.ReadFile(path)
18 | if err != nil {
19 | err = fmt.Errorf("Error reading asset %s at %s: %v", name, path, err)
20 | }
21 | return buf, err
22 | }
23 |
24 | type asset struct {
25 | bytes []byte
26 | info os.FileInfo
27 | }
28 |
29 | // assetsGeolite2CityMmdb reads file data from disk. It returns an error on failure.
30 | func assetsGeolite2CityMmdb() (*asset, error) {
31 | path := "assets/GeoLite2-City.mmdb"
32 | name := "assets/GeoLite2-City.mmdb"
33 | bytes, err := bindataRead(path, name)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | fi, err := os.Stat(path)
39 | if err != nil {
40 | err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
41 | }
42 |
43 | a := &asset{bytes: bytes, info: fi}
44 | return a, err
45 | }
46 |
47 | // assetsFlagsJson reads file data from disk. It returns an error on failure.
48 | func assetsFlagsJson() (*asset, error) {
49 | path := "assets/flags.json"
50 | name := "assets/flags.json"
51 | bytes, err := bindataRead(path, name)
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | fi, err := os.Stat(path)
57 | if err != nil {
58 | err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
59 | }
60 |
61 | a := &asset{bytes: bytes, info: fi}
62 | return a, err
63 | }
64 |
65 | // Asset loads and returns the asset for the given name.
66 | // It returns an error if the asset could not be found or
67 | // could not be loaded.
68 | func Asset(name string) ([]byte, error) {
69 | cannonicalName := strings.Replace(name, "\\", "/", -1)
70 | if f, ok := _bindata[cannonicalName]; ok {
71 | a, err := f()
72 | if err != nil {
73 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
74 | }
75 | return a.bytes, nil
76 | }
77 | return nil, fmt.Errorf("Asset %s not found", name)
78 | }
79 |
80 | // MustAsset is like Asset but panics when Asset would return an error.
81 | // It simplifies safe initialization of global variables.
82 | func MustAsset(name string) []byte {
83 | a, err := Asset(name)
84 | if err != nil {
85 | panic("asset: Asset(" + name + "): " + err.Error())
86 | }
87 |
88 | return a
89 | }
90 |
91 | // AssetInfo loads and returns the asset info for the given name.
92 | // It returns an error if the asset could not be found or
93 | // could not be loaded.
94 | func AssetInfo(name string) (os.FileInfo, error) {
95 | cannonicalName := strings.Replace(name, "\\", "/", -1)
96 | if f, ok := _bindata[cannonicalName]; ok {
97 | a, err := f()
98 | if err != nil {
99 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
100 | }
101 | return a.info, nil
102 | }
103 | return nil, fmt.Errorf("AssetInfo %s not found", name)
104 | }
105 |
106 | // AssetNames returns the names of the assets.
107 | func AssetNames() []string {
108 | names := make([]string, 0, len(_bindata))
109 | for name := range _bindata {
110 | names = append(names, name)
111 | }
112 | return names
113 | }
114 |
115 | // _bindata is a table, holding each asset generator, mapped to its name.
116 | var _bindata = map[string]func() (*asset, error){
117 | "assets/GeoLite2-City.mmdb": assetsGeolite2CityMmdb,
118 | "assets/flags.json": assetsFlagsJson,
119 | }
120 |
121 | // AssetDir returns the file names below a certain
122 | // directory embedded in the file by go-bindata.
123 | // For example if you run go-bindata on data/... and data contains the
124 | // following hierarchy:
125 | // data/
126 | // foo.txt
127 | // img/
128 | // a.png
129 | // b.png
130 | // then AssetDir("data") would return []string{"foo.txt", "img"}
131 | // AssetDir("data/img") would return []string{"a.png", "b.png"}
132 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error
133 | // AssetDir("") will return []string{"data"}.
134 | func AssetDir(name string) ([]string, error) {
135 | node := _bintree
136 | if len(name) != 0 {
137 | cannonicalName := strings.Replace(name, "\\", "/", -1)
138 | pathList := strings.Split(cannonicalName, "/")
139 | for _, p := range pathList {
140 | node = node.Children[p]
141 | if node == nil {
142 | return nil, fmt.Errorf("Asset %s not found", name)
143 | }
144 | }
145 | }
146 | if node.Func != nil {
147 | return nil, fmt.Errorf("Asset %s not found", name)
148 | }
149 | rv := make([]string, 0, len(node.Children))
150 | for childName := range node.Children {
151 | rv = append(rv, childName)
152 | }
153 | return rv, nil
154 | }
155 |
156 | type bintree struct {
157 | Func func() (*asset, error)
158 | Children map[string]*bintree
159 | }
160 |
161 | var _bintree = &bintree{nil, map[string]*bintree{
162 | "assets": &bintree{nil, map[string]*bintree{
163 | "GeoLite2-City.mmdb": &bintree{assetsGeolite2CityMmdb, map[string]*bintree{}},
164 | "flags.json": &bintree{assetsFlagsJson, map[string]*bintree{}},
165 | }},
166 | }}
167 |
168 | // RestoreAsset restores an asset under the given directory
169 | func RestoreAsset(dir, name string) error {
170 | data, err := Asset(name)
171 | if err != nil {
172 | return err
173 | }
174 | info, err := AssetInfo(name)
175 | if err != nil {
176 | return err
177 | }
178 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
179 | if err != nil {
180 | return err
181 | }
182 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
183 | if err != nil {
184 | return err
185 | }
186 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
187 | if err != nil {
188 | return err
189 | }
190 | return nil
191 | }
192 |
193 | // RestoreAssets restores an asset under the given directory recursively
194 | func RestoreAssets(dir, name string) error {
195 | children, err := AssetDir(name)
196 | // File
197 | if err != nil {
198 | return RestoreAsset(dir, name)
199 | }
200 | // Dir
201 | for _, child := range children {
202 | err = RestoreAssets(dir, filepath.Join(name, child))
203 | if err != nil {
204 | return err
205 | }
206 | }
207 | return nil
208 | }
209 |
210 | func _filePath(dir, name string) string {
211 | cannonicalName := strings.Replace(name, "\\", "/", -1)
212 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
213 | }
214 |
--------------------------------------------------------------------------------
/internal/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/patrickmn/go-cache"
7 | "github.com/zu1k/proxypool/pkg/proxy"
8 | )
9 |
10 | var c = cache.New(cache.NoExpiration, 10*time.Minute)
11 |
12 | func GetProxies(key string) proxy.ProxyList {
13 | result, found := c.Get(key)
14 | if found {
15 | return result.(proxy.ProxyList)
16 | }
17 | return nil
18 | }
19 |
20 | func SetProxies(key string, proxies proxy.ProxyList) {
21 | c.Set(key, proxies, cache.NoExpiration)
22 | }
23 |
24 | func SetString(key, value string) {
25 | c.Set(key, value, cache.NoExpiration)
26 | }
27 |
28 | func GetString(key string) string {
29 | result, found := c.Get(key)
30 | if found {
31 | return result.(string)
32 | }
33 | return ""
34 | }
35 |
--------------------------------------------------------------------------------
/internal/cache/vars.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | var (
4 | GettersCount = 0
5 |
6 | AllProxiesCount = 0
7 | SSRProxiesCount = 0
8 | SSProxiesCount = 0
9 | VmessProxiesCount = 0
10 | TrojanProxiesCount = 0
11 |
12 | UsefullProxiesCount = 0
13 |
14 | LastCrawlTime = "程序刚升级完成,正在爬取中..."
15 | )
16 |
--------------------------------------------------------------------------------
/internal/cloudflare/cache.go:
--------------------------------------------------------------------------------
1 | package cloudflare
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/cloudflare/cloudflare-go"
8 | "github.com/zu1k/proxypool/config"
9 | )
10 |
11 | func test() {
12 | api, err := cloudflare.New(config.Config.CFKey, config.Config.CFKey)
13 | if err != nil {
14 | log.Fatal(err)
15 | }
16 |
17 | // Fetch the zone ID
18 | id, err := api.ZoneIDByName(config.Config.Domain)
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 |
23 | // Fetch zone details
24 | zone, err := api.ZoneDetails(id)
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 | // Print zone details
29 | fmt.Println(zone)
30 | }
31 |
--------------------------------------------------------------------------------
/internal/cron/cron.go:
--------------------------------------------------------------------------------
1 | package cron
2 |
3 | import (
4 | "runtime"
5 |
6 | "github.com/jasonlvhit/gocron"
7 | "github.com/zu1k/proxypool/internal/app"
8 | )
9 |
10 | func Cron() {
11 | _ = gocron.Every(15).Minutes().Do(crawlTask)
12 | <-gocron.Start()
13 | }
14 |
15 | func crawlTask() {
16 | _ = app.InitConfigAndGetters("")
17 | app.CrawlGo()
18 | app.Getters = nil
19 | runtime.GC()
20 | }
21 |
--------------------------------------------------------------------------------
/internal/database/db.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/zu1k/proxypool/config"
8 |
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 | "gorm.io/gorm/logger"
12 | )
13 |
14 | var DB *gorm.DB
15 |
16 | func connect() (err error) {
17 | dsn := "user=proxypool password=proxypool dbname=proxypool port=5432 sslmode=disable TimeZone=Asia/Shanghai"
18 | if url := config.Config.DatabaseUrl; url != "" {
19 | dsn = url
20 | }
21 | if url := os.Getenv("DATABASE_URL"); url != "" {
22 | dsn = url
23 | }
24 | DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
25 | Logger: logger.Default.LogMode(logger.Silent),
26 | })
27 | if err == nil {
28 | fmt.Println("DB connect success: ", DB.Name())
29 | }
30 | return
31 | }
32 |
--------------------------------------------------------------------------------
/internal/database/db_test.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "testing"
4 |
5 | func TestConnect(t *testing.T) {
6 | t.SkipNow()
7 | //connect()
8 | InitTables()
9 | }
10 |
--------------------------------------------------------------------------------
/internal/database/proxy.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "github.com/zu1k/proxypool/pkg/getter"
5 | "github.com/zu1k/proxypool/pkg/proxy"
6 | "gorm.io/gorm"
7 | )
8 |
9 | type Proxy struct {
10 | gorm.Model
11 | proxy.Base
12 | Link string
13 | Identifier string `gorm:"unique"`
14 | }
15 |
16 | func InitTables() {
17 | if DB == nil {
18 | err := connect()
19 | if err != nil {
20 | return
21 | }
22 | }
23 | err := DB.AutoMigrate(&Proxy{})
24 | if err != nil {
25 | panic(err)
26 | }
27 | }
28 |
29 | const roundSize = 100
30 |
31 | func SaveProxyList(pl proxy.ProxyList) {
32 | if DB == nil {
33 | return
34 | }
35 |
36 | size := pl.Len()
37 | round := (size + roundSize - 1) / roundSize
38 |
39 | for r := 0; r < round; r++ {
40 | proxies := make([]Proxy, 0, roundSize)
41 | for i, j := r*roundSize, (r+1)*roundSize-1; i < j && i < size; i++ {
42 | p := pl[i]
43 | proxies = append(proxies, Proxy{
44 | Base: *p.BaseInfo(),
45 | Link: p.Link(),
46 | Identifier: p.Identifier(),
47 | })
48 | }
49 | DB.Create(&proxies)
50 | }
51 | }
52 |
53 | func GetAllProxies() (proxies proxy.ProxyList) {
54 | proxies = make(proxy.ProxyList, 0)
55 | if DB == nil {
56 | return
57 | }
58 |
59 | proxiesDB := make([]Proxy, 0)
60 | DB.Select("link").Find(&proxiesDB)
61 |
62 | for _, proxyDB := range proxiesDB {
63 | if proxiesDB != nil {
64 | proxies = append(proxies, getter.String2Proxy(proxyDB.Link))
65 | }
66 | }
67 | return
68 | }
69 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net/http"
7 | _ "net/http/pprof"
8 | "os"
9 |
10 | "github.com/zu1k/proxypool/api"
11 | "github.com/zu1k/proxypool/internal/app"
12 | "github.com/zu1k/proxypool/internal/cron"
13 | "github.com/zu1k/proxypool/internal/database"
14 | "github.com/zu1k/proxypool/pkg/proxy"
15 | )
16 |
17 | var configFilePath = ""
18 |
19 | func main() {
20 | go func() {
21 | http.ListenAndServe("0.0.0.0:6060", nil)
22 | }()
23 |
24 | flag.StringVar(&configFilePath, "c", "", "path to config file: config.yaml")
25 | flag.Parse()
26 |
27 | if configFilePath == "" {
28 | configFilePath = os.Getenv("CONFIG_FILE")
29 | }
30 | if configFilePath == "" {
31 | configFilePath = "config.yaml"
32 | }
33 | err := app.InitConfigAndGetters(configFilePath)
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | database.InitTables()
39 | proxy.InitGeoIpDB()
40 | fmt.Println("Do the first crawl...")
41 | go app.CrawlGo()
42 | go cron.Cron()
43 | api.Run()
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/getter/base.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | "sync"
7 |
8 | "github.com/zu1k/proxypool/pkg/proxy"
9 | "github.com/zu1k/proxypool/pkg/tool"
10 | )
11 |
12 | type Getter interface {
13 | Get() proxy.ProxyList
14 | Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup)
15 | }
16 |
17 | type creator func(options tool.Options) (getter Getter, err error)
18 |
19 | var creatorMap = make(map[string]creator)
20 |
21 | func Register(sourceType string, c creator) {
22 | creatorMap[sourceType] = c
23 | }
24 |
25 | func NewGetter(sourceType string, options tool.Options) (getter Getter, err error) {
26 | c, ok := creatorMap[sourceType]
27 | if ok {
28 | return c(options)
29 | }
30 | return nil, ErrorCreaterNotSupported
31 | }
32 |
33 | func String2Proxy(link string) proxy.Proxy {
34 | var err error
35 | var data proxy.Proxy
36 | if strings.HasPrefix(link, "ssr://") {
37 | data, err = proxy.ParseSSRLink(link)
38 | } else if strings.HasPrefix(link, "vmess://") {
39 | data, err = proxy.ParseVmessLink(link)
40 | } else if strings.HasPrefix(link, "ss://") {
41 | data, err = proxy.ParseSSLink(link)
42 | } else if strings.HasPrefix(link, "trojan://") {
43 | data, err = proxy.ParseTrojanLink(link)
44 | }
45 | if err != nil {
46 | return nil
47 | }
48 | return data
49 | }
50 |
51 | func StringArray2ProxyArray(origin []string) proxy.ProxyList {
52 | results := make(proxy.ProxyList, 0)
53 | for _, link := range origin {
54 | results = append(results, String2Proxy(link))
55 | }
56 | return results
57 | }
58 |
59 | func GrepLinksFromString(text string) []string {
60 | results := proxy.GrepSSRLinkFromString(text)
61 | results = append(results, proxy.GrepVmessLinkFromString(text)...)
62 | results = append(results, proxy.GrepSSLinkFromString(text)...)
63 | results = append(results, proxy.GrepTrojanLinkFromString(text)...)
64 | return results
65 | }
66 |
67 | func FuzzParseProxyFromString(text string) proxy.ProxyList {
68 | return StringArray2ProxyArray(GrepLinksFromString(text))
69 | }
70 |
71 | var (
72 | ErrorUrlNotFound = errors.New("url should be specified")
73 | ErrorCreaterNotSupported = errors.New("type not supported")
74 | )
75 |
76 | func AssertTypeStringNotNull(i interface{}) (str string, err error) {
77 | switch i.(type) {
78 | case string:
79 | str = i.(string)
80 | if str == "" {
81 | return "", errors.New("string is null")
82 | }
83 | return str, nil
84 | default:
85 | return "", errors.New("type is not string")
86 | }
87 | return "", nil
88 | }
89 |
--------------------------------------------------------------------------------
/pkg/getter/subscribe.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "strings"
7 | "sync"
8 |
9 | "github.com/zu1k/proxypool/pkg/proxy"
10 | "github.com/zu1k/proxypool/pkg/tool"
11 | )
12 |
13 | func init() {
14 | Register("subscribe", NewSubscribe)
15 | }
16 |
17 | type Subscribe struct {
18 | Url string
19 | }
20 |
21 | func (s *Subscribe) Get() proxy.ProxyList {
22 | resp, err := tool.GetHttpClient().Get(s.Url)
23 | if err != nil {
24 | return nil
25 | }
26 | defer resp.Body.Close()
27 | body, err := ioutil.ReadAll(resp.Body)
28 | if err != nil {
29 | return nil
30 | }
31 |
32 | nodesString, err := tool.Base64DecodeString(string(body))
33 | if err != nil {
34 | return nil
35 | }
36 | nodesString = strings.ReplaceAll(nodesString, "\t", "")
37 |
38 | nodes := strings.Split(nodesString, "\n")
39 | return StringArray2ProxyArray(nodes)
40 | }
41 |
42 | func (s *Subscribe) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
43 | defer wg.Done()
44 | nodes := s.Get()
45 | log.Printf("STATISTIC: Subscribe\tcount=%d\turl=%s\n", len(nodes), s.Url)
46 | for _, node := range nodes {
47 | pc <- node
48 | }
49 | }
50 |
51 | func NewSubscribe(options tool.Options) (getter Getter, err error) {
52 | urlInterface, found := options["url"]
53 | if found {
54 | url, err := AssertTypeStringNotNull(urlInterface)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return &Subscribe{
59 | Url: url,
60 | }, nil
61 | }
62 | return nil, ErrorUrlNotFound
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/getter/tgchannel.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sync"
7 |
8 | "github.com/gocolly/colly"
9 | "github.com/zu1k/proxypool/pkg/proxy"
10 | "github.com/zu1k/proxypool/pkg/tool"
11 | )
12 |
13 | func init() {
14 | Register("tgchannel", NewTGChannelGetter)
15 | }
16 |
17 | type TGChannelGetter struct {
18 | c *colly.Collector
19 | NumNeeded int
20 | results []string
21 | Url string
22 | }
23 |
24 | func NewTGChannelGetter(options tool.Options) (getter Getter, err error) {
25 | num, found := options["num"]
26 | t := 200
27 | switch num.(type) {
28 | case int:
29 | t = num.(int)
30 | case float64:
31 | t = int(num.(float64))
32 | }
33 |
34 | if !found || t <= 0 {
35 | t = 200
36 | }
37 | urlInterface, found := options["channel"]
38 | if found {
39 | url, err := AssertTypeStringNotNull(urlInterface)
40 | if err != nil {
41 | return nil, err
42 | }
43 | return &TGChannelGetter{
44 | c: tool.GetColly(),
45 | NumNeeded: t,
46 | Url: "https://t.me/s/" + url,
47 | }, nil
48 | }
49 | return nil, ErrorUrlNotFound
50 | }
51 |
52 | func (g *TGChannelGetter) Get() proxy.ProxyList {
53 | result := make(proxy.ProxyList, 0)
54 | g.results = make([]string, 0)
55 | // 找到所有的文字消息
56 | g.c.OnHTML("div.tgme_widget_message_text", func(e *colly.HTMLElement) {
57 | g.results = append(g.results, GrepLinksFromString(e.Text)...)
58 | // 抓取到http链接,有可能是订阅链接或其他链接,无论如何试一下
59 | subUrls := urlRe.FindAllString(e.Text, -1)
60 | for _, url := range subUrls {
61 | result = append(result, (&Subscribe{Url: url}).Get()...)
62 | }
63 | })
64 |
65 | // 找到之前消息页面的链接,加入访问队列
66 | g.c.OnHTML("link[rel=prev]", func(e *colly.HTMLElement) {
67 | if len(g.results) < g.NumNeeded {
68 | _ = e.Request.Visit(e.Attr("href"))
69 | }
70 | })
71 |
72 | g.results = make([]string, 0)
73 | err := g.c.Visit(g.Url)
74 | if err != nil {
75 | _ = fmt.Errorf("%s", err.Error())
76 | }
77 | return append(result, StringArray2ProxyArray(g.results)...)
78 | }
79 |
80 | func (g *TGChannelGetter) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
81 | defer wg.Done()
82 | nodes := g.Get()
83 | log.Printf("STATISTIC: TGChannel\tcount=%d\turl=%s\n", len(nodes), g.Url)
84 | for _, node := range nodes {
85 | pc <- node
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/pkg/getter/web_fanqiangdang.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strings"
7 | "sync"
8 |
9 | "github.com/gocolly/colly"
10 | "github.com/zu1k/proxypool/pkg/proxy"
11 | "github.com/zu1k/proxypool/pkg/tool"
12 | )
13 |
14 | func init() {
15 | Register("web-fanqiangdang", NewWebFanqiangdangGetter)
16 | }
17 |
18 | type WebFanqiangdang struct {
19 | c *colly.Collector
20 | Url string
21 | results proxy.ProxyList
22 | }
23 |
24 | func NewWebFanqiangdangGetter(options tool.Options) (getter Getter, err error) {
25 | urlInterface, found := options["url"]
26 | if found {
27 | url, err := AssertTypeStringNotNull(urlInterface)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return &WebFanqiangdang{
32 | c: colly.NewCollector(),
33 | Url: url,
34 | }, nil
35 | }
36 | return nil, ErrorUrlNotFound
37 | }
38 |
39 | func (w *WebFanqiangdang) Get() proxy.ProxyList {
40 | w.results = make(proxy.ProxyList, 0)
41 | w.c.OnHTML("td.t_f", func(e *colly.HTMLElement) {
42 | w.results = append(w.results, FuzzParseProxyFromString(e.Text)...)
43 | subUrls := urlRe.FindAllString(e.Text, -1)
44 | for _, url := range subUrls {
45 | w.results = append(w.results, (&Subscribe{Url: url}).Get()...)
46 | }
47 | })
48 |
49 | w.c.OnHTML("th.new>a[href]", func(e *colly.HTMLElement) {
50 | url := e.Attr("href")
51 | if strings.HasPrefix(url, "https://fanqiangdang.com/thread") {
52 | _ = e.Request.Visit(url)
53 | }
54 | })
55 |
56 | w.results = make(proxy.ProxyList, 0)
57 | err := w.c.Visit(w.Url)
58 | if err != nil {
59 | _ = fmt.Errorf("%s", err.Error())
60 | }
61 |
62 | return w.results
63 | }
64 |
65 | func (w *WebFanqiangdang) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
66 | defer wg.Done()
67 | nodes := w.Get()
68 | log.Printf("STATISTIC: Fanqiangdang\tcount=%d\turl=%s\n", len(nodes), w.Url)
69 | for _, node := range nodes {
70 | pc <- node
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/getter/web_free_ssr_xyz.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "log"
7 | "sync"
8 |
9 | "github.com/zu1k/proxypool/pkg/proxy"
10 | "github.com/zu1k/proxypool/pkg/tool"
11 | )
12 |
13 | func init() {
14 | Register("web-freessrxyz", NewWebFreessrxyzGetter)
15 | }
16 |
17 | const (
18 | freessrxyzSsrLink = "https://api.free-ssr.xyz/ssr"
19 | freessrxyzV2rayLink = "https://api.free-ssr.xyz/v2ray"
20 | )
21 |
22 | type WebFreessrXyz struct {
23 | }
24 |
25 | func NewWebFreessrxyzGetter(options tool.Options) (getter Getter, err error) {
26 | return &WebFreessrXyz{}, nil
27 | }
28 |
29 | func (w *WebFreessrXyz) Get() proxy.ProxyList {
30 | results := freessrxyzFetch(freessrxyzSsrLink)
31 | results = append(results, freessrxyzFetch(freessrxyzV2rayLink)...)
32 | return results
33 | }
34 |
35 | func (w *WebFreessrXyz) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
36 | defer wg.Done()
37 | nodes := w.Get()
38 | log.Printf("STATISTIC: FreeSSRxyz\tcount=%d\turl=%s\n", len(nodes), "api.free-ssr.xyz")
39 | for _, node := range nodes {
40 | pc <- node
41 | }
42 | }
43 |
44 | func freessrxyzFetch(link string) proxy.ProxyList {
45 | resp, err := tool.GetHttpClient().Get(link)
46 | if err != nil {
47 | return nil
48 | }
49 | defer resp.Body.Close()
50 | body, err := ioutil.ReadAll(resp.Body)
51 | if err != nil {
52 | return nil
53 | }
54 |
55 | type node struct {
56 | Url string `json:"url"`
57 | }
58 | ssrs := make([]node, 0)
59 | err = json.Unmarshal(body, &ssrs)
60 | if err != nil {
61 | return nil
62 | }
63 |
64 | result := make([]string, 0)
65 | for _, node := range ssrs {
66 | result = append(result, node.Url)
67 | }
68 |
69 | return StringArray2ProxyArray(result)
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/getter/web_fuzz.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "sync"
7 |
8 | "github.com/zu1k/proxypool/pkg/proxy"
9 | "github.com/zu1k/proxypool/pkg/tool"
10 | )
11 |
12 | func init() {
13 | Register("webfuzz", NewWebFuzzGetter)
14 | }
15 |
16 | type WebFuzz struct {
17 | Url string
18 | }
19 |
20 | func (w *WebFuzz) Get() proxy.ProxyList {
21 | resp, err := tool.GetHttpClient().Get(w.Url)
22 | if err != nil {
23 | return nil
24 | }
25 | defer resp.Body.Close()
26 | body, err := ioutil.ReadAll(resp.Body)
27 | if err != nil {
28 | return nil
29 | }
30 |
31 | return FuzzParseProxyFromString(string(body))
32 | }
33 |
34 | func (w *WebFuzz) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
35 | defer wg.Done()
36 | nodes := w.Get()
37 | log.Printf("STATISTIC: WebFuzz\tcount=%d\turl=%s\n", len(nodes), w.Url)
38 | for _, node := range nodes {
39 | pc <- node
40 | }
41 | }
42 |
43 | func NewWebFuzzGetter(options tool.Options) (getter Getter, err error) {
44 | urlInterface, found := options["url"]
45 | if found {
46 | url, err := AssertTypeStringNotNull(urlInterface)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return &WebFuzz{Url: url}, nil
51 | }
52 | return nil, ErrorUrlNotFound
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/getter/web_fuzz_sub.go:
--------------------------------------------------------------------------------
1 | package getter
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "regexp"
7 | "sync"
8 |
9 | "github.com/zu1k/proxypool/pkg/proxy"
10 | "github.com/zu1k/proxypool/pkg/tool"
11 | )
12 |
13 | func init() {
14 | Register("webfuzzsub", NewWebFuzzSubGetter)
15 | }
16 |
17 | type WebFuzzSub struct {
18 | Url string
19 | }
20 |
21 | func (w *WebFuzzSub) Get() proxy.ProxyList {
22 | resp, err := tool.GetHttpClient().Get(w.Url)
23 | if err != nil {
24 | return nil
25 | }
26 | defer resp.Body.Close()
27 | body, err := ioutil.ReadAll(resp.Body)
28 | if err != nil {
29 | return nil
30 | }
31 | text := string(body)
32 | subUrls := urlRe.FindAllString(text, -1)
33 | result := make(proxy.ProxyList, 0)
34 | for _, url := range subUrls {
35 | result = append(result, (&Subscribe{Url: url}).Get()...)
36 | }
37 | return result
38 | }
39 |
40 | func (w *WebFuzzSub) Get2Chan(pc chan proxy.Proxy, wg *sync.WaitGroup) {
41 | defer wg.Done()
42 | nodes := w.Get()
43 | log.Printf("STATISTIC: WebFuzzSub\tcount=%d\turl=%s\n", len(nodes), w.Url)
44 | for _, node := range nodes {
45 | pc <- node
46 | }
47 | }
48 |
49 | func NewWebFuzzSubGetter(options tool.Options) (getter Getter, err error) {
50 | urlInterface, found := options["url"]
51 | if found {
52 | url, err := AssertTypeStringNotNull(urlInterface)
53 | if err != nil {
54 | return nil, err
55 | }
56 | return &WebFuzzSub{Url: url}, nil
57 | }
58 | return nil, ErrorUrlNotFound
59 | }
60 |
61 | var urlRe = regexp.MustCompile(urlPattern)
62 |
63 | const (
64 | // 匹配 IP4
65 | ip4Pattern = `((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)`
66 |
67 | // 匹配 IP6,参考以下网页内容:
68 | // http://blog.csdn.net/jiangfeng08/article/details/7642018
69 | ip6Pattern = `(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|` +
70 | `(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|` +
71 | `(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|` +
72 | `(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|` +
73 | `(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|` +
74 | `(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|` +
75 | `(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|` +
76 | `(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))`
77 |
78 | // 同时匹配 IP4 和 IP6
79 | ipPattern = "(" + ip4Pattern + ")|(" + ip6Pattern + ")"
80 |
81 | // 匹配域名
82 | domainPattern = `[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,62})*(\.[a-zA-Z][a-zA-Z0-9]{0,10}){1}`
83 |
84 | // 匹配 URL
85 | urlPattern = `((https|http)?://)?` + // 协议
86 | `(([0-9a-zA-Z]+:)?[0-9a-zA-Z_-]+@)?` + // pwd:user@
87 | "(" + ipPattern + "|(" + domainPattern + "))" + // IP 或域名
88 | `(:\d{1,5})?` + // 端口
89 | `(/+[a-zA-Z0-9][a-zA-Z0-9_.-]*)*/*` + // path
90 | `(\?([a-zA-Z0-9_-]+(=.*&?)*)*)*` // query
91 | )
92 |
--------------------------------------------------------------------------------
/pkg/provider/base.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/zu1k/proxypool/pkg/proxy"
7 | )
8 |
9 | type Provider interface {
10 | Provide() string
11 | }
12 |
13 | type Base struct {
14 | Proxies *proxy.ProxyList `yaml:"proxies"`
15 | Types string `yaml:"type"`
16 | Country string `yaml:"country"`
17 | NotCountry string `yaml:"not_country"`
18 | }
19 |
20 | func (b *Base) preFilter() {
21 | proxies := make(proxy.ProxyList, 0)
22 |
23 | needFilterType := true
24 | needFilterCountry := true
25 | needFilterNotCountry := true
26 | if b.Types == "" || b.Types == "all" {
27 | needFilterType = false
28 | }
29 | if b.Country == "" || b.Country == "all" {
30 | needFilterCountry = false
31 | }
32 | if b.NotCountry == "" {
33 | needFilterNotCountry = false
34 | }
35 | types := strings.Split(b.Types, ",")
36 | countries := strings.Split(b.Country, ",")
37 | notCountries := strings.Split(b.NotCountry, ",")
38 |
39 | bProxies := *b.Proxies
40 | for _, p := range bProxies {
41 | if needFilterType {
42 | typeOk := false
43 | for _, t := range types {
44 | if p.TypeName() == t {
45 | typeOk = true
46 | break
47 | }
48 | }
49 | if !typeOk {
50 | goto exclude
51 | }
52 | }
53 |
54 | if needFilterNotCountry {
55 | for _, c := range notCountries {
56 | if strings.Contains(p.BaseInfo().Name, c) {
57 | goto exclude
58 | }
59 | }
60 | }
61 |
62 | if needFilterCountry {
63 | countryOk := false
64 | for _, c := range countries {
65 | if strings.Contains(p.BaseInfo().Name, c) {
66 | countryOk = true
67 | break
68 | }
69 | }
70 | if !countryOk {
71 | goto exclude
72 | }
73 | }
74 |
75 | proxies = append(proxies, p)
76 | exclude:
77 | }
78 |
79 | b.Proxies = &proxies
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/provider/clash.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/zu1k/proxypool/pkg/tool"
7 |
8 | "github.com/zu1k/proxypool/pkg/proxy"
9 | )
10 |
11 | type Clash struct {
12 | Base
13 | }
14 |
15 | func (c Clash) CleanProxies() (proxies proxy.ProxyList) {
16 | proxies = make(proxy.ProxyList, 0)
17 | for _, p := range *c.Proxies {
18 | if checkClashSupport(p) {
19 | proxies = append(proxies, p)
20 | }
21 | }
22 | return
23 | }
24 |
25 | func (c Clash) Provide() string {
26 | c.preFilter()
27 |
28 | var resultBuilder strings.Builder
29 | resultBuilder.WriteString("proxies:\n")
30 | for _, p := range *c.Proxies {
31 | if checkClashSupport(p) {
32 | resultBuilder.WriteString(p.ToClash() + "\n")
33 | }
34 | }
35 | return resultBuilder.String()
36 | }
37 |
38 | func checkClashSupport(p proxy.Proxy) bool {
39 | switch p.TypeName() {
40 | case "ssr":
41 | ssr := p.(*proxy.ShadowsocksR)
42 | if tool.CheckInList(proxy.SSRCipherList, ssr.Cipher) && tool.CheckInList(ssrProtocolList, ssr.Protocol) && tool.CheckInList(ssrObfsList, ssr.Obfs) {
43 | return true
44 | }
45 | case "vmess":
46 | vmess := p.(*proxy.Vmess)
47 | if tool.CheckInList(vmessCipherList, vmess.Cipher) {
48 | return true
49 | }
50 | case "ss":
51 | ss := p.(*proxy.Shadowsocks)
52 | if tool.CheckInList(proxy.SSCipherList, ss.Cipher) {
53 | return true
54 | }
55 | case "trojan":
56 | return true
57 | default:
58 | return false
59 | }
60 | return false
61 | }
62 |
63 | var ssrObfsList = []string{
64 | "plain",
65 | "http_simple",
66 | "http_post",
67 | "random_head",
68 | "tls1.2_ticket_auth",
69 | "tls1.2_ticket_fastauth",
70 | }
71 |
72 | var ssrProtocolList = []string{
73 | "origin",
74 | "verify_deflate",
75 | "verify_sha1",
76 | "auth_sha1",
77 | "auth_sha1_v2",
78 | "auth_sha1_v4",
79 | "auth_aes128_md5",
80 | "auth_aes128_sha1",
81 | "auth_chain_a",
82 | "auth_chain_b",
83 | }
84 |
85 | var vmessCipherList = []string{
86 | "auto",
87 | "aes-128-gcm",
88 | "chacha20-poly1305",
89 | "none",
90 | }
91 |
--------------------------------------------------------------------------------
/pkg/provider/ssrsub.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/zu1k/proxypool/pkg/proxy"
7 |
8 | "github.com/zu1k/proxypool/pkg/tool"
9 | )
10 |
11 | type SSRSub struct {
12 | Base
13 | }
14 |
15 | func (sub SSRSub) Provide() string {
16 | sub.Types = "ssr,ss"
17 | sub.preFilter()
18 | var resultBuilder strings.Builder
19 | for _, p := range *sub.Proxies {
20 | if p.TypeName() == "ssr" {
21 | resultBuilder.WriteString(p.Link() + "\n")
22 | } else if p.TypeName() == "ss" {
23 | ssr, err := proxy.SS2SSR(p.(*proxy.Shadowsocks))
24 | if err == nil {
25 | resultBuilder.WriteString(ssr.Link() + "\n")
26 | }
27 | }
28 | }
29 | return tool.Base64EncodeString(resultBuilder.String(), false)
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/provider/sssub.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "encoding/json"
5 | "strconv"
6 |
7 | "github.com/zu1k/proxypool/pkg/proxy"
8 | )
9 |
10 | type SSSub struct {
11 | Base
12 | }
13 |
14 | type ssJson struct {
15 | Remarks string `json:"remarks"`
16 | Server string `json:"server"`
17 | ServerPort string `json:"server_port"`
18 | Method string `json:"method"`
19 | Password string `json:"password"`
20 | Plugin string `json:"plugin"`
21 | PluginOpts map[string]interface{} `json:"plugin_opts"`
22 | }
23 |
24 | func (sub SSSub) Provide() string {
25 | sub.Types = "ss,ssr"
26 | sub.preFilter()
27 | proxies := make([]ssJson, 0, sub.Proxies.Len())
28 | for _, p := range *sub.Proxies {
29 | var pp *proxy.Shadowsocks
30 |
31 | if p.TypeName() == "ssr" {
32 | var err error
33 | pp, err = proxy.SSR2SS(p.(*proxy.ShadowsocksR))
34 | if err != nil {
35 | continue
36 | }
37 | } else if p.TypeName() == "ss" {
38 | pp = p.(*proxy.Shadowsocks)
39 | }
40 |
41 | proxies = append(proxies, ssJson{
42 | Remarks: pp.Name,
43 | Server: pp.Server,
44 | ServerPort: strconv.Itoa(pp.Port),
45 | Method: pp.Cipher,
46 | Password: pp.Password,
47 | Plugin: pp.Plugin,
48 | PluginOpts: pp.PluginOpts,
49 | })
50 | }
51 | text, err := json.Marshal(proxies)
52 | if err != nil {
53 | return ""
54 | }
55 | return string(text)
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/provider/surge.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/zu1k/proxypool/pkg/tool"
7 |
8 | "github.com/zu1k/proxypool/pkg/proxy"
9 | )
10 |
11 | type Surge struct {
12 | Base
13 | }
14 |
15 | func (s Surge) Provide() string {
16 | s.preFilter()
17 |
18 | var resultBuilder strings.Builder
19 | for _, p := range *s.Proxies {
20 | if checkSurgeSupport(p) {
21 | resultBuilder.WriteString(p.ToClash() + "\n")
22 | }
23 | }
24 | return resultBuilder.String()
25 | }
26 |
27 | func checkSurgeSupport(p proxy.Proxy) bool {
28 | switch p.(type) {
29 | case *proxy.ShadowsocksR:
30 | return false
31 | case *proxy.Vmess:
32 | return true
33 | case *proxy.Shadowsocks:
34 | ss := p.(*proxy.Shadowsocks)
35 | if tool.CheckInList(proxy.SSCipherList, ss.Cipher) {
36 | return true
37 | }
38 | default:
39 | return false
40 | }
41 | return false
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/provider/vmesssub.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/zu1k/proxypool/pkg/tool"
7 | )
8 |
9 | type VmessSub struct {
10 | Base
11 | }
12 |
13 | func (sub VmessSub) Provide() string {
14 | sub.Types = "vmess"
15 | sub.preFilter()
16 | var resultBuilder strings.Builder
17 | for _, p := range *sub.Proxies {
18 | resultBuilder.WriteString(p.Link() + "\n")
19 | }
20 | return tool.Base64EncodeString(resultBuilder.String(), false)
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/proxy/base.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | type Base struct {
4 | Name string `yaml:"name" json:"name" gorm:"index"`
5 | Server string `yaml:"server" json:"server" gorm:"index"`
6 | Port int `yaml:"port" json:"port" gorm:"index"`
7 | Type string `yaml:"type" json:"type" gorm:"index"`
8 | UDP bool `yaml:"udp,omitempty" json:"udp,omitempty"`
9 | Country string `yaml:"country,omitempty" json:"country,omitempty" gorm:"index"`
10 | Useable bool `yaml:"useable,omitempty" json:"useable,omitempty" gorm:"index"`
11 | }
12 |
13 | func (b *Base) TypeName() string {
14 | if b.Type == "" {
15 | return "unknown"
16 | }
17 | return b.Type
18 | }
19 |
20 | func (b *Base) SetName(name string) {
21 | b.Name = name
22 | }
23 |
24 | func (b *Base) SetIP(ip string) {
25 | b.Server = ip
26 | }
27 |
28 | func (b *Base) BaseInfo() *Base {
29 | return b
30 | }
31 |
32 | func (b *Base) Clone() Base {
33 | c := *b
34 | return c
35 | }
36 |
37 | func (b *Base) SetUseable(useable bool) {
38 | b.Useable = useable
39 | }
40 |
41 | func (b *Base) SetCountry(country string) {
42 | b.Country = country
43 | }
44 |
45 | type Proxy interface {
46 | String() string
47 | ToClash() string
48 | ToSurge() string
49 | Link() string
50 | Identifier() string
51 | SetName(name string)
52 | SetIP(ip string)
53 | TypeName() string
54 | BaseInfo() *Base
55 | Clone() Proxy
56 | SetUseable(useable bool)
57 | SetCountry(country string)
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/proxy/check.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "sync"
8 | "time"
9 |
10 | "github.com/ivpusic/grpool"
11 |
12 | "github.com/Dreamacro/clash/adapters/outbound"
13 | )
14 |
15 | const defaultURLTestTimeout = time.Second * 5
16 |
17 | func testDelay(p Proxy) (delay uint16, err error) {
18 | pmap := make(map[string]interface{})
19 | err = json.Unmarshal([]byte(p.String()), &pmap)
20 | if err != nil {
21 | return
22 | }
23 |
24 | pmap["port"] = int(pmap["port"].(float64))
25 | if p.TypeName() == "vmess" {
26 | pmap["alterId"] = int(pmap["alterId"].(float64))
27 | }
28 |
29 | clashProxy, err := outbound.ParseProxy(pmap)
30 | if err != nil {
31 | fmt.Println(err.Error())
32 | return
33 | }
34 |
35 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
36 | delay, err = clashProxy.URLTest(ctx, "http://www.gstatic.com/generate_204")
37 | cancel()
38 | return delay, err
39 | }
40 |
41 | func CleanBadProxiesWithGrpool(proxies []Proxy) (cproxies []Proxy) {
42 | pool := grpool.NewPool(500, 200)
43 |
44 | c := make(chan checkResult)
45 | defer close(c)
46 |
47 | pool.WaitCount(len(proxies))
48 | go func() {
49 | for _, p := range proxies {
50 | pp := p
51 | pool.JobQueue <- func() {
52 | defer pool.JobDone()
53 | delay, err := testDelay(pp)
54 | if err == nil {
55 | c <- checkResult{
56 | name: pp.Identifier(),
57 | delay: delay,
58 | }
59 | }
60 | }
61 | }
62 | }()
63 | done := make(chan struct{})
64 | defer close(done)
65 |
66 | go func() {
67 | pool.WaitAll()
68 | pool.Release()
69 | done <- struct{}{}
70 | }()
71 |
72 | okMap := make(map[string]struct{})
73 | for {
74 | select {
75 | case r := <-c:
76 | if r.delay > 0 {
77 | okMap[r.name] = struct{}{}
78 | }
79 | case <-done:
80 | cproxies = make(ProxyList, 0, 500)
81 | for _, p := range proxies {
82 | if _, ok := okMap[p.Identifier()]; ok {
83 | cproxies = append(cproxies, p.Clone())
84 | }
85 | }
86 | return
87 | }
88 | }
89 | }
90 |
91 | func CleanBadProxies(proxies []Proxy) (cproxies []Proxy) {
92 | c := make(chan checkResult, 40)
93 | wg := &sync.WaitGroup{}
94 | wg.Add(len(proxies))
95 | for _, p := range proxies {
96 | go testProxyDelayToChan(p, c, wg)
97 | }
98 | go func() {
99 | wg.Wait()
100 | close(c)
101 | }()
102 |
103 | okMap := make(map[string]struct{})
104 | for r := range c {
105 | if r.delay > 0 {
106 | okMap[r.name] = struct{}{}
107 | }
108 | }
109 | cproxies = make(ProxyList, 0, 500)
110 | for _, p := range proxies {
111 | if _, ok := okMap[p.Identifier()]; ok {
112 | p.SetUseable(true)
113 | cproxies = append(cproxies, p.Clone())
114 | } else {
115 | p.SetUseable(false)
116 | }
117 | }
118 | return
119 | }
120 |
121 | type checkResult struct {
122 | name string
123 | delay uint16
124 | }
125 |
126 | func testProxyDelayToChan(p Proxy, c chan checkResult, wg *sync.WaitGroup) {
127 | defer wg.Done()
128 | delay, err := testDelay(p)
129 | if err == nil {
130 | c <- checkResult{
131 | name: p.Identifier(),
132 | delay: delay,
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/pkg/proxy/convert.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/zu1k/proxypool/pkg/tool"
7 | )
8 |
9 | func SS2SSR(ss *Shadowsocks) (ssr *ShadowsocksR, err error) {
10 | if ss == nil {
11 | return nil, errors.New("ss is nil")
12 | }
13 | if !tool.CheckInList(SSRCipherList, ss.Cipher) {
14 | return nil, errors.New("cipher not support")
15 | }
16 | base := ss.Base
17 | base.Type = "ssr"
18 | return &ShadowsocksR{
19 | Base: base,
20 | Password: ss.Password,
21 | Cipher: ss.Cipher,
22 | Protocol: "origin",
23 | Obfs: "plain",
24 | Group: "proxy.tgbot.co",
25 | }, nil
26 | }
27 |
28 | func SSR2SS(ssr *ShadowsocksR) (ss *Shadowsocks, err error) {
29 | if ssr == nil {
30 | return nil, errors.New("ssr is nil")
31 | }
32 | if !tool.CheckInList(SSCipherList, ssr.Cipher) {
33 | return nil, errors.New("cipher not support")
34 | }
35 | if ssr.Protocol != "origin" || ssr.Obfs != "plain" {
36 | return nil, errors.New("protocol or obfs not allowed")
37 | }
38 | base := ssr.Base
39 | base.Type = "ss"
40 | return &Shadowsocks{
41 | Base: base,
42 | Password: ssr.Password,
43 | Cipher: ssr.Cipher,
44 | Plugin: "",
45 | PluginOpts: nil,
46 | }, nil
47 | }
48 |
49 | var SSRCipherList = []string{
50 | "aes-128-cfb",
51 | "aes-192-cfb",
52 | "aes-256-cfb",
53 | "aes-128-ctr",
54 | "aes-192-ctr",
55 | "aes-256-ctr",
56 | "aes-128-ofb",
57 | "aes-192-ofb",
58 | "aes-256-ofb",
59 | "des-cfb",
60 | "bf-cfb",
61 | "cast5-cfb",
62 | "rc4-md5",
63 | "chacha20-ietf",
64 | "salsa20",
65 | "camellia-128-cfb",
66 | "camellia-192-cfb",
67 | "camellia-256-cfb",
68 | "idea-cfb",
69 | "rc2-cfb",
70 | "seed-cfb",
71 | }
72 |
73 | var SSCipherList = []string{
74 | "aes-128-gcm",
75 | "aes-192-gcm",
76 | "aes-256-gcm",
77 | "aes-128-cfb",
78 | "aes-192-cfb",
79 | "aes-256-cfb",
80 | "aes-128-ctr",
81 | "aes-192-ctr",
82 | "aes-256-ctr",
83 | "rc4-md5",
84 | "chacha20-ietf",
85 | "xchacha20",
86 | "chacha20-ietf-poly1305",
87 | "xchacha20-ietf-poly1305",
88 | }
89 |
--------------------------------------------------------------------------------
/pkg/proxy/geoip.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "net"
9 | "os"
10 |
11 | "github.com/oschwald/geoip2-golang"
12 | bingeoip "github.com/zu1k/proxypool/internal/bindata/geoip"
13 | )
14 |
15 | var geoIp GeoIP
16 |
17 | func InitGeoIpDB() {
18 | err := bingeoip.RestoreAsset("", "assets/GeoLite2-City.mmdb")
19 | if err != nil {
20 | panic(err)
21 | }
22 | err = bingeoip.RestoreAsset("", "assets/flags.json")
23 | if err != nil {
24 | panic(err)
25 | }
26 | geoIp = NewGeoIP("assets/GeoLite2-City.mmdb", "assets/flags.json")
27 | }
28 |
29 | // GeoIP2
30 | type GeoIP struct {
31 | db *geoip2.Reader
32 | emojiMap map[string]string
33 | }
34 |
35 | type CountryEmoji struct {
36 | Code string `json:"code"`
37 | Emoji string `json:"emoji"`
38 | }
39 |
40 | // new geoip from db file
41 | func NewGeoIP(geodb, flags string) (geoip GeoIP) {
42 | // 判断文件是否存在
43 | _, err := os.Stat(geodb)
44 | if err != nil && os.IsNotExist(err) {
45 | log.Println("文件不存在,请自行下载 Geoip2 City库,并保存在", geodb)
46 | os.Exit(1)
47 | } else {
48 | db, err := geoip2.Open(geodb)
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 | geoip.db = db
53 | }
54 |
55 | _, err = os.Stat(flags)
56 | if err != nil && os.IsNotExist(err) {
57 | log.Println("flags 文件不存在,请自行下载 flags.json,并保存在", flags)
58 | os.Exit(1)
59 | } else {
60 | data, err := ioutil.ReadFile(flags)
61 | if err != nil {
62 | log.Fatal(err)
63 | return
64 | }
65 | var countryEmojiList = make([]CountryEmoji, 0)
66 | err = json.Unmarshal(data, &countryEmojiList)
67 | if err != nil {
68 | log.Fatalln(err.Error())
69 | return
70 | }
71 |
72 | emojiMap := make(map[string]string)
73 | for _, i := range countryEmojiList {
74 | emojiMap[i.Code] = i.Emoji
75 | }
76 | geoip.emojiMap = emojiMap
77 | }
78 | return
79 | }
80 |
81 | // find ip info
82 | func (g GeoIP) Find(ipORdomain string) (ip, country string, err error) {
83 | ips, err := net.LookupIP(ipORdomain)
84 | if err != nil {
85 | return "", "", err
86 | }
87 | ip = ips[0].String()
88 |
89 | var record *geoip2.City
90 | record, err = g.db.City(ips[0])
91 | if err != nil {
92 | return
93 | }
94 | countryIsoCode := record.Country.IsoCode
95 | if countryIsoCode == "" {
96 | country = fmt.Sprintf("🏁 ZZ")
97 | }
98 | emoji, found := g.emojiMap[countryIsoCode]
99 | if found {
100 | country = fmt.Sprintf("%v %v", emoji, countryIsoCode)
101 | } else {
102 | country = fmt.Sprintf("🏁 ZZ")
103 | }
104 | return
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/proxy/link_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestSSLink(t *testing.T) {
9 | ss, err := ParseSSLink("ss://YWVzLTI1Ni1jZmI6ZUlXMERuazY5NDU0ZTZuU3d1c3B2OURtUzIwMXRRMERAMTcyLjEwNC4xNjEuNTQ6ODA5OQ==#翻墙党223.13新加坡")
10 | if err != nil {
11 | t.Error(err)
12 | }
13 | fmt.Println(ss)
14 | fmt.Println(ss.Link())
15 | ss, err = ParseSSLink(ss.Link())
16 | if err != nil {
17 | t.Error(err)
18 | }
19 | fmt.Println(ss)
20 | }
21 |
22 | func TestSSRLink(t *testing.T) {
23 | ssr, err := ParseSSRLink("ssr://MTcyLjEwNC4xNjEuNTQ6ODA5OTpvcmlnaW46YWVzLTI1Ni1jZmI6cGxhaW46WlVsWE1FUnVhelk1TkRVMFpUWnVVM2QxYzNCMk9VUnRVekl3TVhSUk1FUT0vP29iZnNwYXJhbT0mcHJvdG9wYXJhbT0mcmVtYXJrcz01Ny03NWFLWjVZV2FNakl6TGpFejVwYXc1WXFnNVoyaCZncm91cD01cGF3NVlxZzVaMmg=")
24 | if err != nil {
25 | t.Error(err)
26 | }
27 | fmt.Println(ssr)
28 | fmt.Println(ssr.Link())
29 | ssr, err = ParseSSRLink(ssr.Link())
30 | if err != nil {
31 | t.Error(err)
32 | }
33 | fmt.Println(ssr)
34 | }
35 |
36 | func TestTrojanLink(t *testing.T) {
37 | trojan, err := ParseTrojanLink("trojan://65474277@sqcu.hostmsu.ru:55551?allowinsecure=0&peer=mza.hkfq.xyz&mux=1&ws=0&wspath=&wshost=&ss=0&ssmethod=aes-128-gcm&sspasswd=&group=#%E9%A6%99%E6%B8%AFCN2-MZA%E8%8A%82%E7%82%B9-%E5%AE%BF%E8%BF%81%E8%81%94%E9%80%9A%E4%B8%AD%E8%BD%AC")
38 | if err != nil {
39 | t.Error(err)
40 | }
41 | fmt.Println(trojan)
42 | fmt.Println(trojan.Link())
43 | trojan, err = ParseTrojanLink(trojan.Link())
44 | if err != nil {
45 | t.Error(err)
46 | }
47 | fmt.Println(trojan)
48 | }
49 |
50 | func TestVmessLink(t *testing.T) {
51 | v, err := ParseVmessLink("vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIuW+ruS/oeWFrOS8l+WPtyDlpJrlvannmoTlpKfljYPkuJbnlYwiLA0KICAiYWRkIjogInMyNzEuc25vZGUueHl6IiwNCiAgInBvcnQiOiAiNDQzIiwNCiAgImlkIjogIjZhOTAwZDYzLWNiOTItMzVhMC1hZWYwLTNhMGMxMWFhODUyMyIsDQogICJhaWQiOiAiMSIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAiczI3MS5zbm9kZS54eXoiLA0KICAicGF0aCI6ICIvcGFuZWwiLA0KICAidGxzIjogInRscyINCn0=")
52 | if err != nil {
53 | t.Error(err)
54 | }
55 | fmt.Println(v)
56 | fmt.Println(v.Link())
57 | v, err = ParseVmessLink(v.Link())
58 | if err != nil {
59 | t.Error(err)
60 | }
61 | fmt.Println(v)
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/proxy/proxies.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "strings"
7 | "sync"
8 | )
9 |
10 | type ProxyList []Proxy
11 |
12 | func (ps ProxyList) Len() int {
13 | return len(ps)
14 | }
15 |
16 | func (ps ProxyList) TypeLen(t string) int {
17 | l := 0
18 | for _, p := range ps {
19 | if p.TypeName() == t {
20 | l++
21 | }
22 | }
23 | return l
24 | }
25 |
26 | var sortType = make(map[string]int)
27 |
28 | func init() {
29 | sortType["ss"] = 1
30 | sortType["ssr"] = 2
31 | sortType["vmess"] = 3
32 | sortType["trojan"] = 4
33 | }
34 |
35 | func (ps ProxyList) Less(i, j int) bool {
36 | if ps[i].BaseInfo().Name == ps[j].BaseInfo().Name {
37 | return sortType[ps[i].BaseInfo().Type] < sortType[ps[j].BaseInfo().Type]
38 | } else {
39 | return ps[i].BaseInfo().Name < ps[j].BaseInfo().Name
40 | }
41 | }
42 |
43 | func (ps ProxyList) Swap(i, j int) {
44 | ps[i], ps[j] = ps[j], ps[i]
45 | }
46 |
47 | func (ps ProxyList) Deduplication() ProxyList {
48 | result := make(ProxyList, 0, len(ps))
49 | temp := map[string]struct{}{}
50 | for _, item := range ps {
51 | if item != nil {
52 | if _, ok := temp[item.Identifier()]; !ok {
53 | temp[item.Identifier()] = struct{}{}
54 | result = append(result, item)
55 | }
56 | }
57 | }
58 | return result
59 | }
60 |
61 | func (ps ProxyList) Sort() ProxyList {
62 | sort.Sort(ps)
63 | return ps
64 | }
65 |
66 | func (ps ProxyList) NameAddCounrty() ProxyList {
67 | num := len(ps)
68 | wg := &sync.WaitGroup{}
69 | wg.Add(num)
70 | for i := 0; i < num; i++ {
71 | ii := i
72 | go func() {
73 | defer wg.Done()
74 | _, country, err := geoIp.Find(ps[ii].BaseInfo().Server)
75 | if err != nil {
76 | country = "🏁 ZZ"
77 | }
78 | ps[ii].SetName(fmt.Sprintf("%s", country))
79 | ps[ii].SetCountry(country)
80 | //ps[ii].SetIP(ip)
81 | }()
82 | }
83 | wg.Wait()
84 | return ps
85 | }
86 |
87 | func (ps ProxyList) NameAddIndex() ProxyList {
88 | num := len(ps)
89 | for i := 0; i < num; i++ {
90 | ps[i].SetName(fmt.Sprintf("%s_%+02v", ps[i].BaseInfo().Name, i+1))
91 | }
92 | return ps
93 | }
94 |
95 | func (ps ProxyList) NameReIndex() ProxyList {
96 | num := len(ps)
97 | for i := 0; i < num; i++ {
98 | originName := ps[i].BaseInfo().Name
99 | country := strings.SplitN(originName, "_", 2)[0]
100 | ps[i].SetName(fmt.Sprintf("%s_%+02v", country, i+1))
101 | }
102 | return ps
103 | }
104 |
105 | func (ps ProxyList) NameAddTG() ProxyList {
106 | num := len(ps)
107 | for i := 0; i < num; i++ {
108 | ps[i].SetName(fmt.Sprintf("%s %s", ps[i].BaseInfo().Name, "@peekfun"))
109 | }
110 | return ps
111 | }
112 |
113 | func Deduplication(src ProxyList) ProxyList {
114 | result := make(ProxyList, 0, len(src))
115 | temp := map[string]struct{}{}
116 | for _, item := range src {
117 | if item != nil {
118 | if _, ok := temp[item.Identifier()]; !ok {
119 | temp[item.Identifier()] = struct{}{}
120 | result = append(result, item)
121 | }
122 | }
123 | }
124 | return result
125 | }
126 |
127 | func (ps ProxyList) Clone() ProxyList {
128 | result := make(ProxyList, 0, len(ps))
129 | for _, pp := range ps {
130 | if pp != nil {
131 | result = append(result, pp.Clone())
132 | }
133 | }
134 | return result
135 | }
136 |
--------------------------------------------------------------------------------
/pkg/proxy/shadowsocks.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "math/rand"
8 | "net"
9 | "net/url"
10 | "regexp"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/zu1k/proxypool/pkg/tool"
15 | )
16 |
17 | var (
18 | ErrorNotSSLink = errors.New("not a correct ss link")
19 | )
20 |
21 | type Shadowsocks struct {
22 | Base
23 | Password string `yaml:"password" json:"password"`
24 | Cipher string `yaml:"cipher" json:"cipher"`
25 | Plugin string `yaml:"plugin,omitempty" json:"plugin,omitempty"`
26 | PluginOpts map[string]interface{} `yaml:"plugin-opts,omitempty" json:"plugin-opts,omitempty"`
27 | }
28 |
29 | func (ss Shadowsocks) Identifier() string {
30 | return net.JoinHostPort(ss.Server, strconv.Itoa(ss.Port)) + ss.Password
31 | }
32 |
33 | func (ss Shadowsocks) String() string {
34 | data, err := json.Marshal(ss)
35 | if err != nil {
36 | return ""
37 | }
38 | return string(data)
39 | }
40 |
41 | func (ss Shadowsocks) ToClash() string {
42 | data, err := json.Marshal(ss)
43 | if err != nil {
44 | return ""
45 | }
46 | return "- " + string(data)
47 | }
48 |
49 | func (ss Shadowsocks) ToSurge() string {
50 | // node1 = ss, server, port, encrypt-method=, password=, obfs=, obfs-host=, udp-relay=false
51 | if ss.Plugin == "obfs" {
52 | text := fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s, obfs=%s, udp-relay=false",
53 | ss.Name, ss.Server, ss.Port, ss.Cipher, ss.Password, ss.PluginOpts["mode"])
54 | if ss.PluginOpts["host"].(string) != "" {
55 | text += ", obfs-host=" + ss.PluginOpts["host"].(string)
56 | }
57 | return text
58 | } else {
59 | return fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s, udp-relay=false",
60 | ss.Name, ss.Server, ss.Port, ss.Cipher, ss.Password)
61 | }
62 | }
63 |
64 | func (ss Shadowsocks) Clone() Proxy {
65 | return &ss
66 | }
67 |
68 | // https://shadowsocks.org/en/config/quick-guide.html
69 | func (ss Shadowsocks) Link() (link string) {
70 | payload := fmt.Sprintf("%s:%s@%s:%d", ss.Cipher, ss.Password, ss.Server, ss.Port)
71 | payload = tool.Base64EncodeString(payload, false)
72 | return fmt.Sprintf("ss://%s#%s", payload, ss.Name)
73 | }
74 |
75 | func ParseSSLink(link string) (*Shadowsocks, error) {
76 | if !strings.HasPrefix(link, "ss://") {
77 | return nil, ErrorNotSSRLink
78 | }
79 |
80 | uri, err := url.Parse(link)
81 | if err != nil {
82 | return nil, ErrorNotSSLink
83 | }
84 |
85 | cipher := ""
86 | password := ""
87 | if uri.User.String() == "" {
88 | // base64的情况
89 | infos, err := tool.Base64DecodeString(uri.Hostname())
90 | if err != nil {
91 | return nil, err
92 | }
93 | uri, err = url.Parse("ss://" + infos)
94 | if err != nil {
95 | return nil, err
96 | }
97 | cipher = uri.User.Username()
98 | password, _ = uri.User.Password()
99 | } else {
100 | cipherInfoString, err := tool.Base64DecodeString(uri.User.Username())
101 | if err != nil {
102 | return nil, ErrorPasswordParseFail
103 | }
104 | cipherInfo := strings.SplitN(cipherInfoString, ":", 2)
105 | if len(cipherInfo) < 2 {
106 | return nil, ErrorPasswordParseFail
107 | }
108 | cipher = strings.ToLower(cipherInfo[0])
109 | password = cipherInfo[1]
110 | }
111 | server := uri.Hostname()
112 | port, _ := strconv.Atoi(uri.Port())
113 |
114 | moreInfos := uri.Query()
115 | pluginString := moreInfos.Get("plugin")
116 | plugin := ""
117 | pluginOpts := make(map[string]interface{})
118 | if strings.Contains(pluginString, ";") {
119 | pluginInfos, err := url.ParseQuery(pluginString)
120 | if err == nil {
121 | if strings.Contains(pluginString, "obfs") {
122 | plugin = "obfs"
123 | pluginOpts["mode"] = pluginInfos.Get("obfs")
124 | pluginOpts["host"] = pluginInfos.Get("obfs-host")
125 | } else if strings.Contains(pluginString, "v2ray") {
126 | plugin = "v2ray-plugin"
127 | pluginOpts["mode"] = pluginInfos.Get("mode")
128 | pluginOpts["host"] = pluginInfos.Get("host")
129 | pluginOpts["tls"] = strings.Contains(pluginString, "tls")
130 | }
131 | }
132 | }
133 | if port == 0 || cipher == "" {
134 | return nil, ErrorNotSSLink
135 | }
136 |
137 | return &Shadowsocks{
138 | Base: Base{
139 | Name: strconv.Itoa(rand.Int()),
140 | Server: server,
141 | Port: port,
142 | Type: "ss",
143 | },
144 | Password: password,
145 | Cipher: cipher,
146 | Plugin: plugin,
147 | PluginOpts: pluginOpts,
148 | }, nil
149 | }
150 |
151 | var (
152 | ssPlainRe = regexp.MustCompile("ss://([A-Za-z0-9+/_&?=@:%.-])+")
153 | )
154 |
155 | func GrepSSLinkFromString(text string) []string {
156 | results := make([]string, 0)
157 | texts := strings.Split(text, "ss://")
158 | for _, text := range texts {
159 | results = append(results, ssPlainRe.FindAllString("ss://"+text, -1)...)
160 | }
161 | return results
162 | }
163 |
--------------------------------------------------------------------------------
/pkg/proxy/shadowsocksr.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "math/rand"
8 | "net"
9 | "net/url"
10 | "regexp"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/zu1k/proxypool/pkg/tool"
15 | )
16 |
17 | var (
18 | ErrorNotSSRLink = errors.New("not a correct ssr link")
19 | ErrorPasswordParseFail = errors.New("password parse failed")
20 | ErrorPathNotComplete = errors.New("path not complete")
21 | ErrorMissingQuery = errors.New("link missing query")
22 | ErrorProtocolParamParseFail = errors.New("protocol param parse failed")
23 | ErrorObfsParamParseFail = errors.New("obfs param parse failed")
24 | )
25 |
26 | type ShadowsocksR struct {
27 | Base
28 | Password string `yaml:"password" json:"password"`
29 | Cipher string `yaml:"cipher" json:"cipher"`
30 | Protocol string `yaml:"protocol" json:"protocol"`
31 | ProtocolParam string `yaml:"protocol-param,omitempty" json:"protocol_param,omitempty"`
32 | Obfs string `yaml:"obfs" json:"obfs"`
33 | ObfsParam string `yaml:"obfs-param,omitempty" json:"obfs_param,omitempty"`
34 | Group string `yaml:"group,omitempty" json:"group,omitempty"`
35 | }
36 |
37 | func (ssr ShadowsocksR) Identifier() string {
38 | return net.JoinHostPort(ssr.Server, strconv.Itoa(ssr.Port)) + ssr.Password + ssr.ProtocolParam
39 | }
40 |
41 | func (ssr ShadowsocksR) String() string {
42 | data, err := json.Marshal(ssr)
43 | if err != nil {
44 | return ""
45 | }
46 | return string(data)
47 | }
48 |
49 | func (ssr ShadowsocksR) ToClash() string {
50 | data, err := json.Marshal(ssr)
51 | if err != nil {
52 | return ""
53 | }
54 | return "- " + string(data)
55 | }
56 |
57 | func (ssr ShadowsocksR) ToSurge() string {
58 | return ""
59 | }
60 |
61 | func (ssr ShadowsocksR) Clone() Proxy {
62 | return &ssr
63 | }
64 |
65 | // https://github.com/HMBSbige/ShadowsocksR-Windows/wiki/SSR-QRcode-scheme
66 | func (ssr ShadowsocksR) Link() (link string) {
67 | payload := fmt.Sprintf("%s:%d:%s:%s:%s:%s",
68 | ssr.Server, ssr.Port, ssr.Protocol, ssr.Cipher, ssr.Obfs, tool.Base64EncodeString(ssr.Password, true))
69 | query := url.Values{}
70 | query.Add("obfsparam", tool.Base64EncodeString(ssr.ObfsParam, true))
71 | query.Add("protoparam", tool.Base64EncodeString(ssr.ProtocolParam, true))
72 | query.Add("remarks", tool.Base64EncodeString(ssr.Name, true))
73 | query.Add("group", tool.Base64EncodeString("proxy.tgbot.co", true))
74 | payload = tool.Base64EncodeString(fmt.Sprintf("%s/?%s", payload, query.Encode()), true)
75 | return fmt.Sprintf("ssr://%s", payload)
76 | }
77 |
78 | func ParseSSRLink(link string) (*ShadowsocksR, error) {
79 | if !strings.HasPrefix(link, "ssr") {
80 | return nil, ErrorNotSSRLink
81 | }
82 |
83 | ssrmix := strings.SplitN(link, "://", 2)
84 | if len(ssrmix) < 2 {
85 | return nil, ErrorNotSSRLink
86 | }
87 | linkPayloadBase64 := ssrmix[1]
88 | payload, err := tool.Base64DecodeString(linkPayloadBase64)
89 | if err != nil {
90 | return nil, ErrorMissingQuery
91 | }
92 |
93 | infoPayload := strings.SplitN(payload, "/?", 2)
94 | if len(infoPayload) < 2 {
95 | return nil, ErrorNotSSRLink
96 | }
97 | ssrpath := strings.Split(infoPayload[0], ":")
98 | if len(ssrpath) < 6 {
99 | return nil, ErrorPathNotComplete
100 | }
101 | // base info
102 | server := strings.ToLower(ssrpath[0])
103 | port, _ := strconv.Atoi(ssrpath[1])
104 | protocol := strings.ToLower(ssrpath[2])
105 | cipher := strings.ToLower(ssrpath[3])
106 | obfs := strings.ToLower(ssrpath[4])
107 | password, err := tool.Base64DecodeString(ssrpath[5])
108 | if err != nil {
109 | return nil, ErrorPasswordParseFail
110 | }
111 |
112 | moreInfo, _ := url.ParseQuery(infoPayload[1])
113 |
114 | // remarks
115 | remarks := moreInfo.Get("remarks")
116 | remarks, err = tool.Base64DecodeString(remarks)
117 | if err != nil {
118 | remarks = ""
119 | err = nil
120 | }
121 | if strings.ContainsAny(remarks, "\t\r\n ") {
122 | remarks = strings.ReplaceAll(remarks, "\t", "")
123 | remarks = strings.ReplaceAll(remarks, "\r", "")
124 | remarks = strings.ReplaceAll(remarks, "\n", "")
125 | remarks = strings.ReplaceAll(remarks, " ", "")
126 | }
127 |
128 | // protocol param
129 | protocolParam, err := tool.Base64DecodeString(moreInfo.Get("protoparam"))
130 | if err != nil {
131 | return nil, ErrorProtocolParamParseFail
132 | }
133 | if tool.ContainChineseChar(protocolParam) {
134 | protocolParam = ""
135 | }
136 | if strings.HasSuffix(protocol, "_compatible") {
137 | protocol = strings.ReplaceAll(protocol, "_compatible", "")
138 | }
139 |
140 | // obfs param
141 | obfsParam, err := tool.Base64DecodeString(moreInfo.Get("obfsparam"))
142 | if err != nil {
143 | return nil, ErrorObfsParamParseFail
144 | }
145 | if tool.ContainChineseChar(obfsParam) {
146 | obfsParam = ""
147 | }
148 | if strings.HasSuffix(obfs, "_compatible") {
149 | obfs = strings.ReplaceAll(obfs, "_compatible", "")
150 | }
151 |
152 | //group, err := tool.Base64DecodeString(moreInfo.Get("group"))
153 | //if err != nil {
154 | // group = ""
155 | //}
156 | group := ""
157 |
158 | return &ShadowsocksR{
159 | Base: Base{
160 | Name: remarks + "_" + strconv.Itoa(rand.Int()),
161 | Server: server,
162 | Port: port,
163 | Type: "ssr",
164 | },
165 | Password: password,
166 | Cipher: cipher,
167 | Protocol: protocol,
168 | ProtocolParam: protocolParam,
169 | Obfs: obfs,
170 | ObfsParam: obfsParam,
171 | Group: group,
172 | }, nil
173 | }
174 |
175 | var (
176 | ssrPlainRe = regexp.MustCompile("ssr://([A-Za-z0-9+/_-])+")
177 | )
178 |
179 | func GrepSSRLinkFromString(text string) []string {
180 | results := make([]string, 0)
181 | texts := strings.Split(text, "ssr://")
182 | for _, text := range texts {
183 | results = append(results, ssrPlainRe.FindAllString("ssr://"+text, -1)...)
184 | }
185 | return results
186 | }
187 |
--------------------------------------------------------------------------------
/pkg/proxy/trojan.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "math/rand"
7 | "net"
8 | "net/url"
9 | "regexp"
10 | "strconv"
11 | "strings"
12 | )
13 |
14 | var (
15 | ErrorNotTrojanink = errors.New("not a correct trojan link")
16 | )
17 |
18 | type Trojan struct {
19 | Base
20 | Password string `yaml:"password" json:"password"`
21 | ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"`
22 | SNI string `yaml:"sni,omitempty" json:"sni,omitempty"`
23 | SkipCertVerify bool `yaml:"skip-cert-verify,omitempty" json:"skip-cert-verify,omitempty"`
24 | UDP bool `yaml:"udp,omitempty" json:"udp,omitempty"`
25 | }
26 |
27 | /**
28 | - name: "trojan"
29 | type: trojan
30 | server: server
31 | port: 443
32 | password: yourpsk
33 | # udp: true
34 | # sni: example.com # aka server name
35 | # alpn:
36 | # - h2
37 | # - http/1.1
38 | # skip-cert-verify: true
39 | */
40 |
41 | func (t Trojan) Identifier() string {
42 | return net.JoinHostPort(t.Server, strconv.Itoa(t.Port)) + t.Password
43 | }
44 |
45 | func (t Trojan) String() string {
46 | data, err := json.Marshal(t)
47 | if err != nil {
48 | return ""
49 | }
50 | return string(data)
51 | }
52 |
53 | func (t Trojan) ToClash() string {
54 | data, err := json.Marshal(t)
55 | if err != nil {
56 | return ""
57 | }
58 | return "- " + string(data)
59 | }
60 |
61 | func (t Trojan) ToSurge() string {
62 | return ""
63 | }
64 |
65 | func (t Trojan) Clone() Proxy {
66 | return &t
67 | }
68 |
69 | // https://p4gefau1t.github.io/trojan-go/developer/url/
70 | func (t Trojan) Link() (link string) {
71 | query := url.Values{}
72 | if t.SNI != "" {
73 | query.Set("sni", url.QueryEscape(t.SNI))
74 | }
75 |
76 | uri := url.URL{
77 | Scheme: "trojan",
78 | User: url.User(url.QueryEscape(t.Password)),
79 | Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)),
80 | RawQuery: query.Encode(),
81 | Fragment: t.Name,
82 | }
83 |
84 | return uri.String()
85 | }
86 |
87 | func ParseTrojanLink(link string) (*Trojan, error) {
88 | if !strings.HasPrefix(link, "trojan://") && !strings.HasPrefix(link, "trojan-go://") {
89 | return nil, ErrorNotTrojanink
90 | }
91 |
92 | /**
93 | trojan-go://
94 | $(trojan-password)
95 | @
96 | trojan-host
97 | :
98 | port
99 | /?
100 | sni=$(tls-sni.com)&
101 | type=$(original|ws|h2|h2+ws)&
102 | host=$(websocket-host.com)&
103 | path=$(/websocket/path)&
104 | encryption=$(ss;aes-256-gcm;ss-password)&
105 | plugin=$(...)
106 | #$(descriptive-text)
107 | */
108 |
109 | uri, err := url.Parse(link)
110 | if err != nil {
111 | return nil, ErrorNotSSLink
112 | }
113 |
114 | password := uri.User.Username()
115 | password, _ = url.QueryUnescape(password)
116 |
117 | server := uri.Hostname()
118 | port, _ := strconv.Atoi(uri.Port())
119 |
120 | moreInfos := uri.Query()
121 | sni := moreInfos.Get("sni")
122 | sni, _ = url.QueryUnescape(sni)
123 | transformType := moreInfos.Get("type")
124 | transformType, _ = url.QueryUnescape(transformType)
125 | host := moreInfos.Get("host")
126 | host, _ = url.QueryUnescape(host)
127 | path := moreInfos.Get("path")
128 | path, _ = url.QueryUnescape(path)
129 |
130 | alpn := make([]string, 0)
131 | if transformType == "h2" {
132 | alpn = append(alpn, "h2")
133 | }
134 |
135 | if port == 0 {
136 | return nil, ErrorNotTrojanink
137 | }
138 |
139 | return &Trojan{
140 | Base: Base{
141 | Name: strconv.Itoa(rand.Int()),
142 | Server: server,
143 | Port: port,
144 | Type: "trojan",
145 | },
146 | Password: password,
147 | ALPN: alpn,
148 | UDP: true,
149 | SNI: host,
150 | SkipCertVerify: true,
151 | }, nil
152 | }
153 |
154 | var (
155 | trojanPlainRe = regexp.MustCompile("trojan(-go)?://([A-Za-z0-9+/_&?=@:%.-])+")
156 | )
157 |
158 | func GrepTrojanLinkFromString(text string) []string {
159 | results := make([]string, 0)
160 | texts := strings.Split(text, "trojan://")
161 | for _, text := range texts {
162 | results = append(results, trojanPlainRe.FindAllString("trojan://"+text, -1)...)
163 | }
164 | return results
165 | }
166 |
--------------------------------------------------------------------------------
/pkg/proxy/vmess.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "math/rand"
8 | "net"
9 | "net/url"
10 | "regexp"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/zu1k/proxypool/pkg/tool"
15 | )
16 |
17 | var (
18 | ErrorNotVmessLink = errors.New("not a correct vmess link")
19 | ErrorVmessPayloadParseFail = errors.New("vmess link payload parse failed")
20 | )
21 |
22 | type Vmess struct {
23 | Base
24 | UUID string `yaml:"uuid" json:"uuid"`
25 | AlterID int `yaml:"alterId" json:"alterId"`
26 | Cipher string `yaml:"cipher" json:"cipher"`
27 | TLS bool `yaml:"tls,omitempty" json:"tls,omitempty"`
28 | Network string `yaml:"network,omitempty" json:"network,omitempty"`
29 | HTTPOpts HTTPOptions `yaml:"http-opts,omitempty" json:"http-opts,omitempty"`
30 | WSPath string `yaml:"ws-path,omitempty" json:"ws-path,omitempty"`
31 | WSHeaders map[string]string `yaml:"ws-headers,omitempty" json:"ws-headers,omitempty"`
32 | SkipCertVerify bool `yaml:"skip-cert-verify,omitempty" json:"skip-cert-verify,omitempty"`
33 | ServerName string `yaml:"servername,omitempty" json:"servername,omitempty"`
34 | }
35 |
36 | type HTTPOptions struct {
37 | Method string `yaml:"method,omitempty" json:"method,omitempty"`
38 | Path []string `yaml:"path,omitempty" json:"path,omitempty"`
39 | Headers map[string][]string `yaml:"headers,omitempty" json:"headers,omitempty"`
40 | }
41 |
42 | func (v Vmess) Identifier() string {
43 | return net.JoinHostPort(v.Server, strconv.Itoa(v.Port)) + v.Cipher + v.UUID
44 | }
45 |
46 | func (v Vmess) String() string {
47 | data, err := json.Marshal(v)
48 | if err != nil {
49 | return ""
50 | }
51 | return string(data)
52 | }
53 |
54 | func (v Vmess) ToClash() string {
55 | data, err := json.Marshal(v)
56 | if err != nil {
57 | return ""
58 | }
59 | return "- " + string(data)
60 | }
61 |
62 | func (v Vmess) ToSurge() string {
63 | // node2 = vmess, server, port, username=, ws=true, ws-path=, ws-headers=
64 | if v.Network == "ws" {
65 | wsHeasers := ""
66 | for k, v := range v.WSHeaders {
67 | if wsHeasers == "" {
68 | wsHeasers = k + ":" + v
69 | } else {
70 | wsHeasers += "|" + k + ":" + v
71 | }
72 | }
73 | text := fmt.Sprintf("%s = vmess, %s, %d, username=%s, ws=true, tls=%t, ws-path=%s",
74 | v.Name, v.Server, v.Port, v.UUID, v.TLS, v.WSPath)
75 | if wsHeasers != "" {
76 | text += ", ws-headers=" + wsHeasers
77 | }
78 | return text
79 | } else {
80 | return fmt.Sprintf("%s = vmess, %s, %d, username=%s, tls=%t",
81 | v.Name, v.Server, v.Port, v.UUID, v.TLS)
82 | }
83 | }
84 |
85 | func (v Vmess) Clone() Proxy {
86 | return &v
87 | }
88 |
89 | func (v Vmess) Link() (link string) {
90 | vjv, err := json.Marshal(v.toLinkJson())
91 | if err != nil {
92 | return
93 | }
94 | return fmt.Sprintf("vmess://%s", tool.Base64EncodeBytes(vjv))
95 | }
96 |
97 | type vmessLinkJson struct {
98 | Add string `json:"add"`
99 | V string `json:"v"`
100 | Ps string `json:"ps"`
101 | Port interface{} `json:"port"`
102 | Id string `json:"id"`
103 | Aid string `json:"aid"`
104 | Net string `json:"net"`
105 | Type string `json:"type"`
106 | Host string `json:"host"`
107 | Path string `json:"path"`
108 | Tls string `json:"tls"`
109 | }
110 |
111 | func (v Vmess) toLinkJson() vmessLinkJson {
112 | vj := vmessLinkJson{
113 | Add: v.Server,
114 | Ps: v.Name,
115 | Port: v.Port,
116 | Id: v.UUID,
117 | Aid: strconv.Itoa(v.AlterID),
118 | Net: v.Network,
119 | Path: v.WSPath,
120 | Host: v.ServerName,
121 | V: "2",
122 | }
123 | if v.TLS {
124 | vj.Tls = "tls"
125 | }
126 | if host, ok := v.WSHeaders["HOST"]; ok && host != "" {
127 | vj.Host = host
128 | }
129 | return vj
130 | }
131 |
132 | func ParseVmessLink(link string) (*Vmess, error) {
133 | if !strings.HasPrefix(link, "vmess") {
134 | return nil, ErrorNotVmessLink
135 | }
136 |
137 | vmessmix := strings.SplitN(link, "://", 2)
138 | if len(vmessmix) < 2 {
139 | return nil, ErrorNotVmessLink
140 | }
141 | linkPayload := vmessmix[1]
142 | if strings.Contains(linkPayload, "?") {
143 | // 使用第二种解析方法
144 | var infoPayloads []string
145 | if strings.Contains(linkPayload, "/?") {
146 | infoPayloads = strings.SplitN(linkPayload, "/?", 2)
147 | } else {
148 | infoPayloads = strings.SplitN(linkPayload, "?", 2)
149 | }
150 | if len(infoPayloads) < 2 {
151 | return nil, ErrorNotVmessLink
152 | }
153 |
154 | baseInfo, err := tool.Base64DecodeString(infoPayloads[0])
155 | if err != nil {
156 | return nil, ErrorVmessPayloadParseFail
157 | }
158 | baseInfoPath := strings.Split(baseInfo, ":")
159 | if len(baseInfoPath) < 3 {
160 | return nil, ErrorPathNotComplete
161 | }
162 | // base info
163 | cipher := baseInfoPath[0]
164 | mixInfo := strings.SplitN(baseInfoPath[1], "@", 2)
165 | if len(mixInfo) < 2 {
166 | return nil, ErrorVmessPayloadParseFail
167 | }
168 | uuid := mixInfo[0]
169 | server := mixInfo[1]
170 | portStr := baseInfoPath[2]
171 | port, err := strconv.Atoi(portStr)
172 | if err != nil {
173 | return nil, ErrorVmessPayloadParseFail
174 | }
175 |
176 | moreInfo, _ := url.ParseQuery(infoPayloads[1])
177 | remarks := moreInfo.Get("remarks")
178 | obfs := moreInfo.Get("obfs")
179 | network := "tcp"
180 | if obfs == "websocket" {
181 | network = "ws"
182 | }
183 | //obfsParam := moreInfo.Get("obfsParam")
184 | path := moreInfo.Get("path")
185 | if path == "" {
186 | path = "/"
187 | }
188 | tls := moreInfo.Get("tls") == "1"
189 |
190 | wsHeaders := make(map[string]string)
191 | return &Vmess{
192 | Base: Base{
193 | Name: remarks + "_" + strconv.Itoa(rand.Int()),
194 | Server: server,
195 | Port: port,
196 | Type: "vmess",
197 | UDP: false,
198 | },
199 | UUID: uuid,
200 | AlterID: 0,
201 | Cipher: cipher,
202 | TLS: tls,
203 | Network: network,
204 | HTTPOpts: HTTPOptions{},
205 | WSPath: path,
206 | WSHeaders: wsHeaders,
207 | SkipCertVerify: true,
208 | ServerName: server,
209 | }, nil
210 | } else {
211 | payload, err := tool.Base64DecodeString(linkPayload)
212 | if err != nil {
213 | return nil, ErrorVmessPayloadParseFail
214 | }
215 | vmessJson := vmessLinkJson{}
216 | err = json.Unmarshal([]byte(payload), &vmessJson)
217 | if err != nil {
218 | return nil, err
219 | }
220 | port := 443
221 | portInterface := vmessJson.Port
222 | switch portInterface.(type) {
223 | case int:
224 | port = portInterface.(int)
225 | case string:
226 | port, _ = strconv.Atoi(portInterface.(string))
227 | }
228 |
229 | alterId, err := strconv.Atoi(vmessJson.Aid)
230 | if err != nil {
231 | alterId = 0
232 | }
233 | tls := vmessJson.Tls == "tls"
234 |
235 | wsHeaders := make(map[string]string)
236 | if vmessJson.Host != "" {
237 | wsHeaders["HOST"] = vmessJson.Host
238 | }
239 |
240 | if vmessJson.Path == "" {
241 | vmessJson.Path = "/"
242 | }
243 | return &Vmess{
244 | Base: Base{
245 | Name: vmessJson.Ps + "_" + strconv.Itoa(rand.Int()),
246 | Server: vmessJson.Add,
247 | Port: port,
248 | Type: "vmess",
249 | UDP: false,
250 | },
251 | UUID: vmessJson.Id,
252 | AlterID: alterId,
253 | Cipher: "auto",
254 | TLS: tls,
255 | Network: vmessJson.Net,
256 | HTTPOpts: HTTPOptions{},
257 | WSPath: vmessJson.Path,
258 | WSHeaders: wsHeaders,
259 | SkipCertVerify: true,
260 | ServerName: vmessJson.Host,
261 | }, nil
262 | }
263 | }
264 |
265 | var (
266 | vmessPlainRe = regexp.MustCompile("vmess://([A-Za-z0-9+/_?&=-])+")
267 | )
268 |
269 | func GrepVmessLinkFromString(text string) []string {
270 | results := make([]string, 0)
271 | texts := strings.Split(text, "vmess://")
272 | for _, text := range texts {
273 | results = append(results, vmessPlainRe.FindAllString("vmess://"+text, -1)...)
274 | }
275 | return results
276 | }
277 |
--------------------------------------------------------------------------------
/pkg/tool/base64.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "encoding/base64"
5 | )
6 |
7 | func Base64DecodeString(src string) (dst string, err error) {
8 | if src == "" {
9 | return "", nil
10 | }
11 | var dstbytes []byte
12 | dstbytes, err = base64.RawURLEncoding.DecodeString(src)
13 |
14 | if err != nil {
15 | dstbytes, err = base64.RawStdEncoding.DecodeString(src)
16 | }
17 | if err != nil {
18 | dstbytes, err = base64.StdEncoding.DecodeString(src)
19 | }
20 | if err != nil {
21 | dstbytes, err = base64.URLEncoding.DecodeString(src)
22 | }
23 | if err != nil {
24 | return "", err
25 | }
26 | dst = string(dstbytes)
27 | return
28 | }
29 |
30 | func Base64EncodeString(origin string, urlsafe bool) (result string) {
31 | if urlsafe {
32 | return base64.URLEncoding.EncodeToString([]byte(origin))
33 | }
34 | return base64.StdEncoding.EncodeToString([]byte(origin))
35 | }
36 |
37 | func Base64EncodeBytes(origin []byte) (result string) {
38 | return base64.StdEncoding.EncodeToString([]byte(origin))
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/tool/check.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | func CheckInList(list []string, item string) bool {
4 | for _, i := range list {
5 | if item == i {
6 | return true
7 | }
8 | }
9 | return false
10 | }
11 |
--------------------------------------------------------------------------------
/pkg/tool/colly.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "net"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/gocolly/colly"
9 | )
10 |
11 | func GetColly() *colly.Collector {
12 | c := colly.NewCollector(
13 | colly.UserAgent(UserAgent),
14 | )
15 | c.WithTransport(&http.Transport{
16 | Proxy: http.ProxyFromEnvironment,
17 | DialContext: (&net.Dialer{
18 | Timeout: 10 * time.Second, // 超时时间
19 | KeepAlive: 10 * time.Second, // keepAlive 超时时间
20 | }).DialContext,
21 | MaxIdleConns: 100, // 最大空闲连接数
22 | IdleConnTimeout: 20 * time.Second, // 空闲连接超时
23 | TLSHandshakeTimeout: 10 * time.Second, // TLS 握手超时
24 | ExpectContinueTimeout: 10 * time.Second,
25 | })
26 | return c
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/tool/httpclient.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "time"
7 | )
8 |
9 | const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
10 |
11 | type HttpClient struct {
12 | *http.Client
13 | }
14 |
15 | var httpClient *HttpClient
16 |
17 | func init() {
18 | httpClient = &HttpClient{http.DefaultClient}
19 | httpClient.Timeout = time.Second * 10
20 | }
21 |
22 | func GetHttpClient() *HttpClient {
23 | c := *httpClient
24 | return &c
25 | }
26 |
27 | func (c *HttpClient) Get(url string) (resp *http.Response, err error) {
28 | req, err := http.NewRequest(http.MethodGet, url, nil)
29 | if err != nil {
30 | return nil, err
31 | }
32 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
33 | req.Header.Set("User-Agent", UserAgent)
34 | return c.Do(req)
35 | }
36 |
37 | func (c *HttpClient) Post(url string, body io.Reader) (resp *http.Response, err error) {
38 | req, err := http.NewRequest(http.MethodPost, url, body)
39 | if err != nil {
40 | return nil, err
41 | }
42 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
43 | req.Header.Set("User-Agent", UserAgent)
44 | return c.Do(req)
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/tool/option.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | type Options map[string]interface{}
4 |
--------------------------------------------------------------------------------
/pkg/tool/unicode.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "regexp"
5 | "unicode"
6 | )
7 |
8 | var hanRe = regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]")
9 |
10 | func ContainChineseChar(str string) bool {
11 | for _, r := range str {
12 | if unicode.Is(unicode.Scripts["Han"], r) || (hanRe.MatchString(string(r))) {
13 | return true
14 | }
15 | }
16 | return false
17 | }
18 |
--------------------------------------------------------------------------------