├── .gitattributes ├── pkg ├── conf │ ├── version.go │ ├── defaults.go │ └── conf.go ├── util │ ├── io.go │ ├── common.go │ └── logger.go ├── serializer │ └── error.go └── request │ └── request.go ├── extractors ├── types │ └── types.go ├── 66ip │ ├── 66ip_test.go │ └── 66ip.go ├── 7yip │ ├── 7yip_test.go │ └── 7yip.go ├── 89ip │ ├── 89ip_test.go │ └── 89ip.go ├── ip3366 │ ├── ip3366_test.go │ └── ip3366.go ├── sudaili │ ├── sudaili_test.go │ └── sudaili.go ├── goubanjia │ ├── goubanjia_test.go │ └── goubanjia.go ├── kuaidaili │ ├── kuaidaili_test.go │ └── kuaidaili.go ├── seofangfa │ ├── seofangfa_test.go │ └── seofangfa.go ├── xiladaili │ ├── xiladaili_test.go │ └── xiladaili.go ├── jiangxianli │ ├── jiangxianli_test.go │ └── jiangxianli.go ├── proxylistplus │ ├── proxylistplus_test.go │ └── proxylistplus.go └── extractors.go ├── .gitignore ├── bootstrap ├── init.go ├── static.go └── app.go ├── middleware ├── token.go └── frontend.go ├── routers ├── controllers │ ├── site.go │ └── proxy.go └── router.go ├── go.mod ├── models ├── init.go └── proxy.go ├── main.go ├── README.md ├── assets └── index.md ├── go.sum ├── LICENSE └── statik └── statik.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-language=go 2 | -------------------------------------------------------------------------------- /pkg/conf/version.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | // Version 当前版本号 4 | var Version = "0.0.1" 5 | -------------------------------------------------------------------------------- /extractors/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/HaliComing/fpp/models" 4 | 5 | // 提取器 6 | type Extractor interface { 7 | // 提取器名称 一般为域名 8 | Key() string 9 | // 提取器主要函数 10 | Extract() ([]*models.ProxyIP, error) 11 | } 12 | -------------------------------------------------------------------------------- /extractors/66ip/66ip_test.go: -------------------------------------------------------------------------------- 1 | package ip66 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/7yip/7yip_test.go: -------------------------------------------------------------------------------- 1 | package yip7 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/89ip/89ip_test.go: -------------------------------------------------------------------------------- 1 | package ip89 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/ip3366/ip3366_test.go: -------------------------------------------------------------------------------- 1 | package ip3366 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/sudaili/sudaili_test.go: -------------------------------------------------------------------------------- 1 | package sudaili 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/goubanjia/goubanjia_test.go: -------------------------------------------------------------------------------- 1 | package goubanjia 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/kuaidaili/kuaidaili_test.go: -------------------------------------------------------------------------------- 1 | package kuaidaili 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/seofangfa/seofangfa_test.go: -------------------------------------------------------------------------------- 1 | package seofangfa 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/xiladaili/xiladaili_test.go: -------------------------------------------------------------------------------- 1 | package xiladaili 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/jiangxianli/jiangxianli_test.go: -------------------------------------------------------------------------------- 1 | package jiangxianli 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extractors/proxylistplus/proxylistplus_test.go: -------------------------------------------------------------------------------- 1 | package proxylistplus 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // 测试提取 9 | func TestExtract(t *testing.T) { 10 | extract, err := New().Extract() 11 | if err != nil { 12 | fmt.Println(err) 13 | } else { 14 | for _, ip := range extract { 15 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # file 2 | conf.ini 3 | fpp.db 4 | 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | .gittoken 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | .idea/ -------------------------------------------------------------------------------- /bootstrap/init.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/models" 5 | "github.com/HaliComing/fpp/pkg/conf" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // Init 初始化启动 10 | func Init(path string) { 11 | InitApplication() 12 | conf.Init(path) 13 | // Debug 关闭时,切换为生产模式 14 | if !conf.SystemConfig.Debug { 15 | gin.SetMode(gin.ReleaseMode) 16 | } 17 | 18 | models.Init() 19 | InitStatic() 20 | } 21 | -------------------------------------------------------------------------------- /pkg/conf/defaults.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | // DatabaseConfig 数据库配置 4 | var DatabaseConfig = &database{ 5 | Type: "sqlite3", 6 | DBFile: "fpp.db", 7 | } 8 | 9 | // SystemConfig 系统公用配置 10 | var SystemConfig = &system{ 11 | Debug: false, 12 | Listen: ":9826", 13 | ProxyListen: ":9827", 14 | NumberOfThreads: 50, 15 | ExtractionInterval: 60, 16 | CheckInterval: 15, 17 | } 18 | 19 | var SSLConfig = &ssl{ 20 | Listen: ":443", 21 | CertPath: "", 22 | KeyPath: "", 23 | } 24 | -------------------------------------------------------------------------------- /middleware/token.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "errors" 5 | "github.com/HaliComing/fpp/pkg/conf" 6 | "github.com/HaliComing/fpp/pkg/serializer" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // TokenHandler Token处理 11 | func TokenHandler() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | token := c.Query("token") 14 | // 成功跳过 15 | if token == conf.SystemConfig.Token { 16 | c.Next() 17 | return 18 | } 19 | 20 | err := errors.New("token is error") 21 | c.JSON(200, serializer.Err( 22 | serializer.CodeNoPermissionErr, err.Error(), err)) 23 | c.Abort() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /routers/controllers/site.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/models" 5 | "github.com/HaliComing/fpp/pkg/conf" 6 | "github.com/HaliComing/fpp/pkg/serializer" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Ping(c *gin.Context) { 11 | c.JSON(200, serializer.Response{ 12 | Code: 0, 13 | Data: conf.Version, 14 | }) 15 | } 16 | 17 | // 统计数据 18 | func Count(c *gin.Context) { 19 | count, err := models.ProxyCount() 20 | if err != nil { 21 | c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) 22 | return 23 | } 24 | c.JSON(200, serializer.Response{ 25 | Code: 0, 26 | Data: map[string]interface{}{ 27 | "Count": count, 28 | }, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /middleware/frontend.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/bootstrap" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | // FrontendFileHandler 前端静态文件处理 11 | func FrontendFileHandler() gin.HandlerFunc { 12 | ignoreFunc := func(c *gin.Context) { 13 | c.Next() 14 | } 15 | 16 | if bootstrap.StaticFS == nil { 17 | return ignoreFunc 18 | } 19 | 20 | fileServer := http.FileServer(bootstrap.StaticFS) 21 | return func(c *gin.Context) { 22 | path := c.Request.URL.Path 23 | 24 | // API 跳过 25 | if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/custom") || strings.HasPrefix(path, "/dav") || path == "/manifest.json" { 26 | c.Next() 27 | return 28 | } 29 | 30 | // 存在的静态文件 31 | fileServer.ServeHTTP(c.Writer, c.Request) 32 | c.Abort() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bootstrap/static.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/pkg/util" 5 | _ "github.com/HaliComing/fpp/statik" 6 | "github.com/gin-contrib/static" 7 | "github.com/rakyll/statik/fs" 8 | "net/http" 9 | ) 10 | 11 | type GinFS struct { 12 | FS http.FileSystem 13 | } 14 | 15 | // StaticFS 内置静态文件资源 16 | var StaticFS static.ServeFileSystem 17 | 18 | // Open 打开文件 19 | func (b *GinFS) Open(name string) (http.File, error) { 20 | return b.FS.Open(name) 21 | } 22 | 23 | // Exists 文件是否存在 24 | func (b *GinFS) Exists(prefix string, filepath string) bool { 25 | 26 | if _, err := b.FS.Open(filepath); err != nil { 27 | return false 28 | } 29 | return true 30 | 31 | } 32 | 33 | // InitStatic 初始化静态资源文件 34 | func InitStatic() { 35 | var err error 36 | 37 | StaticFS = &GinFS{} 38 | StaticFS.(*GinFS).FS, err = fs.New() 39 | if err != nil { 40 | util.Log().Panic("[Static] Unable to initialize static resource, Error = %s", err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/util/io.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | // Exists reports whether the named file or directory exists. 10 | func Exists(name string) bool { 11 | if _, err := os.Stat(name); err != nil { 12 | if os.IsNotExist(err) { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | 19 | // CreatNestedFile 给定path创建文件,如果目录不存在就递归创建 20 | func CreatNestedFile(path string) (*os.File, error) { 21 | basePath := filepath.Dir(path) 22 | if !Exists(basePath) { 23 | err := os.MkdirAll(basePath, 0700) 24 | if err != nil { 25 | Log().Warning("无法创建目录,%s", err) 26 | return nil, err 27 | } 28 | } 29 | 30 | return os.Create(path) 31 | } 32 | 33 | // IsEmpty 返回给定目录是否为空目录 34 | func IsEmpty(name string) (bool, error) { 35 | f, err := os.Open(name) 36 | if err != nil { 37 | return false, err 38 | } 39 | defer f.Close() 40 | 41 | _, err = f.Readdirnames(1) // Or f.Readdir(1) 42 | if err == io.EOF { 43 | return true, nil 44 | } 45 | return false, err // Either not empty or error, suits both cases 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/HaliComing/fpp 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.6.1 7 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 8 | github.com/bitly/go-simplejson v0.5.0 9 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 10 | github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e 11 | github.com/fatih/color v1.7.0 12 | github.com/gin-contrib/cors v1.3.0 13 | github.com/gin-contrib/gzip v0.0.2-0.20200226035851-25bef2ef21e8 14 | github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 15 | github.com/gin-gonic/gin v1.5.0 16 | github.com/go-ini/ini v1.50.0 17 | github.com/go-sql-driver/mysql v1.5.0 // indirect 18 | github.com/hashicorp/go-version v1.2.0 19 | github.com/jinzhu/gorm v1.9.11 20 | github.com/mattn/go-colorable v0.1.4 // indirect 21 | github.com/rakyll/statik v0.1.7 22 | github.com/smartystreets/goconvey v1.6.4 // indirect 23 | github.com/stretchr/testify v1.5.1 // indirect 24 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect 25 | gopkg.in/go-playground/validator.v9 v9.29.1 26 | gopkg.in/ini.v1 v1.51.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /models/init.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/HaliComing/fpp/pkg/conf" 7 | "github.com/HaliComing/fpp/pkg/util" 8 | "github.com/jinzhu/gorm" 9 | 10 | _ "github.com/jinzhu/gorm/dialects/sqlite" 11 | ) 12 | 13 | // DB 数据库链接单例 14 | var DB *gorm.DB 15 | 16 | // Init 初始化 MySQL 链接 17 | func Init() { 18 | util.Log().Info("[DB] Init DB.") 19 | 20 | var ( 21 | db *gorm.DB 22 | err error 23 | ) 24 | 25 | switch conf.DatabaseConfig.Type { 26 | case "memory": 27 | db, err = gorm.Open("sqlite3", ":memory:") 28 | case "sqlite", "sqlite3": 29 | db, err = gorm.Open("sqlite3", conf.DatabaseConfig.DBFile) 30 | default: 31 | util.Log().Panic("[DB] Database type = %s, Error = Database type is not supported.", conf.DatabaseConfig.Type) 32 | } 33 | 34 | //db.SetLogger(util.Log()) 35 | if err != nil { 36 | util.Log().Panic("[DB] Failed to connect to database, Error = %s", err) 37 | } 38 | 39 | // Debug模式下,输出所有 SQL 日志 40 | if conf.SystemConfig.Debug { 41 | db.LogMode(true) 42 | } else { 43 | db.LogMode(false) 44 | } 45 | 46 | //设置连接池 47 | //空闲 48 | db.DB().SetMaxIdleConns(50) 49 | //打开 50 | db.DB().SetMaxOpenConns(100) 51 | //超时 52 | db.DB().SetConnMaxLifetime(time.Second * 30) 53 | 54 | DB = db 55 | 56 | // 清空数据 57 | DB.DropTable(&ProxyIP{}) 58 | // 执行迁移 59 | DB.AutoMigrate(&ProxyIP{}) 60 | } 61 | -------------------------------------------------------------------------------- /extractors/jiangxianli/jiangxianli.go: -------------------------------------------------------------------------------- 1 | package jiangxianli 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "strings" 9 | ) 10 | 11 | type extractor struct{} 12 | 13 | // New returns a extractor. 14 | func New() types.Extractor { 15 | return &extractor{} 16 | } 17 | 18 | func (e *extractor) Key() string { 19 | return "ip.jiangxianli.com" 20 | } 21 | 22 | // Extract is the main function to extract the data. 23 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 24 | var proxyIPs []*models.ProxyIP 25 | client := request.HTTPClient{} 26 | html, err := client.Request("GET", "https://ip.jiangxianli.com/?page=1&country=%E4%B8%AD%E5%9B%BD", nil).GetResponse() 27 | if err != nil { 28 | return nil, err 29 | } 30 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 31 | if err != nil { 32 | return nil, err 33 | } 34 | dom.Find(".ip-tables table tbody tr").Each(func(i int, s *goquery.Selection) { 35 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 36 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 37 | proxyIP := &models.ProxyIP{ 38 | IP: ip, 39 | Port: port, 40 | } 41 | proxyIPs = append(proxyIPs, proxyIP) 42 | }) 43 | return proxyIPs, nil 44 | } 45 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/middleware" 5 | "github.com/HaliComing/fpp/pkg/util" 6 | "github.com/HaliComing/fpp/routers/controllers" 7 | "github.com/gin-contrib/cors" 8 | "github.com/gin-contrib/gzip" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // InitCORS 初始化跨域配置 13 | func InitCORS(router *gin.Engine) { 14 | config := cors.DefaultConfig() 15 | config.AllowAllOrigins = true 16 | 17 | router.Use(cors.New(config)) 18 | } 19 | 20 | // InitRouter 初始化主机模式路由 21 | func InitRouter() *gin.Engine { 22 | util.Log().Info("[API] Start API, Init Router.") 23 | 24 | r := gin.Default() 25 | 26 | /* 27 | 静态资源 28 | */ 29 | r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/api/"}))) 30 | r.Use(middleware.FrontendFileHandler()) 31 | 32 | v1 := r.Group("/api/v1") 33 | 34 | // 跨域相关 35 | InitCORS(r) 36 | 37 | v1.Use(middleware.TokenHandler()) 38 | /* 39 | 路由 40 | */ 41 | { 42 | // 全局相关 43 | site := v1.Group("site") 44 | { 45 | // 测试用路由 46 | site.GET("ping", controllers.Ping) 47 | // 统计数据 48 | site.GET("count", controllers.Count) 49 | } 50 | 51 | // IP代理池 52 | proxy := v1.Group("proxy") 53 | { 54 | // 随机获取一个IP 55 | proxy.GET("random", controllers.ProxyRandom) 56 | // 获取全部IP 57 | proxy.GET("all", controllers.ProxyAll) 58 | // 删除IP 59 | proxy.GET("delete", controllers.ProxyDelete) 60 | } 61 | } 62 | return r 63 | } 64 | -------------------------------------------------------------------------------- /bootstrap/app.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/HaliComing/fpp/pkg/conf" 8 | "github.com/HaliComing/fpp/pkg/request" 9 | "github.com/HaliComing/fpp/pkg/util" 10 | "github.com/hashicorp/go-version" 11 | ) 12 | 13 | // InitApplication 初始化应用常量 14 | func InitApplication() { 15 | fmt.Print(` 16 | fpp V` + conf.Version + ` 17 | ============================= 18 | 19 | `) 20 | //go CheckUpdate() 21 | } 22 | 23 | type GitHubRelease struct { 24 | URL string `json:"html_url"` 25 | Name string `json:"name"` 26 | Tag string `json:"tag_name"` 27 | } 28 | 29 | // CheckUpdate 检查更新 30 | func CheckUpdate() { 31 | client := request.HTTPClient{} 32 | res, err := client.Request("GET", "https://api.github.com/repos/halicoming/fpp/releases", nil).GetResponse() 33 | if err != nil { 34 | util.Log().Warning("[CheckUpdate] Check update failed, Error = %s", err) 35 | return 36 | } 37 | 38 | var list []GitHubRelease 39 | if err := json.Unmarshal([]byte(res), &list); err != nil { 40 | util.Log().Warning("[CheckUpdate] Check update failed, Error = %s", err) 41 | return 42 | } 43 | 44 | if len(list) > 0 { 45 | present, err1 := version.NewVersion(conf.Version) 46 | latest, err2 := version.NewVersion(list[0].Tag) 47 | if err1 == nil && err2 == nil && latest.GreaterThan(present) { 48 | util.Log().Info("[CheckUpdate] A new version is available, Version = [%s], Download url = %s", list[0].Name, list[0].URL) 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /extractors/kuaidaili/kuaidaili.go: -------------------------------------------------------------------------------- 1 | package kuaidaili 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "www.kuaidaili.com" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "https://www.kuaidaili.com/free/", nil, 28 | request.WithHeader(http.Header{ 29 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 30 | })).GetResponse() 31 | if err != nil { 32 | return nil, err 33 | } 34 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | dom.Find("#list > table > tbody > tr").Each(func(i int, s *goquery.Selection) { 39 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 40 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 41 | proxyIP := &models.ProxyIP{ 42 | IP: ip, 43 | Port: port, 44 | } 45 | proxyIPs = append(proxyIPs, proxyIP) 46 | }) 47 | return proxyIPs, nil 48 | } 49 | -------------------------------------------------------------------------------- /extractors/xiladaili/xiladaili.go: -------------------------------------------------------------------------------- 1 | package xiladaili 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "net/http" 8 | "regexp" 9 | ) 10 | 11 | type extractor struct{} 12 | 13 | // New returns a extractor. 14 | func New() types.Extractor { 15 | return &extractor{} 16 | } 17 | 18 | func (e *extractor) Key() string { 19 | return "www.xiladaili.com" 20 | } 21 | 22 | // Extract is the main function to extract the data. 23 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 24 | var proxyIPs []*models.ProxyIP 25 | client := request.HTTPClient{} 26 | html, err := client.Request("GET", "http://www.xiladaili.com/", nil, 27 | request.WithHeader(http.Header{ 28 | "Referer": {"http://www.xiladaili.com/"}, 29 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 30 | })).GetResponse() 31 | if err != nil { 32 | return nil, err 33 | } 34 | reg, err := regexp.Compile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})`) 35 | if err != nil { 36 | return nil, err 37 | } 38 | matchOk := reg.MatchString(html) 39 | if !matchOk { 40 | return proxyIPs, nil 41 | } 42 | submatchs := reg.FindAllStringSubmatch(html, -1) 43 | for _, submatch := range submatchs { 44 | proxyIP := &models.ProxyIP{ 45 | IP: submatch[1], 46 | Port: submatch[2], 47 | } 48 | proxyIPs = append(proxyIPs, proxyIP) 49 | } 50 | return proxyIPs, nil 51 | } 52 | -------------------------------------------------------------------------------- /extractors/ip3366/ip3366.go: -------------------------------------------------------------------------------- 1 | package ip3366 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "www.ip3366.net" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "http://www.ip3366.net/", nil, 28 | request.WithHeader(http.Header{ 29 | "Referer": {"http://www.ip3366.net/"}, 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find("#list > table > tbody > tr").Each(func(i int, s *goquery.Selection) { 40 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 41 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 42 | proxyIP := &models.ProxyIP{ 43 | IP: ip, 44 | Port: port, 45 | } 46 | proxyIPs = append(proxyIPs, proxyIP) 47 | }) 48 | return proxyIPs, nil 49 | } 50 | -------------------------------------------------------------------------------- /extractors/89ip/89ip.go: -------------------------------------------------------------------------------- 1 | package ip89 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "www.89ip.cn" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "https://www.89ip.cn/index_1.html", nil, 28 | request.WithHeader(http.Header{ 29 | "Referer": {"https://www.89ip.cn/"}, 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find("div div.layui-form > table > tbody > tr").Each(func(i int, s *goquery.Selection) { 40 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 41 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 42 | proxyIP := &models.ProxyIP{ 43 | IP: ip, 44 | Port: port, 45 | } 46 | proxyIPs = append(proxyIPs, proxyIP) 47 | }) 48 | return proxyIPs, nil 49 | } 50 | -------------------------------------------------------------------------------- /extractors/sudaili/sudaili.go: -------------------------------------------------------------------------------- 1 | package sudaili 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "www.sudaili.com" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "http://www.sudaili.com/free.html", nil, 28 | request.WithHeader(http.Header{ 29 | "Referer": {"http://www.sudaili.com/"}, 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find("div table.freeTable > tbody > tr").Each(func(i int, s *goquery.Selection) { 40 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 41 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 42 | proxyIP := &models.ProxyIP{ 43 | IP: ip, 44 | Port: port, 45 | } 46 | proxyIPs = append(proxyIPs, proxyIP) 47 | }) 48 | return proxyIPs, nil 49 | } 50 | -------------------------------------------------------------------------------- /extractors/seofangfa/seofangfa.go: -------------------------------------------------------------------------------- 1 | package seofangfa 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "seofangfa.com" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "https://seofangfa.com/proxy/", nil, 28 | request.WithHeader(http.Header{ 29 | "Referer": {"https://seofangfa.com/proxy/"}, 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find("div.table-responsive > table.table > tbody:nth-child(2) > tr").Each(func(i int, s *goquery.Selection) { 40 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 41 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 42 | proxyIP := &models.ProxyIP{ 43 | IP: ip, 44 | Port: port, 45 | } 46 | proxyIPs = append(proxyIPs, proxyIP) 47 | }) 48 | return proxyIPs, nil 49 | } 50 | -------------------------------------------------------------------------------- /extractors/proxylistplus/proxylistplus.go: -------------------------------------------------------------------------------- 1 | package proxylistplus 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type extractor struct{} 14 | 15 | // New returns a extractor. 16 | func New() types.Extractor { 17 | return &extractor{} 18 | } 19 | 20 | func (e *extractor) Key() string { 21 | return "list.proxylistplus.com" 22 | } 23 | 24 | // Extract is the main function to extract the data. 25 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 26 | var proxyIPs []*models.ProxyIP 27 | client := request.HTTPClient{} 28 | html, err := client.Request("GET", "https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1", nil, 29 | request.WithHeader(http.Header{ 30 | "Referer": {"https://list.proxylistplus.com/update-2"}, 31 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 32 | }), request.WithTimeout(time.Duration(10)*time.Second)).GetResponse() 33 | if err != nil { 34 | return nil, err 35 | } 36 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | dom.Find("#page table.bg tbody tr.cells").Each(func(i int, s *goquery.Selection) { 41 | ip := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 42 | port := strings.TrimSpace(s.Find("td:nth-child(3)").Text()) 43 | proxyIP := &models.ProxyIP{ 44 | IP: ip, 45 | Port: port, 46 | } 47 | proxyIPs = append(proxyIPs, proxyIP) 48 | }) 49 | return proxyIPs, nil 50 | } 51 | -------------------------------------------------------------------------------- /extractors/7yip/7yip.go: -------------------------------------------------------------------------------- 1 | package yip7 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | type extractor struct{} 13 | 14 | // New returns a extractor. 15 | func New() types.Extractor { 16 | return &extractor{} 17 | } 18 | 19 | func (e *extractor) Key() string { 20 | return "www.7yip.cn" 21 | } 22 | 23 | // Extract is the main function to extract the data. 24 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 25 | var proxyIPs []*models.ProxyIP 26 | client := request.HTTPClient{} 27 | html, err := client.Request("GET", "https://www.7yip.cn/free/", nil, 28 | request.WithHeader(http.Header{ 29 | "Referer": {"https://www.7yip.cn/"}, 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find("#content > section > div.container > table > tbody > tr").Each(func(i int, s *goquery.Selection) { 40 | s.Find("td:nth-child(1)").ChildrenFiltered("span").Remove() 41 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 42 | s.Find("td:nth-child(2)").ChildrenFiltered("span").Remove() 43 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 44 | proxyIP := &models.ProxyIP{ 45 | IP: ip, 46 | Port: port, 47 | } 48 | proxyIPs = append(proxyIPs, proxyIP) 49 | }) 50 | return proxyIPs, nil 51 | } 52 | -------------------------------------------------------------------------------- /extractors/goubanjia/goubanjia.go: -------------------------------------------------------------------------------- 1 | package goubanjia 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type extractor struct{} 14 | 15 | // New returns a extractor. 16 | func New() types.Extractor { 17 | return &extractor{} 18 | } 19 | 20 | func (e *extractor) Key() string { 21 | return "www.goubanjia.com" 22 | } 23 | 24 | // Extract is the main function to extract the data. 25 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 26 | var proxyIPs []*models.ProxyIP 27 | client := request.HTTPClient{} 28 | html, err := client.Request("GET", "http://www.goubanjia.com/", nil, 29 | request.WithHeader(http.Header{ 30 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 31 | })).GetResponse() 32 | if err != nil { 33 | return nil, err 34 | } 35 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dom.Find(".services div table > tbody > tr").Each(func(i int, s *goquery.Selection) { 40 | // 获取IP 41 | s.Find("td:nth-child(1)").ChildrenFiltered("p").Remove() 42 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 43 | ipport := strings.Split(ip, ":") 44 | // 获取端口 45 | attr, exists := s.Find("td:nth-child(1)").Find(".port").Attr("class") 46 | port := "" 47 | if exists { 48 | portClass := strings.Split(attr, " ") 49 | for _, ch := range portClass[1] { 50 | port = port + strconv.Itoa(strings.Index("ABCDEFGHIZ", string(ch))) 51 | } 52 | } 53 | atoi, err2 := strconv.Atoi(port) 54 | if err2 != nil { 55 | return 56 | } 57 | proxyIP := &models.ProxyIP{ 58 | IP: ipport[0], 59 | Port: strconv.Itoa(atoi >> 3), 60 | } 61 | proxyIPs = append(proxyIPs, proxyIP) 62 | }) 63 | return proxyIPs, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/serializer/error.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | // Response 基础序列化器 6 | type Response struct { 7 | Code int `json:"Code"` 8 | Data interface{} `json:"Data,omitempty"` 9 | Msg string `json:"Msg"` 10 | Error string `json:"Error,omitempty"` 11 | } 12 | 13 | // AppError 应用错误,实现了error接口 14 | type AppError struct { 15 | Code int 16 | Msg string 17 | RawError error 18 | } 19 | 20 | // NewError 返回新的错误对象 21 | func NewError(code int, msg string, err error) AppError { 22 | return AppError{ 23 | Code: code, 24 | Msg: msg, 25 | RawError: err, 26 | } 27 | } 28 | 29 | // WithError 将应用error携带标准库中的error 30 | func (err *AppError) WithError(raw error) AppError { 31 | err.RawError = raw 32 | return *err 33 | } 34 | 35 | // Error 返回业务代码确定的可读错误信息 36 | func (err AppError) Error() string { 37 | return err.Msg 38 | } 39 | 40 | // 三位数错误编码为复用http原本含义 41 | // 五位数错误编码为应用自定义错误 42 | // 五开头的五位数错误编码为服务器端错误,比如数据库操作失败 43 | // 四开头的五位数错误编码为客户端错误,有时候是客户端代码写错了,有时候是用户操作错误 44 | const ( 45 | // CodeNotFullySuccess 未完全成功 46 | CodeNotFullySuccess = 203 47 | // CodeNoPermissionErr 未授权访问 48 | CodeNoPermissionErr = 403 49 | // CodeNotFound 资源未找到 50 | CodeNotFound = 404 51 | // CodeDBError 数据库操作失败 52 | CodeDBError = 50001 53 | //CodeParamErr 各种奇奇怪怪的参数错误 54 | CodeParamErr = 40001 55 | // CodeNotSet 未定错误,后续尝试从error中获取 56 | CodeNotSet = -1 57 | ) 58 | 59 | // DBErr 数据库操作失败 60 | func DBErr(msg string, err error) Response { 61 | if msg == "" { 62 | msg = "数据库操作失败" 63 | } 64 | return Err(CodeDBError, msg, err) 65 | } 66 | 67 | // ParamErr 各种参数错误 68 | func ParamErr(msg string, err error) Response { 69 | if msg == "" { 70 | msg = "参数错误" 71 | } 72 | return Err(CodeParamErr, msg, err) 73 | } 74 | 75 | // Err 通用错误处理 76 | func Err(errCode int, msg string, err error) Response { 77 | // 底层错误是AppError,则尝试从AppError中获取详细信息 78 | if appError, ok := err.(AppError); ok { 79 | errCode = appError.Code 80 | err = appError.RawError 81 | msg = appError.Msg 82 | } 83 | 84 | res := Response{ 85 | Code: errCode, 86 | Msg: msg, 87 | } 88 | // 生产环境隐藏底层报错 89 | if err != nil && gin.Mode() != gin.ReleaseMode { 90 | res.Error = err.Error() 91 | } 92 | return res 93 | } 94 | -------------------------------------------------------------------------------- /routers/controllers/proxy.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/serializer" 7 | "github.com/HaliComing/fpp/pkg/util" 8 | "github.com/gin-gonic/gin" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // 随机获取一个IP 14 | func ProxyRandom(c *gin.Context) { 15 | anonymous, _ := strconv.Atoi(c.Query("anonymous")) 16 | protocol, _ := strconv.Atoi(c.Query("protocol")) 17 | country := c.Query("country") 18 | random, err := models.ProxyRandom( 19 | util.IfIntArray(anonymous != 0, []int{anonymous}, nil), 20 | util.IfIntArray(protocol != 0, []int{protocol}, nil), 21 | util.IfStringArray(country != "", []string{country}, nil)) 22 | if err != nil { 23 | c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) 24 | return 25 | } 26 | c.JSON(200, serializer.Response{ 27 | Code: 0, 28 | Data: random, 29 | }) 30 | } 31 | 32 | // 获取全部IP 33 | func ProxyAll(c *gin.Context) { 34 | anonymous, _ := strconv.Atoi(c.Query("anonymous")) 35 | protocol, _ := strconv.Atoi(c.Query("protocol")) 36 | country := c.Query("country") 37 | page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) 38 | pageSize, _ := strconv.Atoi(c.DefaultQuery("size", "10")) 39 | if page <= 0 { 40 | page = 1 41 | } 42 | if pageSize <= 0 { 43 | pageSize = 10 44 | } 45 | all, err := models.ProxyAll( 46 | util.IfIntArray(anonymous != 0, []int{anonymous}, nil), 47 | util.IfIntArray(protocol != 0, []int{protocol}, nil), 48 | util.IfStringArray(country != "", []string{country}, nil), page, pageSize) 49 | if err != nil { 50 | c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) 51 | return 52 | } 53 | c.JSON(200, serializer.Response{ 54 | Code: 0, 55 | Data: all, 56 | }) 57 | } 58 | 59 | // 删除IP 60 | func ProxyDelete(c *gin.Context) { 61 | ip := c.Query("ip") 62 | port := c.Query("port") 63 | proxy := c.Query("proxy") 64 | if strings.Index(proxy, ":") != -1 { 65 | split := strings.Split(proxy, ":") 66 | ip = split[0] 67 | port = split[1] 68 | } 69 | if ip == "" || port == "" { 70 | err := errors.New("param is error") 71 | c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) 72 | return 73 | } 74 | models.ProxyDelete(ip, port) 75 | c.JSON(200, serializer.Response{ 76 | Code: 0, 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /extractors/extractors.go: -------------------------------------------------------------------------------- 1 | package extractors 2 | 3 | import ( 4 | ip66 "github.com/HaliComing/fpp/extractors/66ip" 5 | yip7 "github.com/HaliComing/fpp/extractors/7yip" 6 | ip89 "github.com/HaliComing/fpp/extractors/89ip" 7 | "github.com/HaliComing/fpp/extractors/goubanjia" 8 | "github.com/HaliComing/fpp/extractors/ip3366" 9 | "github.com/HaliComing/fpp/extractors/jiangxianli" 10 | "github.com/HaliComing/fpp/extractors/kuaidaili" 11 | "github.com/HaliComing/fpp/extractors/proxylistplus" 12 | "github.com/HaliComing/fpp/extractors/seofangfa" 13 | "github.com/HaliComing/fpp/extractors/sudaili" 14 | "github.com/HaliComing/fpp/extractors/types" 15 | "github.com/HaliComing/fpp/extractors/xiladaili" 16 | "github.com/HaliComing/fpp/models" 17 | "github.com/HaliComing/fpp/pkg/request" 18 | "github.com/HaliComing/fpp/pkg/util" 19 | "time" 20 | ) 21 | 22 | var extractors []types.Extractor 23 | 24 | func init() { 25 | extractors = []types.Extractor{ 26 | yip7.New(), 27 | ip66.New(), 28 | ip89.New(), 29 | goubanjia.New(), 30 | ip3366.New(), 31 | jiangxianli.New(), 32 | kuaidaili.New(), 33 | proxylistplus.New(), 34 | seofangfa.New(), 35 | sudaili.New(), 36 | xiladaili.New(), 37 | } 38 | } 39 | 40 | // 提取器主要函数 41 | func Extract() []*models.ProxyIP { 42 | ip, err := request.RequestIP(models.TestUrlHttps) 43 | if err == nil { 44 | models.LocalIp = ip 45 | } 46 | var result []*models.ProxyIP 47 | util.Log().Info("[Extractor] ExtractorNumber = %d", len(extractors)) 48 | for _, extractor := range extractors { 49 | proxyIPs, err := extractor.Extract() 50 | if err != nil { 51 | util.Log().Error("[Extractor] Extractor = %s, Error = %s", extractor.Key(), err) 52 | continue 53 | } 54 | if proxyIPs == nil || len(proxyIPs) == 0 { 55 | util.Log().Info("[Extractor] Extractor = %s, IPNumber = 0", extractor.Key()) 56 | continue 57 | } 58 | for _, proxyIP := range proxyIPs { 59 | if proxyIP == nil || proxyIP.IP == "" || proxyIP.Port == "" { 60 | continue 61 | } 62 | proxyIP.Score = 10 63 | proxyIP.Source = extractor.Key() 64 | proxyIP.CreateTime = time.Now().Format("2006-01-02 15:04:05") 65 | proxyIP.FailedCount = 0 66 | result = append(result, proxyIP) 67 | util.Log().Info("[Extractor] Extractor = %s, Proxy = %s:%s", extractor.Key(), proxyIP.IP, proxyIP.Port) 68 | } 69 | } 70 | util.Log().Info("[Extractor] Extractor = ALL, IPNumber = %d", len(result)) 71 | return result 72 | } 73 | -------------------------------------------------------------------------------- /pkg/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/pkg/util" 5 | "github.com/go-ini/ini" 6 | "gopkg.in/go-playground/validator.v9" 7 | ) 8 | 9 | // database 数据库 10 | type database struct { 11 | Type string 12 | DBFile string 13 | } 14 | 15 | // system 系统通用配置 16 | type system struct { 17 | Listen string `validate:"required"` 18 | ProxyListen string `validate:"required"` 19 | Token string `validate:"required"` 20 | Debug bool 21 | NumberOfThreads int 22 | ExtractionInterval int 23 | CheckInterval int 24 | } 25 | 26 | type ssl struct { 27 | CertPath string `validate:"omitempty,required"` 28 | KeyPath string `validate:"omitempty,required"` 29 | Listen string `validate:"required"` 30 | } 31 | 32 | var cfg *ini.File 33 | 34 | const defaultConf = `[System] 35 | Listen = :9826 36 | ProxyListen = :9827 37 | NumberOfThreads = 50 38 | Token = {Token} 39 | ` 40 | 41 | // Init 初始化配置文件 42 | func Init(path string) { 43 | var err error 44 | 45 | if path == "" || !util.Exists(path) { 46 | // 创建初始配置文件 47 | confContent := util.Replace(map[string]string{ 48 | "{Token}": util.RandStringRunes(32), 49 | }, defaultConf) 50 | f, err := util.CreatNestedFile(path) 51 | if err != nil { 52 | util.Log().Panic("[Conf] Unable to create configuration file, Error = %s", err) 53 | } 54 | 55 | // 写入配置文件 56 | _, err = f.WriteString(confContent) 57 | if err != nil { 58 | util.Log().Panic("[Conf] Unable to write to configuration file, Error = %s", err) 59 | } 60 | 61 | f.Close() 62 | } 63 | 64 | cfg, err = ini.Load(path) 65 | if err != nil { 66 | util.Log().Panic("[Conf] The configuration file could not be resolved, Path = '%s', Error = %s", path, err) 67 | } 68 | 69 | sections := map[string]interface{}{ 70 | "System": SystemConfig, 71 | "SSL": SSLConfig, 72 | "Database": DatabaseConfig, 73 | } 74 | for sectionName, sectionStruct := range sections { 75 | err = mapSection(sectionName, sectionStruct) 76 | if err != nil { 77 | util.Log().Panic("[Conf] %s Section parsing failed, Error = %s", sectionName, err) 78 | } 79 | } 80 | 81 | // 重设log等级 82 | if !SystemConfig.Debug { 83 | util.Level = util.LevelInformational 84 | util.GloablLogger = nil 85 | util.Log() 86 | } 87 | 88 | } 89 | 90 | // mapSection 将配置文件的 Section 映射到结构体上 91 | func mapSection(section string, confStruct interface{}) error { 92 | err := cfg.Section(section).MapTo(confStruct) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | // 验证合法性 98 | validate := validator.New() 99 | err = validate.Struct(confStruct) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /extractors/66ip/66ip.go: -------------------------------------------------------------------------------- 1 | package ip66 2 | 3 | import ( 4 | "github.com/HaliComing/fpp/extractors/types" 5 | "github.com/HaliComing/fpp/models" 6 | "github.com/HaliComing/fpp/pkg/request" 7 | "github.com/PuerkitoBio/goquery" 8 | "net/http" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | type extractor struct{} 14 | 15 | // New returns a extractor. 16 | func New() types.Extractor { 17 | return &extractor{} 18 | } 19 | 20 | func (e *extractor) Key() string { 21 | return "www.66ip.cn" 22 | } 23 | 24 | // Extract is the main function to extract the data. 25 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 26 | var proxyIPs []*models.ProxyIP 27 | ips, err := ip66() 28 | if err != nil { 29 | return nil, err 30 | } 31 | proxyIPs = append(proxyIPs, ips...) 32 | ips2, err := ip66mo() 33 | if err != nil { 34 | return nil, err 35 | } 36 | proxyIPs = append(proxyIPs, ips2...) 37 | return proxyIPs, nil 38 | } 39 | 40 | func ip66() ([]*models.ProxyIP, error) { 41 | var proxyIPs []*models.ProxyIP 42 | client := request.HTTPClient{} 43 | html, err := client.Request("GET", "http://www.66ip.cn/", nil, 44 | request.WithHeader(http.Header{ 45 | "Referer": {"http://www.66ip.cn/"}, 46 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 47 | })).GetResponse() 48 | if err != nil { 49 | return nil, err 50 | } 51 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 52 | if err != nil { 53 | return nil, err 54 | } 55 | dom.Find("#main > div table > tbody > tr:not(:first-child)").Each(func(i int, s *goquery.Selection) { 56 | ip := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) 57 | port := strings.TrimSpace(s.Find("td:nth-child(2)").Text()) 58 | proxyIP := &models.ProxyIP{ 59 | IP: ip, 60 | Port: port, 61 | } 62 | proxyIPs = append(proxyIPs, proxyIP) 63 | }) 64 | return proxyIPs, nil 65 | } 66 | 67 | func ip66mo() ([]*models.ProxyIP, error) { 68 | var proxyIPs []*models.ProxyIP 69 | client := request.HTTPClient{} 70 | html, err := client.Request("GET", "http://www.66ip.cn/mo.php", nil, 71 | request.WithHeader(http.Header{ 72 | "Referer": {"http://www.66ip.cn/"}, 73 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 74 | })).GetResponse() 75 | if err != nil { 76 | return nil, err 77 | } 78 | reg, err := regexp.Compile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})`) 79 | if err != nil { 80 | return nil, err 81 | } 82 | matchOk := reg.MatchString(html) 83 | if !matchOk { 84 | return proxyIPs, nil 85 | } 86 | submatchs := reg.FindAllStringSubmatch(html, -1) 87 | for _, submatch := range submatchs { 88 | proxyIP := &models.ProxyIP{ 89 | IP: submatch[1], 90 | Port: submatch[2], 91 | } 92 | proxyIPs = append(proxyIPs, proxyIP) 93 | } 94 | return proxyIPs, nil 95 | } 96 | -------------------------------------------------------------------------------- /pkg/util/common.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/axgle/mahonia" 5 | "math/rand" 6 | "regexp" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func init() { 12 | rand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | func If(condition bool, trueVal, falseVal interface{}) interface{} { 16 | if condition { 17 | return trueVal 18 | } 19 | return falseVal 20 | } 21 | 22 | func IfIntArray(condition bool, trueVal, falseVal []int) []int { 23 | if condition { 24 | return trueVal 25 | } 26 | return falseVal 27 | } 28 | 29 | func IfStringArray(condition bool, trueVal, falseVal []string) []string { 30 | if condition { 31 | return trueVal 32 | } 33 | return falseVal 34 | } 35 | 36 | // RandStringRunes 返回随机字符串 37 | func RandStringRunes(n int) string { 38 | var letterRunes = []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 39 | 40 | b := make([]rune, n) 41 | for i := range b { 42 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 43 | } 44 | return string(b) 45 | } 46 | 47 | // ContainsUint 返回list中是否包含 48 | func ContainsUint(s []uint, e uint) bool { 49 | for _, a := range s { 50 | if a == e { 51 | return true 52 | } 53 | } 54 | return false 55 | } 56 | 57 | // ContainsString 返回list中是否包含 58 | func ContainsString(s []string, e string) bool { 59 | for _, a := range s { 60 | if a == e { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | 67 | // Replace 根据替换表执行批量替换 68 | func Replace(table map[string]string, s string) string { 69 | for key, value := range table { 70 | s = strings.Replace(s, key, value, -1) 71 | } 72 | return s 73 | } 74 | 75 | // BuildRegexp 构建用于SQL查询用的多条件正则 76 | func BuildRegexp(search []string, prefix, suffix, condition string) string { 77 | var res string 78 | for key, value := range search { 79 | res += prefix + regexp.QuoteMeta(value) + suffix 80 | if key < len(search)-1 { 81 | res += condition 82 | } 83 | } 84 | return res 85 | } 86 | 87 | // BuildConcat 根据数据库类型构建字符串连接表达式 88 | func BuildConcat(str1, str2 string, DBType string) string { 89 | switch DBType { 90 | case "mysql": 91 | return "CONCAT(" + str1 + "," + str2 + ")" 92 | default: 93 | return str1 + "||" + str2 94 | } 95 | } 96 | 97 | // SliceIntersect 求两个切片交集 98 | func SliceIntersect(slice1, slice2 []string) []string { 99 | m := make(map[string]int) 100 | nn := make([]string, 0) 101 | for _, v := range slice1 { 102 | m[v]++ 103 | } 104 | 105 | for _, v := range slice2 { 106 | times, _ := m[v] 107 | if times == 1 { 108 | nn = append(nn, v) 109 | } 110 | } 111 | return nn 112 | } 113 | 114 | // SliceDifference 求两个切片差集 115 | func SliceDifference(slice1, slice2 []string) []string { 116 | m := make(map[string]int) 117 | nn := make([]string, 0) 118 | inter := SliceIntersect(slice1, slice2) 119 | for _, v := range inter { 120 | m[v]++ 121 | } 122 | 123 | for _, value := range slice1 { 124 | times, _ := m[value] 125 | if times == 0 { 126 | nn = append(nn, value) 127 | } 128 | } 129 | return nn 130 | } 131 | 132 | // 编码转换 133 | func ConvertToString(src string, srcCode string, tagCode string) string { 134 | srcCoder := mahonia.NewDecoder(srcCode) 135 | srcResult := srcCoder.ConvertString(src) 136 | tagCoder := mahonia.NewDecoder(tagCode) 137 | _, cdata, _ := tagCoder.Translate([]byte(srcResult), true) 138 | result := string(cdata) 139 | return result 140 | } 141 | -------------------------------------------------------------------------------- /pkg/util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const ( 11 | // LevelError 错误 12 | LevelError = iota 13 | // LevelWarning 警告 14 | LevelWarning 15 | // LevelInformational 提示 16 | LevelInformational 17 | // LevelDebug 除错 18 | LevelDebug 19 | ) 20 | 21 | var GloablLogger *Logger 22 | var Level = LevelDebug 23 | 24 | // Logger 日志 25 | type Logger struct { 26 | level int 27 | mu sync.Mutex 28 | } 29 | 30 | // 日志颜色 31 | var colors = map[string]func(a ...interface{}) string{ 32 | "Warning": color.New(color.FgYellow).Add(color.Bold).SprintFunc(), 33 | "Panic": color.New(color.BgRed).Add(color.Bold).SprintFunc(), 34 | "Error": color.New(color.FgRed).Add(color.Bold).SprintFunc(), 35 | "Info": color.New(color.FgCyan).Add(color.Bold).SprintFunc(), 36 | "Debug": color.New(color.FgWhite).Add(color.Bold).SprintFunc(), 37 | } 38 | 39 | // 不同级别前缀与时间的间隔,保持宽度一致 40 | var spaces = map[string]string{ 41 | "Warning": "", 42 | "Panic": " ", 43 | "Error": " ", 44 | "Info": " ", 45 | "Debug": " ", 46 | } 47 | 48 | // Println 打印 49 | func (ll *Logger) Println(prefix string, msg string) { 50 | 51 | c := color.New() 52 | 53 | ll.mu.Lock() 54 | defer ll.mu.Unlock() 55 | 56 | _, _ = c.Printf( 57 | "%s%s %s %s\n", 58 | colors[prefix]("["+prefix+"]"), 59 | spaces[prefix], 60 | time.Now().Format("2006-01-02 15:04:05"), 61 | msg, 62 | ) 63 | } 64 | 65 | // Panic 极端错误 66 | func (ll *Logger) Panic(format string, v ...interface{}) { 67 | if LevelError > ll.level { 68 | return 69 | } 70 | msg := fmt.Sprintf(format, v...) 71 | ll.Println("Panic", msg) 72 | panic(msg) 73 | } 74 | 75 | // Error 错误 76 | func (ll *Logger) Error(format string, v ...interface{}) { 77 | if LevelError > ll.level { 78 | return 79 | } 80 | msg := fmt.Sprintf(format, v...) 81 | ll.Println("Error", msg) 82 | } 83 | 84 | // Warning 警告 85 | func (ll *Logger) Warning(format string, v ...interface{}) { 86 | if LevelWarning > ll.level { 87 | return 88 | } 89 | msg := fmt.Sprintf(format, v...) 90 | ll.Println("Warning", msg) 91 | } 92 | 93 | // Info 信息 94 | func (ll *Logger) Info(format string, v ...interface{}) { 95 | if LevelInformational > ll.level { 96 | return 97 | } 98 | msg := fmt.Sprintf(format, v...) 99 | ll.Println("Info", msg) 100 | } 101 | 102 | // Debug 校验 103 | func (ll *Logger) Debug(format string, v ...interface{}) { 104 | if LevelDebug > ll.level { 105 | return 106 | } 107 | msg := fmt.Sprintf(format, v...) 108 | ll.Println("Debug", msg) 109 | } 110 | 111 | // Print GORM 的 Logger实现 112 | //func (ll *Logger) Print(v ...interface{}) { 113 | // if LevelDebug > ll.level { 114 | // return 115 | // } 116 | // msg := fmt.Sprintf("[SQL] %s", v...) 117 | // ll.Println(msg) 118 | //} 119 | 120 | // BuildLogger 构建logger 121 | func BuildLogger(level string) { 122 | intLevel := LevelError 123 | switch level { 124 | case "error": 125 | intLevel = LevelError 126 | case "warning": 127 | intLevel = LevelWarning 128 | case "info": 129 | intLevel = LevelInformational 130 | case "debug": 131 | intLevel = LevelDebug 132 | } 133 | l := Logger{ 134 | level: intLevel, 135 | } 136 | GloablLogger = &l 137 | } 138 | 139 | // Log 返回日志对象 140 | func Log() *Logger { 141 | if GloablLogger == nil { 142 | l := Logger{ 143 | level: Level, 144 | } 145 | GloablLogger = &l 146 | } 147 | return GloablLogger 148 | } 149 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "github.com/HaliComing/fpp/bootstrap" 8 | "github.com/HaliComing/fpp/extractors" 9 | "github.com/HaliComing/fpp/models" 10 | "github.com/HaliComing/fpp/pkg/conf" 11 | "github.com/HaliComing/fpp/pkg/util" 12 | "github.com/HaliComing/fpp/routers" 13 | "github.com/elazarl/goproxy" 14 | "log" 15 | "net/http" 16 | "net/url" 17 | "os" 18 | "runtime" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | var ( 24 | confPath string 25 | ) 26 | 27 | func init() { 28 | flag.StringVar(&confPath, "c", "conf.ini", "配置文件路径") 29 | flag.Parse() 30 | bootstrap.Init(confPath) 31 | } 32 | 33 | func main() { 34 | runtime.GOMAXPROCS(runtime.NumCPU()) 35 | 36 | var wg sync.WaitGroup 37 | wg.Add(3) 38 | 39 | // 开启API接口 40 | go func() { 41 | defer wg.Done() 42 | RunApi() 43 | }() 44 | 45 | // 开启Proxy接口 46 | go func() { 47 | defer wg.Done() 48 | RunProxy() 49 | }() 50 | 51 | proxyIPChan := make(chan *models.ProxyIP, 2000) 52 | 53 | // IP提取器 54 | ticker := time.NewTicker(time.Duration(conf.SystemConfig.ExtractionInterval) * time.Minute) 55 | go func(ticker *time.Ticker) { 56 | defer wg.Done() 57 | for { 58 | RunExtractors(proxyIPChan) 59 | <-ticker.C // 等待时间到达 60 | } 61 | }(ticker) 62 | 63 | // IP消化器 启动n个go程 64 | for i := 0; i < conf.SystemConfig.NumberOfThreads; i++ { 65 | go RunCheck(proxyIPChan) 66 | } 67 | wg.Wait() 68 | } 69 | 70 | func RunApi() { 71 | api := routers.InitRouter() 72 | 73 | // 如果启用了SSL 74 | if conf.SSLConfig.CertPath != "" { 75 | go func() { 76 | util.Log().Info("[API] SSL Listen = %s", conf.SSLConfig.Listen) 77 | if err := api.RunTLS(conf.SSLConfig.Listen, 78 | conf.SSLConfig.CertPath, conf.SSLConfig.KeyPath); err != nil { 79 | util.Log().Error("[API] SSL Listen = %s, Error = %s", conf.SSLConfig.Listen, err) 80 | } 81 | }() 82 | } 83 | 84 | util.Log().Info("[API] Listen = %s", conf.SystemConfig.Listen) 85 | if err := api.Run(conf.SystemConfig.Listen); err != nil { 86 | util.Log().Error("[API] Listen = %s, Error = %s", conf.SystemConfig.Listen, err) 87 | } 88 | } 89 | 90 | func RunProxy() { 91 | proxy := goproxy.NewProxyHttpServer() 92 | proxy.Logger = log.New(os.Stderr, "[proxy] ", log.LstdFlags) 93 | proxy.Verbose = conf.SystemConfig.Debug 94 | proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 95 | println("DoFunc", req.URL.Scheme) 96 | switchProxy(proxy, []int{models.ProtocolTypeHTTP, models.ProtocolTypeHTTPS}) 97 | return req, nil 98 | }) 99 | proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { 100 | println("HandleConnectFunc") 101 | switchProxy(proxy, []int{models.ProtocolTypeHTTPS, models.ProtocolTypeALL}) 102 | return goproxy.OkConnect, host 103 | }) 104 | log.Fatal(http.ListenAndServe(conf.SystemConfig.ProxyListen, proxy)) 105 | } 106 | 107 | func switchProxy(proxy *goproxy.ProxyHttpServer, protocols []int) { 108 | random, err := models.ProxyRandom(nil, protocols, nil) 109 | useProxy := true 110 | var proxyStr string 111 | var proxyURL *url.URL 112 | if err != nil { 113 | useProxy = false 114 | } else { 115 | proxyStr = fmt.Sprintf("http://%s:%s", random.IP, random.Port) 116 | proxyURL, err = url.Parse(proxyStr) 117 | if err != nil { 118 | useProxy = false 119 | } 120 | } 121 | if useProxy { 122 | proxy.Tr = &http.Transport{ 123 | Proxy: http.ProxyURL(proxyURL), 124 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 125 | } 126 | proxy.ConnectDial = proxy.NewConnectDialToProxy(proxyStr) 127 | } else { 128 | proxy.Tr = &http.Transport{ 129 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 130 | } 131 | proxy.ConnectDial = nil 132 | } 133 | } 134 | 135 | func RunExtractors(proxyIPChan chan *models.ProxyIP) { 136 | proxyIPs := extractors.Extract() 137 | for _, proxyIP := range proxyIPs { 138 | proxyIPChan <- proxyIP 139 | } 140 | } 141 | 142 | func RunCheck(proxyIPChan chan *models.ProxyIP) { 143 | for { 144 | proxyIP := <-proxyIPChan 145 | if proxyIP.CheckIP() { 146 | util.Log().Info("[CheckIP] testIP = %s:%s ,Model = %+v", proxyIP.IP, proxyIP.Port, proxyIP) 147 | models.Save(proxyIP) 148 | } else { 149 | models.Delete(proxyIP) 150 | } 151 | if proxyIP.Score > 0 { 152 | timer := time.NewTimer(time.Duration(conf.SystemConfig.CheckInterval) * time.Minute) 153 | go func(timer *time.Timer, proxyIP *models.ProxyIP, proxyIPChan chan *models.ProxyIP) { 154 | <-timer.C 155 | proxyIPChan <- proxyIP 156 | }(timer, proxyIP, proxyIPChan) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fpp (free proxy pool)免费代理池 2 | 3 | ## 介绍 4 | 这是一个基于Golang开发的免费HTTP代理池。定时采集网上发布的免费代理验证并入库,同时你也可以在`extractors`下扩展代码来增加代理IP池的数量。 5 | 6 | ### 新特性与未来规划 7 | 8 | > (最近在忙其他项目,此暂停维护一段时间,有能力可以clone仓库改改,欢迎Pr呦~2022/04/02) 9 | > **比较仓促,代码未优化** 10 | 11 | - [x] **抓取校验**:自动抓取公开代理网站并校验入库 12 | - [x] **集成代理自动切换IP**(实验):其他需要代理IP的项目再也不用主动维护代理池啦,所有请求设置这个fpp代理池的代理服务器后,fpp会自动为每个请求随机切换IP。让您倾注于数据业务的开发。 13 | - [ ] **全球IP扫描模块**:自动扫描可用代理并验证入库 14 | - [ ] **付费代理集成或自有代理导入**:在自动切换IP的基础上集成付费代理或导入自有代理,美滋滋,需要使用代理的程序只需要请求时设置fpp的代理后就可以直接使用高可用的代理了。 15 | 16 | ## 下载与运行 17 | 18 | ### 下载 19 | 20 | 前往`https://github.com/HaliComing/fpp/releases`,下载适用于您目标机器操作系统、CPU架构的主程序,解压直接运行即可。 21 | 22 | ### 运行 23 | 24 | ```shell 25 | # 解压程序包 26 | tar -zxvf fpp_VERSION_OS_ARCH.tar.gz 27 | 28 | # 赋予执行权限 29 | chmod +x ./fpp 30 | 31 | # 启动 fpp 32 | ./fpp 33 | ``` 34 | 35 | ## 配置 36 | 37 | 首次直接运行会生成`conf.ini`配置文件,首次请勿自己创建。 38 | 39 | ```ini 40 | [System] 41 | ; 是否Debug运行,默认false 42 | ; Debug = false 43 | ; api的HTTP监听端口,默认9826 44 | Listen = :9826 45 | ; 代理服务器的HTTP监听端口,默认9827。建议关闭此端口的防火墙。 46 | ProxyListen = :9827 47 | ; 检测线程数,默认50 48 | NumberOfThreads = 70 49 | ; Token 默认会随机生成 50 | Token = STO64ysaLOte9J732YF1aw1Gf17xsnTV 51 | ; IP提取时间间隔,单位分钟,默认60 52 | ExtractionInterval = 45 53 | ; 检测时间间隔,单位分钟,默认15 54 | CheckInterval = 10 55 | 56 | [Database] 57 | ; 数据库类型,支持memory和sqlite3,默认sqlite3 58 | ; 由于memory类型会导致no such table,暂时不推荐,如果您能解决欢迎issue讨论。 59 | Type = sqlite3 60 | ; Type为sqlite3时启用此字段,数据库名称 61 | DBFile = fpp.db 62 | 63 | [SSL] 64 | ; api的HTTPS监听端口 65 | Listen = :443 66 | ; 证书位置 67 | CertPath = cert 68 | ; key位置 69 | KeyPath = key 70 | ``` 71 | 72 | ## 接口 73 | 74 | 简单的接口文档,详情请运行`fpp`后打开`/`即可查看。例如:`http://localhost:9826/` 75 | 76 | | api | method | Description | 77 | | ----| ---- | ---- | 78 | | / | GET | api介绍和文档 | 79 | | /api/v1/site/ping | GET | 服务连通测试 | 80 | | /api/v1/site/count | GET | 统计接口 | 81 | | /api/v1/proxy/random | GET | 随机获取一个IP接口 | 82 | | /api/v1/proxy/all | GET | 分页查询全部IP的接口 | 83 | | /api/v1/proxy/delete | GET | 删除指定IP | 84 | 85 | ## 构建 86 | 87 | 自行构建前需要拥有 `Go >= 1.13` 必要依赖。 88 | 89 | #### 克隆代码 90 | 91 | ```shell 92 | git clone https://github.com/HaliComing/fpp.git 93 | ``` 94 | 95 | #### 嵌入静态资源 96 | 97 | ```shell 98 | # 回到项目主目录 99 | # 将静态资源copy在assets/build/目录下 100 | 101 | # 安装 statik, 用于嵌入静态资源 102 | go get github.com/rakyll/statik 103 | 104 | # 开始嵌入 105 | statik -src=assets/build/ -include=*.html,*.ico,*.icon -f 106 | ``` 107 | 108 | #### 编译项目 109 | 您可以选择在Releases界面下载已编译好的二进制文件。手动编译如下: 110 | ```shell 111 | # 开始编译 112 | # 必须开启CGO_ENABLED 113 | SET CGO_ENABLED=1 114 | 115 | go build -a -o fpp.exe -ldflags "-s" 116 | ``` 117 | 118 | ## 提取器目录 119 | 120 | 不分先后顺序,如果您有更好的代理网站欢迎提交issue 121 | 122 | | 代理名称 | 代理网址 | 提取器包名 | 123 | | ---------------------- | ---------------------- | ------------- | 124 | | 齐云代理 | www.7yip.cn | yip7 | 125 | | 66免费代理 | www.66ip.cn | ip66 | 126 | | 89免费代理 | www.89ip.cn | ip89 | 127 | | 全网代理IP | www.goubanjia.com | goubanjia | 128 | | IP3366云代理 | www.ip3366.net | ip3366 | 129 | | 高可用全球免费代理IP库 | ip.jiangxianli.com | jiangxianli | 130 | | 快代理 | www.kuaidaili.com | kuaidaili | 131 | | ProxyList+ | list.proxylistplus.com | proxylistplus | 132 | | 方法SEO代理 | seofangfa.com | seofangfa | 133 | | 速代理 | www.sudaili.com | sudaili | 134 | | 西拉免费代理IP | www.xiladaili.com | xiladaili | 135 | 136 | ## 添加自定义提取器 137 | 138 | 首先download代码后,在`extractors`文件夹中创建需要抓取的代理网站文件夹和go文件,并实现`Extract`接口,以下以`example.com`举例。 139 | 140 | ``` 141 | 目录 142 | extractors #提取器文件夹 143 | |--example #示例提取器 144 | |--|--example.go #示例提取器代码 145 | |--|--example_test.go #示例提取器测试代码 146 | |--extractors.go #提取者 147 | ``` 148 | 149 | `example.go`,只需要修改`Key`接口和`Extract`接口即可。 150 | 151 | ```go 152 | package example 153 | 154 | import ( 155 | "github.com/HaliComing/fpp/extractors/types" 156 | "github.com/HaliComing/fpp/models" 157 | "github.com/HaliComing/fpp/pkg/request" 158 | "github.com/PuerkitoBio/goquery" 159 | "net/http" 160 | "regexp" 161 | "strings" 162 | ) 163 | 164 | type extractor struct{} 165 | 166 | // New returns a extractor. 167 | func New() types.Extractor { 168 | return &extractor{} 169 | } 170 | 171 | // 修改此方法 172 | func (e *extractor) Key() string { 173 | // 改为代理网站的域名,不带http前缀 174 | return "www.example.com" 175 | } 176 | 177 | // Extract is the main function to extract the data. 178 | func (e *extractor) Extract() ([]*models.ProxyIP, error) { 179 | var proxyIPs []*models.ProxyIP 180 | // 抓取数据的代码,可参考其他提取器代码,代码很简陋,有更好的可以提issue或pr 181 | // ... 182 | // proxyIP := &models.ProxyIP{ 183 | // IP: ip, //只需要填充IP和Port,其他勿填 184 | // Port: port,//只需要填充IP和Port,其他勿填 185 | // } 186 | //proxyIPs = append(proxyIPs, proxyIP) 187 | return proxyIPs, nil 188 | } 189 | 190 | ``` 191 | 192 | `example_test.go`别忘了测试类,如无其他情况测试类无需改动。 193 | 194 | ```go 195 | package example 196 | 197 | import ( 198 | "fmt" 199 | "testing" 200 | ) 201 | 202 | // 测试提取 203 | func TestExtract(t *testing.T) { 204 | extract, err := New().Extract() 205 | if err != nil { 206 | fmt.Println(err) 207 | } else { 208 | for _, ip := range extract { 209 | // 打印出提取出来的代理 210 | fmt.Printf("%s:%s\n", ip.IP, ip.Port) 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | 在`extractors.go`的`init`方法添加示例提取器。 217 | 218 | ```go 219 | func init() { 220 | extractors = []types.Extractor{ 221 | yip7.New(), 222 | ip66.New(), 223 | ip89.New(), 224 | goubanjia.New(), 225 | ip3366.New(), 226 | jiangxianli.New(), 227 | kuaidaili.New(), 228 | proxylistplus.New(), 229 | seofangfa.New(), 230 | sudaili.New(), 231 | xiladaili.New(), 232 | example.New(),// 添加示例提取器,注意后面的逗号 233 | } 234 | } 235 | ``` 236 | 237 | ## 代理服务自动切换IP(实验) 238 | 这是一个模拟请求的python脚本。`api.ipify.org`是一个获取本机IP地址的接口,使用代理池的代理服务器请求会为每次请求随机切换代理IP。 239 | ``` 240 | import requests 241 | 242 | def test(): 243 | rsp = requests.get("http://api.ipify.org/",proxies={"http": "localhost:9827"}) 244 | return rsp.text 245 | 246 | print(test()) 247 | print(test()) 248 | print(test()) 249 | ``` 250 | 终端执行后结果如下。可以看到每次请求返回结果都会显示不同的IP地址。 251 | ``` 252 | >$ python main.py 253 | 223.96.90.216 254 | 151.106.18.124 255 | 120.133.231.92 256 | 257 | >$ 258 | ``` 259 | ## 最后 260 | 261 | - 首先感谢您的使用,如果喜欢本程序,不妨给个star 262 | - 若您发现bug或者建议,欢迎提交issue 263 | - 若您愿意贡献代码那就更棒啦,欢迎提交Pr 264 | -------------------------------------------------------------------------------- /assets/index.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | ## fpp代理池接口文档 6 | 7 | 8 | ### 1. 服务测试接口 9 | 10 | #### 接口功能 11 | 12 | > 测试服务是否在线 13 | 14 | #### URL 15 | 16 | > [/api/v1/site/ping](/api/v1/site/ping) 17 | 18 | #### 支持格式 19 | 20 | > JSON 21 | 22 | #### HTTP请求方式 23 | 24 | > GET 25 | 26 | #### 请求参数 27 | 28 | |参数|必选|类型|说明| 29 | |:----- |:-------|:-----|----- | 30 | |token |是 |string|服务启动时配置的令牌 | 31 | 32 | #### 返回字段 33 | 34 | |返回字段|字段类型|说明 | 35 | |:----- |:------|:----------------------------- | 36 | |Code | number |状态码 0正常 非0异常 | 37 | |Msg | string | 状态信息 | 38 | |Data | string |版本号 | 39 | 40 | #### 接口示例 41 | 42 | > 地址:[http://www.example.com/api/v1/site/ping](http://www.example.com/api/v1/site/ping) 43 | ```json 44 | { 45 | "Code":0, 46 | "Data":"0.0.1", 47 | "Msg":"" 48 | } 49 | ``` 50 | 51 | 52 | ### 2. 统计接口 53 | 54 | #### 接口功能 55 | 56 | > 获取当前服务总IP数量 57 | 58 | #### URL 59 | 60 | > [/api/v1/site/count](/api/v1/site/count) 61 | 62 | #### 支持格式 63 | 64 | > JSON 65 | 66 | #### HTTP请求方式 67 | 68 | > GET 69 | 70 | #### 请求参数 71 | 72 | |参数|必选|类型|说明| 73 | |:----- |:-------|:-----|----- | 74 | |token |是 |string|服务启动时配置的令牌 | 75 | 76 | #### 返回字段 77 | 78 | |返回字段|字段类型|说明 | 79 | |:----- |:------|:----------------------------- | 80 | |Code | number |状态码 0正常 非0异常 | 81 | |Msg | string | 状态信息 | 82 | |Data | string |版本号 | 83 | |Count | number |IP总数 | 84 | 85 | #### 接口示例 86 | 87 | > 地址:[http://www.example.com/api/v1/site/count](http://www.example.com/api/v1/site/count) 88 | ```json 89 | { 90 | "Code":0, 91 | "Data":{ 92 | "Count":19 93 | }, 94 | "Msg":"" 95 | } 96 | ``` 97 | 98 | 99 | ### 3. 随机获取一个IP接口 100 | 101 | #### 接口功能 102 | 103 | > 随机获取一个IP接口 104 | 105 | #### URL 106 | 107 | > [/api/v1/proxy/random](/api/v1/proxy/random) 108 | 109 | #### 支持格式 110 | 111 | > JSON 112 | 113 | #### HTTP请求方式 114 | 115 | > GET 116 | 117 | #### 请求参数 118 | 119 | |参数|必选|类型|说明| 120 | |:----- |:-------|:-----|----- | 121 | |token |是 |string|服务启动时配置的令牌 | 122 | |anonymous |否 |number|匿名程度 透明:1 普通匿名:2 欺骗匿名:3 高匿:4 | 123 | |protocol |否 |number|协议 HTTP:1 HTTPS:2 HTTP/HTTPS:3 | 124 | |country |否 |string|国家 例如:中国 | 125 | 126 | #### 返回字段 127 | 128 | |返回字段|字段类型|说明 | 129 | |:----- |:------|:----------------------------- | 130 | |Code | number |状态码 0正常 非0异常 | 131 | |Msg | string | 状态信息 | 132 | |Data | string |版本号 | 133 | |IP | string |IP地址 | 134 | |Port | string |端口 | 135 | |Protocol | number |支持协议 HTTP:1 HTTPS:2 HTTP/HTTPS:3 | 136 | |Anonymous | number |匿名程度 透明:1 普通匿名:2 欺骗匿名:3 高匿:4 | 137 | |Country | string |国家 | 138 | |Province | string |省份 | 139 | |Attribution | string |归属地 | 140 | |ISP | string |运营商 | 141 | |Score | number |当前分数 满分10分 | 142 | |Source | string |来源地址 | 143 | |Speed | number |响应速度 单位ms | 144 | |CreateTime | string |创建时间 | 145 | |LastTime | string |最后检测时间 | 146 | |FailedCount | number |连续失败次数 | 147 | 148 | #### 接口示例 149 | 150 | > 地址:[http://www.example.com/api/v1/proxy/random](http://www.example.com/api/v1/proxy/random) 151 | ```json 152 | { 153 | "Code":0, 154 | "Data":{ 155 | "IP":"113.214.13.1", 156 | "Port":"1080", 157 | "Protocol":1, 158 | "Anonymous":4, 159 | "Country":"中国", 160 | "Province":"浙江省", 161 | "Attribution":"宁波市", 162 | "ISP":"华数", 163 | "Score":10, 164 | "Source":"ip.jiangxianli.com", 165 | "Speed":1574, 166 | "CreateTime":"2021-03-16 09:21:22", 167 | "LastTime":"2021-03-16 09:31:36", 168 | "FailedCount":0 169 | }, 170 | "Msg":"" 171 | } 172 | ``` 173 | 174 | 175 | ### 4. 查询全部IP接口 176 | 177 | #### 接口功能 178 | 179 | > 分页查询全部IP的接口 180 | 181 | #### URL 182 | 183 | > [/api/v1/proxy/all](/api/v1/proxy/all) 184 | 185 | #### 支持格式 186 | 187 | > JSON 188 | 189 | #### HTTP请求方式 190 | 191 | > GET 192 | 193 | #### 请求参数 194 | 195 | |参数|必选|类型|说明| 196 | |:----- |:-------|:-----|----- | 197 | |anonymous |否 |number|匿名程度 透明:1 普通匿名:2 欺骗匿名:3 高匿:4 | 198 | |protocol |否 |number|协议 HTTP:1 HTTPS:2 HTTP/HTTPS:3 | 199 | |country |否 |string|国家 例如:中国 | 200 | |page |否 |number|页数,默认1开始 | 201 | |size |否 |number|每页数量,默认10 | 202 | |token |是 |string|服务启动时配置的令牌 | 203 | 204 | #### 返回字段 205 | 206 | |返回字段|字段类型|说明 | 207 | |:----- |:------|:----------------------------- | 208 | |Code | number |状态码 0正常 非0异常 | 209 | |Msg | string | 状态信息 | 210 | |Data | string |版本号 | 211 | |IP | string |IP地址 | 212 | |Port | string |端口 | 213 | |Protocol | number |支持协议 HTTP:1 HTTPS:2 HTTP/HTTPS:3 | 214 | |Anonymous | number |匿名程度 透明:1 普通匿名:2 欺骗匿名:3 高匿:4 | 215 | |Country | string |国家 | 216 | |Province | string |省份 | 217 | |Attribution | string |归属地 | 218 | |ISP | string |运营商 | 219 | |Score | number |当前分数 满分10分 | 220 | |Source | string |来源地址 | 221 | |Speed | number |响应速度 单位ms毫秒 | 222 | |CreateTime | string |创建时间 | 223 | |LastTime | string |最后检测时间 | 224 | |FailedCount | number |连续失败次数 | 225 | 226 | #### 接口示例 227 | 228 | > 地址:[http://www.example.com/api/v1/proxy/all](http://www.example.com/api/v1/proxy/all) 229 | ```json 230 | { 231 | "Code":0, 232 | "Data":[ 233 | { 234 | "IP":"113.214.13.1", 235 | "Port":"1080", 236 | "Protocol":1, 237 | "Anonymous":4, 238 | "Country":"中国", 239 | "Province":"浙江省", 240 | "Attribution":"宁波市", 241 | "ISP":"华数", 242 | "Score":10, 243 | "Source":"ip.jiangxianli.com", 244 | "Speed":1574, 245 | "CreateTime":"2021-03-16 09:21:22", 246 | "LastTime":"2021-03-16 09:31:36", 247 | "FailedCount":0 248 | } 249 | ], 250 | "Msg":"" 251 | } 252 | ``` 253 | 254 | 255 | ### 5. 删除IP接口 256 | 257 | #### 接口功能 258 | 259 | > 删除指定IP,当获取IP使用无效时可使用此接口进行删除 260 | 261 | #### URL 262 | 263 | > [/api/v1/proxy/delete](/api/v1/proxy/delete) 264 | 265 | #### 支持格式 266 | 267 | > JSON 268 | 269 | #### HTTP请求方式 270 | 271 | > GET 272 | 273 | #### 请求参数 274 | 275 | |参数|必选|类型|说明| 276 | |:----- |:-------|:-----|----- | 277 | |ip |是 |string|欲删除IP地址 | 278 | |port |是 |string|欲删除端口号 | 279 | |proxy |否 |string|欲删除的代理,格式:ip:port,proxy参数存在时ip和port参数失效 | 280 | |token |是 |string|服务启动时配置的令牌 | 281 | 282 | #### 返回字段 283 | 284 | |返回字段|字段类型|说明 | 285 | |:----- |:------|:----------------------------- | 286 | |Code | number |状态码 0正常 非0异常 | 287 | |Msg | string | 状态信息 | 288 | |Data | string |无 | 289 | 290 | #### 接口示例 291 | 292 | > 地址:[http://www.example.com/api/v1/proxy/delete](http://www.example.com/api/v1/proxy/delete) 293 | ```json 294 | { 295 | "Code":0, 296 | "Data":"", 297 | "Msg":"" 298 | } 299 | ``` 300 | 301 | -------------------------------------------------------------------------------- /pkg/request/request.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | sj "github.com/bitly/go-simplejson" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "net/url" 14 | "time" 15 | 16 | "github.com/HaliComing/fpp/pkg/serializer" 17 | "github.com/HaliComing/fpp/pkg/util" 18 | ) 19 | 20 | // GeneralClient 通用 HTTP Client 21 | var GeneralClient Client = HTTPClient{} 22 | 23 | // Response 请求的响应或错误信息 24 | type Response struct { 25 | Err error 26 | Response *http.Response 27 | } 28 | 29 | // Client 请求客户端 30 | type Client interface { 31 | Request(method, target string, body io.Reader, opts ...Option) *Response 32 | } 33 | 34 | // HTTPClient 实现 Client 接口 35 | type HTTPClient struct { 36 | } 37 | 38 | // Option 发送请求的额外设置 39 | type Option interface { 40 | apply(*options) 41 | } 42 | 43 | type options struct { 44 | timeout time.Duration 45 | header http.Header 46 | signTTL int64 47 | ctx context.Context 48 | contentLength int64 49 | } 50 | 51 | type optionFunc func(*options) 52 | 53 | func (f optionFunc) apply(o *options) { 54 | f(o) 55 | } 56 | 57 | func newDefaultOption() *options { 58 | return &options{ 59 | header: http.Header{}, 60 | timeout: time.Duration(30) * time.Second, 61 | contentLength: -1, 62 | } 63 | } 64 | 65 | // WithTimeout 设置请求超时 66 | func WithTimeout(t time.Duration) Option { 67 | return optionFunc(func(o *options) { 68 | o.timeout = t 69 | }) 70 | } 71 | 72 | // WithContext 设置请求上下文 73 | func WithContext(c context.Context) Option { 74 | return optionFunc(func(o *options) { 75 | o.ctx = c 76 | }) 77 | } 78 | 79 | // WithHeader 设置请求Header 80 | func WithHeader(header http.Header) Option { 81 | return optionFunc(func(o *options) { 82 | for k, v := range header { 83 | o.header[k] = v 84 | } 85 | }) 86 | } 87 | 88 | // WithoutHeader 设置清除请求Header 89 | func WithoutHeader(header []string) Option { 90 | return optionFunc(func(o *options) { 91 | for _, v := range header { 92 | delete(o.header, v) 93 | } 94 | 95 | }) 96 | } 97 | 98 | // WithContentLength 设置请求大小 99 | func WithContentLength(s int64) Option { 100 | return optionFunc(func(o *options) { 101 | o.contentLength = s 102 | }) 103 | } 104 | 105 | // Request 发送HTTP请求 106 | func (c HTTPClient) Request(method, target string, body io.Reader, opts ...Option) *Response { 107 | // 应用额外设置 108 | options := newDefaultOption() 109 | for _, o := range opts { 110 | o.apply(options) 111 | } 112 | 113 | // 创建请求客户端 114 | client := &http.Client{Timeout: options.timeout} 115 | 116 | // size为0时将body设为nil 117 | if options.contentLength == 0 { 118 | body = nil 119 | } 120 | 121 | // 创建请求 122 | var ( 123 | req *http.Request 124 | err error 125 | ) 126 | if options.ctx != nil { 127 | req, err = http.NewRequestWithContext(options.ctx, method, target, body) 128 | } else { 129 | req, err = http.NewRequest(method, target, body) 130 | } 131 | if err != nil { 132 | return &Response{Err: err} 133 | } 134 | 135 | // 添加请求相关设置 136 | req.Header = options.header 137 | if options.contentLength != -1 { 138 | req.ContentLength = options.contentLength 139 | } 140 | 141 | // 发送请求 142 | resp, err := client.Do(req) 143 | if err != nil { 144 | return &Response{Err: err} 145 | } 146 | 147 | return &Response{Err: nil, Response: resp} 148 | } 149 | 150 | // GetResponse 检查响应并获取响应正文 151 | func (resp *Response) GetResponse() (string, error) { 152 | if resp.Err != nil { 153 | return "", resp.Err 154 | } 155 | respBody, err := ioutil.ReadAll(resp.Response.Body) 156 | _ = resp.Response.Body.Close() 157 | 158 | return string(respBody), err 159 | } 160 | 161 | // CheckHTTPResponse 检查请求响应HTTP状态码 162 | func (resp *Response) CheckHTTPResponse(status int) *Response { 163 | if resp.Err != nil { 164 | return resp 165 | } 166 | 167 | // 检查HTTP状态码 168 | if resp.Response.StatusCode != status { 169 | resp.Err = fmt.Errorf("服务器返回非正常HTTP状态%d", resp.Response.StatusCode) 170 | } 171 | return resp 172 | } 173 | 174 | // DecodeResponse 尝试解析为serializer.Response,并对状态码进行检查 175 | func (resp *Response) DecodeResponse() (*serializer.Response, error) { 176 | if resp.Err != nil { 177 | return nil, resp.Err 178 | } 179 | 180 | respString, err := resp.GetResponse() 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | var res serializer.Response 186 | err = json.Unmarshal([]byte(respString), &res) 187 | if err != nil { 188 | util.Log().Debug("[Conf] Unable to parse callback server response, Error = %s", string(respString)) 189 | return nil, err 190 | } 191 | return &res, nil 192 | } 193 | 194 | // NopRSCloser 实现不完整seeker 195 | type NopRSCloser struct { 196 | body io.ReadCloser 197 | status *rscStatus 198 | } 199 | 200 | type rscStatus struct { 201 | // http.ServeContent 会读取一小块以决定内容类型, 202 | // 但是响应body无法实现seek,所以此项为真时第一个read会返回假数据 203 | IgnoreFirst bool 204 | 205 | Size int64 206 | } 207 | 208 | // GetRSCloser 返回带有空seeker的RSCloser,供http.ServeContent使用 209 | func (resp *Response) GetRSCloser() (*NopRSCloser, error) { 210 | if resp.Err != nil { 211 | return nil, resp.Err 212 | } 213 | 214 | return &NopRSCloser{ 215 | body: resp.Response.Body, 216 | status: &rscStatus{ 217 | Size: resp.Response.ContentLength, 218 | }, 219 | }, resp.Err 220 | } 221 | 222 | // SetFirstFakeChunk 开启第一次read返回空数据 223 | func (instance NopRSCloser) SetFirstFakeChunk() { 224 | instance.status.IgnoreFirst = true 225 | } 226 | 227 | // SetContentLength 设置数据流大小 228 | func (instance NopRSCloser) SetContentLength(size int64) { 229 | instance.status.Size = size 230 | } 231 | 232 | // Read 实现 NopRSCloser reader 233 | func (instance NopRSCloser) Read(p []byte) (n int, err error) { 234 | if instance.status.IgnoreFirst && len(p) == 512 { 235 | return 0, io.EOF 236 | } 237 | return instance.body.Read(p) 238 | } 239 | 240 | // Close 实现 NopRSCloser closer 241 | func (instance NopRSCloser) Close() error { 242 | return instance.body.Close() 243 | } 244 | 245 | // Seek 实现 NopRSCloser seeker, 只实现seek开头/结尾以便http.ServeContent用于确定正文大小 246 | func (instance NopRSCloser) Seek(offset int64, whence int) (int64, error) { 247 | // 进行第一次Seek操作后,取消忽略选项 248 | if instance.status.IgnoreFirst { 249 | instance.status.IgnoreFirst = false 250 | } 251 | if offset == 0 { 252 | switch whence { 253 | case io.SeekStart: 254 | return 0, nil 255 | case io.SeekEnd: 256 | return instance.status.Size, nil 257 | } 258 | } 259 | return 0, errors.New("未实现") 260 | 261 | } 262 | 263 | func RequestProxy(testUrl, proxy string) (bool, string, error) { 264 | proxyURL, _ := url.Parse(proxy) 265 | 266 | tlsConfig := &tls.Config{InsecureSkipVerify: true} 267 | netTransport := &http.Transport{ 268 | Proxy: http.ProxyURL(proxyURL), 269 | TLSClientConfig: tlsConfig, 270 | MaxIdleConnsPerHost: 50, 271 | } 272 | httpClient := &http.Client{ 273 | Timeout: time.Second * 20, 274 | Transport: netTransport, 275 | } 276 | request, _ := http.NewRequest("GET", testUrl, nil) 277 | //设置一个header 278 | request.Header.Add("accept", "text/plain") 279 | resp, err := httpClient.Do(request) 280 | 281 | if err != nil { 282 | return false, "", err 283 | } 284 | 285 | defer resp.Body.Close() 286 | if resp.StatusCode == 200 { 287 | json, err := sj.NewFromReader(resp.Body) 288 | if err != nil { 289 | return false, "", err 290 | } 291 | origin, err := json.Get("origin").String() 292 | if err != nil { 293 | return false, "", err 294 | } 295 | return true, origin, nil 296 | } 297 | return false, "", errors.New("error") 298 | } 299 | 300 | func RequestIP(testUrl string) (string, error) { 301 | tlsConfig := &tls.Config{InsecureSkipVerify: true} 302 | netTransport := &http.Transport{ 303 | TLSClientConfig: tlsConfig, 304 | MaxIdleConnsPerHost: 50, 305 | } 306 | httpClient := &http.Client{ 307 | Timeout: time.Second * 20, 308 | Transport: netTransport, 309 | } 310 | request, _ := http.NewRequest("GET", testUrl, nil) 311 | //设置一个header 312 | request.Header.Add("accept", "text/plain") 313 | resp, err := httpClient.Do(request) 314 | 315 | if err != nil { 316 | return "", err 317 | } 318 | 319 | defer resp.Body.Close() 320 | if resp.StatusCode == 200 { 321 | json, err := sj.NewFromReader(resp.Body) 322 | if err != nil { 323 | return "", err 324 | } 325 | origin, err := json.Get("origin").String() 326 | if err != nil { 327 | return "", err 328 | } 329 | return origin, nil 330 | } 331 | return "", errors.New("error") 332 | } 333 | -------------------------------------------------------------------------------- /models/proxy.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/HaliComing/fpp/pkg/request" 8 | "github.com/HaliComing/fpp/pkg/util" 9 | "math/rand" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | var LocalIp = "" 17 | 18 | // 代理协议类型 19 | const ( 20 | // HTTP代理 21 | ProtocolTypeHTTP int = 1 22 | // HTTPS代理 23 | ProtocolTypeHTTPS int = 2 24 | // HTTP/HTTPS代理 25 | ProtocolTypeALL int = 3 26 | ) 27 | 28 | // 代理匿名类型 29 | const ( 30 | // 透明代理 31 | AnonymousTypeT int = 1 32 | // 匿名代理 33 | AnonymousTypeN int = 2 34 | // 混淆代理 35 | AnonymousTypeH int = 3 36 | // 高匿代理 37 | AnonymousTypeG int = 4 38 | ) 39 | 40 | // Proxy 代理IP模型 41 | type ProxyIP struct { 42 | IP string `gorm:"primary_key"` // IP 43 | Port string `gorm:"primary_key"` // Port端口 44 | Protocol int // 代理协议类型 HTTP:1 HTTPS:2 HTTP/HTTPS:3 45 | Anonymous int // 匿名类型 透明:1 普通匿名:2 欺骗匿名:3 高匿:4 46 | Country string // 国家 47 | Province string // 省 48 | Attribution string // 归属 全路径 49 | ISP string // 运营商 50 | Score int // 分数 默认10分 51 | Source string // 来源 52 | Speed int64 // 连接速度 单位ms 53 | CreateTime string // 创建时间 54 | LastTime string // 最后检测时间 55 | FailedCount int // 连续失败次数 扣分时采用 Score - (2 * FailedCount + 1) 56 | } 57 | 58 | func (proxyIP *ProxyIP) String() string { 59 | return "{IP:" + proxyIP.IP + 60 | " " + "Port:" + proxyIP.Port + 61 | " " + "Protocol:" + strconv.Itoa(proxyIP.Protocol) + 62 | " " + "Anonymous:" + strconv.Itoa(proxyIP.Anonymous) + 63 | " " + "Speed:" + strconv.FormatInt(proxyIP.Speed, 10) + 64 | " " + "FailedCount:" + strconv.Itoa(proxyIP.FailedCount) + 65 | " " + "Country:" + proxyIP.Country + 66 | " " + "Source:" + proxyIP.Source + 67 | "}" 68 | } 69 | 70 | const ( 71 | PrefixProtocolHttp = "http://" 72 | PrefixProtocolHttps = "https://" 73 | TestUrlHttp = "http://httpbin.org/get?show_env=1" 74 | TestUrlHttps = "https://httpbin.org/get?show_env=1" 75 | ) 76 | 77 | func (proxyIP *ProxyIP) CheckIP() bool { 78 | testIp := fmt.Sprintf("%s:%s", proxyIP.IP, proxyIP.Port) 79 | begin1 := time.Now() 80 | http, originHttp, errHttp := request.RequestProxy(TestUrlHttp, PrefixProtocolHttp+testIp) 81 | speedHttp := time.Now().Sub(begin1).Nanoseconds() / 1000 / 1000 //ms 82 | begin2 := time.Now() 83 | https, originHttps, errHttps := request.RequestProxy(TestUrlHttps, PrefixProtocolHttps+testIp) 84 | speedHttps := time.Now().Sub(begin2).Nanoseconds() / 1000 / 1000 //ms 85 | if http && https { 86 | proxyIP.Protocol = ProtocolTypeALL 87 | proxyIP.Speed = (speedHttp + speedHttps) / 2 88 | proxyIP.Anonymous = proxyIP.GetAnonymous(originHttps) 89 | } else if http { 90 | proxyIP.Protocol = ProtocolTypeHTTP 91 | proxyIP.Speed = speedHttp 92 | proxyIP.Anonymous = proxyIP.GetAnonymous(originHttp) 93 | } else if https { 94 | proxyIP.Protocol = ProtocolTypeHTTPS 95 | proxyIP.Speed = speedHttps 96 | proxyIP.Anonymous = proxyIP.GetAnonymous(originHttps) 97 | } 98 | proxyIP.LastTime = time.Now().Format("2006-01-02 15:04:05") 99 | if http || https { 100 | proxyIP.FailedCount = 0 101 | proxyIP.GetIpInfo() 102 | return true 103 | } 104 | proxyIP.Score = proxyIP.Score - (2*proxyIP.FailedCount + 1) 105 | proxyIP.FailedCount = proxyIP.FailedCount + 1 106 | if errHttp != nil || errHttps != nil { 107 | util.Log().Warning("[CheckIP] testIP = %s, ErrorHttp = %s, ErrorHttps = %s", testIp, errHttp, errHttps) 108 | } 109 | return false 110 | } 111 | 112 | type pos struct { 113 | Ct string `json:"ct"` 114 | Prov string `json:"prov"` 115 | City string `json:"city"` 116 | Area string `json:"area"` 117 | Idc string `json:"idc"` 118 | Yunyin string `json:"yunyin"` 119 | Net string `json:"net"` 120 | } 121 | 122 | // TODO ip_c_list数组值判断 123 | func (proxyIP *ProxyIP) GetIpInfo() { 124 | html := getHtml("https://www.ip138.com/iplookup.asp?ip=" + proxyIP.IP + "&action=2") 125 | if html == "" { 126 | return 127 | } 128 | html = util.ConvertToString(html, "GBK", "UTF-8") 129 | jsonStr := between(html, "\"ip_c_list\":[", "]") 130 | if jsonStr == "" { 131 | return 132 | } 133 | jsonStr = "[" + jsonStr + "]" 134 | var pos []pos 135 | err := json.Unmarshal([]byte(jsonStr), &pos) 136 | if err == nil { 137 | proxyIP.Country = strings.TrimSpace(pos[len(pos)-1].Ct) 138 | proxyIP.Province = strings.TrimSpace(pos[len(pos)-1].Prov) 139 | proxyIP.Attribution = strings.TrimSpace(pos[len(pos)-1].City + " " + pos[len(pos)-1].Area + " " + pos[len(pos)-1].Idc) 140 | proxyIP.ISP = strings.TrimSpace(pos[len(pos)-1].Yunyin + " " + pos[len(pos)-1].Net) 141 | } 142 | } 143 | 144 | func between(str, starting, ending string) string { 145 | s := strings.Index(str, starting) 146 | if s < 0 { 147 | return "" 148 | } 149 | s += len(starting) 150 | e := strings.Index(str[s:], ending) 151 | if e < 0 { 152 | return "" 153 | } 154 | return str[s : s+e] 155 | } 156 | 157 | func getHtml(url string) string { 158 | client := request.HTTPClient{} 159 | res, err := client.Request("GET", url, nil, 160 | request.WithHeader(http.Header{ 161 | "Referer": {"https://www.ip138.com/"}, 162 | "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"}, 163 | })).GetResponse() 164 | if err != nil { 165 | util.Log().Warning("[getHtml] Error = %s", err) 166 | return "" 167 | } 168 | return res 169 | } 170 | 171 | func (proxyIP *ProxyIP) GetAnonymous(origin string) int { 172 | if LocalIp == "" { 173 | return 0 174 | } 175 | if strings.Index(origin, ",") != -1 { 176 | if strings.Index(origin, LocalIp) != -1 { 177 | return AnonymousTypeN 178 | } else { 179 | return AnonymousTypeH 180 | } 181 | } else { 182 | if origin == proxyIP.IP { 183 | return AnonymousTypeG 184 | } else if origin == LocalIp { 185 | return AnonymousTypeT 186 | } else { 187 | return AnonymousTypeH 188 | } 189 | } 190 | } 191 | 192 | func Exist(proxyIP *ProxyIP) bool { 193 | var count int64 194 | DB.Model(&ProxyIP{}).Where("IP = ?", proxyIP.IP).Where("Port = ?", proxyIP.Port).Count(&count) 195 | if count == 0 { 196 | return false 197 | } else { 198 | return true 199 | } 200 | } 201 | 202 | func Save(proxyIP *ProxyIP) { 203 | DB.Save(proxyIP) 204 | } 205 | 206 | func Delete(proxyIP *ProxyIP) { 207 | DB.Delete(proxyIP) 208 | } 209 | 210 | func ProxyDelete(ip, port string) { 211 | proxyIP := &ProxyIP{ 212 | IP: ip, 213 | Port: port, 214 | } 215 | DB.Delete(proxyIP) 216 | } 217 | 218 | func ProxyCount() (int64, error) { 219 | var count int64 220 | result := DB.Model(&ProxyIP{}).Count(&count) 221 | if result.Error != nil { 222 | return 0, result.Error 223 | } 224 | return count, nil 225 | } 226 | 227 | func ProxyRandomCount(anonymous, protocols []int, countries []string) (int64, error) { 228 | var count int64 229 | db := DB.Model(&ProxyIP{}) 230 | if anonymous != nil && len(anonymous) != 0 { 231 | db = db.Where("Anonymous in (?)", anonymous) 232 | } 233 | if protocols != nil && len(protocols) != 0 { 234 | db = db.Where("Protocol in (?)", protocols) 235 | } 236 | if countries != nil && len(countries) != 0 { 237 | db = db.Where("Country in (?)", countries) 238 | } 239 | result := db.Count(&count) 240 | if result.Error != nil { 241 | return count, result.Error 242 | } 243 | if count == 0 { 244 | return count, errors.New("proxy ip number is 0") 245 | } 246 | return count, nil 247 | } 248 | 249 | func ProxyRandom(anonymous, protocols []int, countries []string) (ProxyIP, error) { 250 | count, err := ProxyRandomCount(anonymous, protocols, countries) 251 | if err != nil { 252 | return ProxyIP{}, err 253 | } 254 | var proxyIP ProxyIP 255 | db := DB 256 | if anonymous != nil && len(anonymous) != 0 { 257 | db = db.Where("Anonymous in (?)", anonymous) 258 | } 259 | if protocols != nil && len(protocols) != 0 { 260 | db = db.Where("Protocol in (?)", protocols) 261 | } 262 | if countries != nil && len(countries) != 0 { 263 | db = db.Where("Country in (?)", countries) 264 | } 265 | rand.Seed(time.Now().UnixNano()) 266 | randInt64 := rand.Int63n(count) 267 | result := db.Limit(1).Offset(randInt64).Find(&proxyIP) 268 | if result.Error != nil { 269 | return ProxyIP{}, result.Error 270 | } 271 | return proxyIP, nil 272 | } 273 | 274 | func ProxyAll(anonymous, protocols []int, countries []string, page, pageSize int) ([]ProxyIP, error) { 275 | var proxyIPs []ProxyIP 276 | db := DB 277 | if anonymous != nil && len(anonymous) != 0 { 278 | db = db.Where("Anonymous in (?)", anonymous) 279 | } 280 | if protocols != nil && len(protocols) != 0 { 281 | db = db.Where("Protocol in (?)", protocols) 282 | } 283 | if countries != nil && len(countries) != 0 { 284 | db = db.Where("Country in (?)", countries) 285 | } 286 | result := db.Limit(pageSize).Offset((page - 1) * pageSize).Find(&proxyIPs) 287 | if result.Error != nil { 288 | return nil, result.Error 289 | } 290 | return proxyIPs, nil 291 | } 292 | -------------------------------------------------------------------------------- /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.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= 4 | cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= 7 | github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 8 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 9 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 12 | github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= 13 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 14 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 15 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= 16 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= 17 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 18 | github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 19 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 20 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 21 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 22 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= 27 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= 28 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 29 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 30 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 31 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 32 | github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e h1:99KFda6F/mw8xSfceY2JEVCrYWX7l+Ms6BcO5wEct+Q= 33 | github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 34 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 35 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 36 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 37 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 38 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 39 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 40 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 41 | github.com/gin-contrib/cors v1.3.0 h1:PolezCc89peu+NgkIWt9OB01Kbzt6IP0J/JvkG6xxlg= 42 | github.com/gin-contrib/cors v1.3.0/go.mod h1:artPvLlhkF7oG06nK8v3U8TNz6IeX+w1uzCSEId5/Vc= 43 | github.com/gin-contrib/gzip v0.0.2-0.20200226035851-25bef2ef21e8 h1:/DnKeA2+K83hkii3nqMJ5koknI+/qlojjxgcSyiAyJw= 44 | github.com/gin-contrib/gzip v0.0.2-0.20200226035851-25bef2ef21e8/go.mod h1:M+xPw/lXk+uAU4iYVnwPZs0iIpR/KwSQSXcJabN+gPs= 45 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 46 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 47 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 48 | github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 h1:xLG16iua01X7Gzms9045s2Y2niNpvSY/Zb1oBwgNYZY= 49 | github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ= 50 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 51 | github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= 52 | github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= 53 | github.com/go-ini/ini v1.50.0 h1:ogX6RS8VstVN8MJcwhEP78hHhWaI3klN02+97bByabY= 54 | github.com/go-ini/ini v1.50.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 55 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 56 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 57 | github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= 58 | github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= 59 | github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= 60 | github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= 61 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 62 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 63 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 64 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 65 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 66 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 67 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 68 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 69 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 70 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 71 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 72 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 73 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 74 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 75 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 76 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 77 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 78 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 79 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 80 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 81 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 82 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 83 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 85 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 86 | github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= 87 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 88 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 89 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 90 | github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= 91 | github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= 92 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 93 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 94 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 95 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 96 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 97 | github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= 98 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 99 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 100 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 101 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 102 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 103 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 104 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 105 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 106 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 107 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 108 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 109 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 110 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 111 | github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= 112 | github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= 113 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 114 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 115 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 116 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 117 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 118 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 119 | github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= 120 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 121 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 122 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 123 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 124 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 125 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 126 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 127 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 128 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 129 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 130 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 131 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 132 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 133 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 134 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 135 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 136 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 137 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 138 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 139 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 140 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 141 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 142 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 143 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 144 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 145 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 146 | github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= 147 | github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= 148 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 149 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 150 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 151 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 152 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 153 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 154 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 155 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 156 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 157 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 158 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 159 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 160 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 161 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 162 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 163 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 164 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 165 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 166 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 167 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 168 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 169 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 170 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 171 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 172 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 173 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 174 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 175 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 176 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 177 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 178 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 179 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 180 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 181 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 182 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 183 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 184 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 185 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 186 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 187 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 188 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 189 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 190 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 191 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 192 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 197 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 198 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 199 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 200 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 201 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 202 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 203 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= 206 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 208 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 209 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 210 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 211 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 212 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 213 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 214 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 215 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 216 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 217 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 218 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 219 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 220 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 221 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 222 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 223 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 224 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 225 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 226 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 227 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 228 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 229 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 230 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 231 | gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= 232 | gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 233 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 234 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 235 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 236 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 237 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 238 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 239 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 240 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 241 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 242 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /statik/statik.go: -------------------------------------------------------------------------------- 1 | // Code generated by statik. DO NOT EDIT. 2 | 3 | package statik 4 | 5 | import ( 6 | "github.com/rakyll/statik/fs" 7 | ) 8 | 9 | func init() { 10 | data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x92*wR\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00 \x00index.htmlUT\x05\x00\x01\xa4zY`\xec}\xfd\x93\xe3\xc6u\xe0\xcf\xe1_\xd1\xe1Vn4\x1b\x80C\x80\x9f\xc3\xd9\x9d\xca\xee\xce\xae\xa3\x94\x9c\xa8\xb2r\xea\xaat\xba+\x10h\x0e\xa1\x05\x01\x18\x00\xe7C\xaa\xad\x92\x9cD\x96\xad\xcfJ,K\x96\x15[\x8adG\x95\xd8\x92u\x17\xdb:}D\xff\xcb\x95\xc8\xdd\xfdI\xff\xc2U\x7f\x01\xdd\x8d\x06 \x923\xbb\xb3#\xee\xd4\xce\x90\x8d\xee\xd7\xaf\xfb\xbd~\xfd\xfa\xbd\xd7\x0f\x97\xfe\xd4 \xec\xe48\x84`\x98\x8c\xbc\xdd\xca%\xf6\x07Z\xcen\xe5\xd2\x08&\x16\xb0\x87V\x14\xc3\xe4\xf2\xc6\xf7\x9e\xb8\xa1w7vI\xa9o\x8d\xe0\xe5\x8d\x03\x17\x1e\x86A\x94l\x00;\xf0\x13\xe8'\x977\x0e]'\x19^v\xe0\x81kC\x1d\x7f\x01\xae\xef&\xae\xe5\xe9\xb1my\xf0\xb2\xb1\xb1[\xb9\x94\xb8\x89\x07w\x07a\xf8\xf5\xe7\xef\xdfy\xfd\x85\xe9'\xefN_\xfd\xd5\xe4\xb5\xf7\xa7?\xfd\xe1\xf4\xbd\xf7/m\x91\xe7\x97<\xd7\xbf\x05\x86\x11\x1c\\\xde\x18\xb8\x1e\xecmm\xf9\xee\xfe0\xd9\x1a\xc1hd\xb9N\xcd\xb1\xa2[5;\x8e7@\x04\xbd\xcb\x1bqr\xec\xc1x\x08a\xb2\x01\xd0\xa8.o$\xf0(\xd9\xc2\x15\xb6\x8a\xa1\xd9\x81\x03\xfb^`\xdf:!xq0\x8el8\n\x1c\xb8 @\xfcX~\xb0\x8bh\x02\x9e\x0d\x0e`4\xf0\x82C\xfd\xa8\xc7&\x14\xfc\xa9;B\xb3o\xf9\xc9\xce\xed^\x14\x04 x\x16\xe8z\x7f_\xb7\x03/\x88z\x17\x06\xf8\xdf\x0e\xd0u\x04\x8d\x956\xf0?T\x1aC\x0f\xda y\x98\xb5\xba\xda\xdak\xdf\xb8&?\x1f\x04>\x83`\x8d\x93\x00=\x1e\x05~\x10\x87\x96\x0d{\xd5\xc7\xc6\xb6\xebX\xe0Z\xe0\xc7\x81\x07\xab\x1a\xf9`\xc5Z\xf5Z0\x8e\\\x18U\xb5\xb4\xf6\x0e\xb8]!c\x02\x18h\xec>\x03{\xc0h\x86G;\xa0o\xd9\xb7\xf6\xa3`\xec;\xb4/p`E\x8fdc\xda\xdc\x01By6\xae\xcd\x1d\x02m`\x8d\\\xef\xb8\x07\xaa\x7f \xbd\x03\x98\xb8\xb6\x05\xfe\x1a\x8eaU\x03i\x81\x06\xaeD\xae\xe5i \xb6\xfcX\x8fa\xe4\xa29:\x84\xfd[.\x1dg<\n\x82d\xe8\xfa\xfb=`\xf9h\xaa]+\x86\x0eB\xbc\x1f8\xc7\xe0Y0\xb2\xa2}\xd7\xef\x81:B:\xb4\x1c\x07\xd7\xc5\xdf\x86\x101A\x0f\x90Y\xea\x07I\x12\x8c\xe8\xa3$\x08\xe9'\x0f\x0e\x12\xfa1\"\xd5\xf1g~>\"8\xda\x01\x9e\xebC\x9dA4jM\xb3\xdb\xea\xec\x00\x9e\x17\x86\xae\xe3@\x9f\x9f9\xc4\x1fC\x18\xb9\xc9\x0eH\xac>\x05\xd7D\xc8\xbb\x83\xc8\x1aA\x0e}\x82\xe3\xed\x8aU\x1bG\x88\x1e\x87A\xe4\xe8\xfd\x08Z\xb7z\x00\xff\xd1-\xcf\xc3\x15z\x96\x9d\xb8\x07P\x03Vo\x88\xba\x07\xcf\x82`\x9c \xf4(\xea\xb7+5\xd7'\xf4 l\xe3\x06\xbe\x06z\xbd\xf4\x0bx\x16\x90\xa7C\xcb \x0e{\xc0\x0f|(\xa2MH\xaabJ\x99\xecj\xc6\xdcDX\\8\x8c\xdc\x04\x8a$\xa2\xe3\x14)\x83e\x137W\xfc\xd8\xfd \x1aY\x1e7\xd1\x87\x91\x15\xb2)A\x15w@\x18\xc4.\x1aV\x0f\xadl\x0b\xcd\xcd\x0e8\x1c\xba \xd4\xc9\x92\xc8\xc38\xea\x81\x037v\xfb\x1eLYF\xc7\x1c\xd1\xa4\x13HP\xaf\x0d\xdc(NtLy\xd7w\xa0\x9f\x80\x90\xcd\x1d\xf9\xde\x03&\xe2\x8d\x19\x0d<\x17\x84\x1a\x98\x01\xef\xa2\x0cq\x1e\n\x9e\x9bN\xa9N\x98\x97\xe2P\x1b\x04\x91\xee\x8e\xac}\x08\xd2\xa9g\xa3#\x15\xbb\xdc\x1a\xd1)\xb7wIwh5\xd5\x92\xe30\x88,\x1d\x1e!Y\x96k\xdd\xa8+\x9a7\x18\xcb\x89mk\x83 H\xfc \x81\x18u\x0dH\x8f=7W\x84fV\xa0Z\x18ALl\x04\xfd/F\xd0q-\x10\xdb\x11\x84>\xb0|\x07<2\xb2\x8et\xca8\xadz=<\xda\x04\xcfV\x00(1\x0c\xd5(\xe8 @\xd1\xc4\x99\xaaFf\xda\xaav-p\xe0w\xdd(\n\"\xbc\xc8#\x99B\x88\xf3\xb9-\"\xd7h\x7f\x9c$0\x8a\xc1\xb3\xc0q\xe3\xd0\xb3\x8e\xc9\xa2\x94\xda\xa4K\xcas\xc1.\x18\xb8\xfb\xe3\x08\xf6<+Nt{\xe8zN\xd6i*\xeaj\xad\x88\xe7O\x10x\x8c\x17\xc1\x18I\x19\xd5\xd2\xb9]qG\xfb\x18V:\xc1F\xbd\xfeg;\xe0\x00FHf{\xba\xe5\xb9\xfb~\x0f\x8c\\\xc7\xf1p\x83\xfe8I\x90\x8cq\xfdp\x9ch\x80\xc8\x04\x0d\xb3\xb5\x15A\x0b<\xcbdF\xba\xc4\x91\xa8\xe0\xbe\xde\xae\xe0\xa6O\xe2\x0d\xb7j\x0f\xa1}\xab\x1f\x1cU\x9f\xa2 iyd9nP}\n<+\nc\xb6\xbcE\xe1\x7f\xbbr\x11 =k\x90\xc0\x08}\xe8\xc3A\x10!\xca\xf6\x83#D$\\\xb3\x1fD\x0e\x8c\xf4~\xc0-904\xd2I\x1a\x9a\xd9\xc7F\xf6\xb1\x99}le\x1f\xdb\xe9\xc70\xfb\x84\xbb\x94\x05\xdcIvUH\xc5P\x9e'\xae{\xd4/\xea\x10\xf5\x84\xba@\xb0\x87m49d\xabA\x93\xd6\x03\xd6A\xe0:zh\xed\xa3\xdd\x01?p\xfd\xd8u }\xb2\x03\x82(\x1cZ~\xdc\x03&\xeb0-i\x92nD\xcd\xc2\xa4\xdc84%\x8d\xa3\xd6eO\x1a\xf2\x936{\xd2\x94\x9f4\xd9\x93\x96\xfc$\xed\xa7-=\xa1\xe5\xb5\x91\xa3\x8f\xacd\xa8c]S\x03\xe8{d\x1d\xd2o\xea\xc9\xd1\xf0T\xd3\x05\x867\x0b\x02MZri\x17D\x1b\x90W4\xeb\x1dw\x85\xd5\xf7tmD\xfb\xfdG\xcczS\x03\xec\x17\xd3\xa3\x0e)\xfd:\xf5:\xd3K\x90v\xda\x03nby\xae\x8d@b0\xe3(Fp\xc2\xc0\xf5\x13\x18\xa1\xe2x\x1c\xa2\xce\x98,\xce\xc4Z\x0f\x98\xe1\x11(\xd0\xf3\xa2\xfd\xbe\xf5\x88\xd9\xe8j \xfbU\xafu\xb2\x9d\x1fa\xdami\x80\xfc\xdf\xdca\xcb\x08\xad\xcf1\xa2=\x82[\x02\x1dK\x03\xb9\xa2T\xa7\x91%\x06\xde#\x93\xc8\xf2\xe3A\x10\x8d\xe4\x07\x0e\xb4\x83\xc8\"\x8b \xbf\xcc\xd4\xc2E5g\xf2JU\xac\x1d\"w\x11\xafs\xea\x04Qd\x98\xa2c\xd4L8\"b\x88\x93\xa2\xb6\xe5\xd9\x8f Q\n\xfe\x1c\x18\xed\xf0h3/\xb2(\xec]\xa4.z\xb2\xea$n\x06I\x94.WiU\x8ak\x98\xea\x95 :H\xf2\xdc\x88{\xd0Q)\x8ct\xc4\x00x\xa3e\xfdR\x8a\xda\x81\xe7Ya\x0c{\x80}J\x89\x8dv\xe9\x0cua\xa3`\x13\x93j\xde\"\x96\xb8\x0cS\x8dn$h\x8fL;G\xec@\xb0H\xf0\x8e\xe6\xfal\xfe\x1a&U5\x94\x1b'\xe3AnC\xcf\xb36GF\x1e\n\x12\x93\xfex\xd4\xc7\x8c7\x8e\xd1\xe8\xf0\x16\xc6\xad\xd9\xac6\xd3\xd7$\xe4yx\xa1g\xd9p\x18x\x0e\xd1\xce\xd1L%\xc7h;n\xc8\xb0\xc8\xfe\xc0\xb3\x01Y=y\xfcb\xa9\x1e\xaa\xe3\xb8\x07\xb5a\xd4\x1b\x04\xf68\xe6\xb8\x99!-\xeeAE\xba\x15S4\xa1o\xc3X\xf7\x03\xa2l\xa2\xa7\xa1\xeb\xef\x17\xb4\x16\xc1\xd7\x92c\xdd\x0e\xfc\xc4r}\xdd\x1e\xc9\xd5\xd9\xfe\\D9*A)\xe5\xd8\xf0\x91T\xc0(\x89\"\xbc^\xdb\xc6\x126\xe5c,I\x0b\xf6\xa7<\x9d2\xd6L\x0f\x00\xf9\xa1)\x0fp\xf9MVZ\x90\x08c\xc7\xb5\xf6#k\xa4\x87\x96\x0f\xbdl\xe3'\xcbB\xd8;\xc8)\x94\xc3\xcf\x86D\x02 \xe7\x11A\xedd[\x0c\xd6\xd8\xf3\xc2'%H6u\xb5Q`\xdfRP\x84'?_\x9b}\xd2\x0f\xdddHVE\xa0\xd6\x9f\xe7\xb2M)$\xd4\xc3\xc87-\x89YW\xbdr4\x90\x1c\xba\x88\xd9\x0e]g\x1f&Eb\x13\x1f\xa0\xc86\x14\x8b\xab\xb6\xbb\xa3\xe2@\x81\x9c*M =\x951\xa0\x7f\x0e\x84\x1ex\x00\xf5\x8c\xeb#\x18c$g\x186\x88\xac\xa3_\xc4\xc3\xbf\xac\xa4'A(24\x121\x19\xf3\xf1\xfb&\x11\x1b\n\xcb\xc0\xc0\x0b\xacT\x16f\xeb N\xac\x04\xa9\x1f\x94\xc9\x95'{Q\x0e\x10\x9ecb*]Z\xcc\xe0\x93X\xa1>t\xf7\x87\x1e\x02\xc1D6\xde\xf2C+\x82h\x95)\xf5~AIj\"%)\xbf\xeey\xa5\x9f\x1aH\x89\xd6\xef\xb8\x111\x8a\xf4\x80\x97`-\xc5s\x81\xe3\x1ep\xcc\xc5\x13\x08K\x9b\xef\x8f\x03\xc1\xae\x81t>V\xc1sA\x0d\xa9\x96O[GL\xbb\xf4\\Nud\x872\xae\xbeLj\xa56\xcfu\xbc\x0bf\x1f\xfar\x98\xee\x82\x1e1#\xe0\x16\x1a9>\xf2E\x05\xcc\x982\xab.\x9e\xe2\x90\xfeg4\xda\x1a`\xbf6\xc5\xd5P\xafu\x0c\xa2\x9c\xcb\xa2\xab^3\x9a\x0d\xfc\xa4`\x87`\"L0\x1f\x94\xb0\x0c\x84\x91\xeb'\xa9\x05@\x03\xd4\xaa\xc9\x16\x8a\x11\x1e\x818\xf0\\Gd'\xc6I\xdb\xdb\x7f&\xabM\xbc*E\x8e\x8e\x9c\xa0\xe0\xec\x04\xb9y\xcbsM\xa6KI\xfc\x9c7 \x88\x96\x8b\x8b\xe0\xd9tm\xe0\x01\x12\x18\xba\xe5<=\x8e\x93\x1e\x80G\x96M[\xa2\xf1\xd6\xfa\x9e\xeb\xdf\xd2\x93@\x0f\x9d\x81t\xf0i\xa4\xa6\n\xb1\x8b\x02{\x07Q\xb5dS\x8f\xa9\xda\x8d\xc8\x10\x15sW\xd8\x1b=\x94\x83g\xd3\xf9\xa7\x0c\xc7\xb1\x1c\xa3{\x8e\xad\x10\xef\x08\xc2\xb8C\xc4\xac\x05\xdc\xd1\xbe\x86~\x81\x82\xc3\x10\xd2S\xd0\xb9\x0f&\x16Y\x99\xb2bA\xce\xa0H\xe1L\xf1\xa2e\x05\xdc\xc7\x8bU\xd5\xe9M\xd6QT\xc6btl\xde\xc5\xbb%\xb6\xdc\xf5\x02\xdf;&\x8b\xb2\xe7\x07\xc9#\xe4\xc1\xbe\x0e\xd1n\xb6I\xc6\x88\x1a\xb8\xa3}\xae*\xaf\xd7\xd3\xbed\xbbrQ/|S\xd7\xc7\xf2\x95B\x10t\x97le~\xd7J\x86\x7fe\x1d\xfd\xaf=\xd2J\x10l]\"\xd7\xf8\xdd,;f\xcb\xea\x90\xfc\x1c\x8f\xb7\x07Gar\xbc\x99\xb1H\xee\xf4\xfc$\x15\xdf\xd0q\xf11\xe1r5\x89\xc6\xb0\xfaTj\x16/\xac\xc0\xd4dq\xc3\xc4;\x83\xb0\xdd\x11\xc4\x12+\xbe\xa5{n\x9c\xe8n\x02G\x05V\x16\xfc\x1c\x9f\xc6ut\xba\xe4 \x88\xcd\xd5\x00Uj\x95\xa2\xe6.9\xbf\n(X\xfd8\xf0\xc6 ,\xf0d\x08\xc6G\x1d\x1fGE\x11M\xce\xa2p\x04t\xac\x8anf:E6\x07V2,6\x9f$\x81MOgl\xb14j-\xb2ZT\x13\xa5\xd0\x9f$\xa3\x81\xc1O@`\xeb\x94\x8a\x05\x13/[W\x15-S\xdb\x1f-\x9f\xc1S\xac%%\x8c\xbc\x96\xb8m\xaf\xddB\x1b\x9e\xa1\x01c\xbb\xbd\x99kj\xb1\xb3a^\xad\xe2j\xfa>\x8cR3G\xae\xfa\xd8w`\x84\xf83\xd7\xa6x\xa9*\xa4\x1dk94\x80\x0c$?wy3\x13\x07\xc0\x9c\x0d\xc0\x14xB\x1f6fWoJ\xd5\x9b\xb3\xab\xb7\xa5\xea\xad\xd9\xd5\xbbR\xf5\xf6\xec\xeaF\x9d\xd4\x9f\xed^hv\xe1\x88x\x17\xca\x0e\xb2Qk\x11\xc0\\\x939\x03\xcd7\x983\xd4\xb6\xa2\x8f9\xc3\xa5\xb3s\xbbb\xc9\xd5\xb8\xe5)\x98\xe8SK\xa3Px(\x1b\xc5\x8a\xcc\xcc\xd2vn\x91--\x82\x070\x8a!{\xb6\xa90\xf6\x11\x12ZIR\xb8\\\x07\xben\x07\xe3t\x95c\x18d\xd9\x83j\xad\x8aj\xd9\x81\x035\xb4[k \xb6F\xa1\x06\x92\x84\x8d\x94y\xa6\x89\x0b3u\x87\xe3\x05}\xab\xefHv\xb7z\xcd\xe0t\xd9\x1e\xf9\x0e\xea\xb5vN\x17\xc1\x13\xccI\x0b\xa4\x1e7\xb65\xd046\x15\xdaB\xab\xa5\x01\xf6\x8b\x13\xc0\x99\xae\x8a\xf5\xecNC\x03Fg[\x03\x86\xca\xd4\xda\x90\xb70l\xbc5L$\xa54`45P\xaf\x99\x9bx\x18\x06\xd9\x985E\xe7l\xd3\xc6\xff\xcd\xf0\x08\xb8~\x0c\x93\x82\xe3[\xb1\x1b\x08\xd1\xc5\x0eF#\"\xb6\xf9\xc3B\x1b!dv4\xd0\xd8\xdcQ\x9d\xacg\x11\x04\xd1Qis\x93\x11\xa1\xb1\x18;\x8028Q\x99-\xdf\x1eb\xa3\x9d\xac\xc4 \xda7\x9d\xd6\xd4\xfc\x9b\x8b\xeaH\xcd\x9edA\xe4\xbcs\xa2\x92\xaf\x02\xa0\x94\xddB\x8d\xfc\xf6.I,W\xbd \xf7\x03\x08\xbe\xf7(\xb8y<\xea\x07^U\x93\xb90\xe7\x08\xd8\xa5\xe3\xc9\x85T\xdc\xae<\xe9Y\xfe\xfe\xe5*\x0d\xa3\xac>\x85jj\x80\x96\"\xea\x93\"Uc\xd1\xd4\xaa\x00U\xf3\xd1\xd6\x89XQ}\x8e\xa3n\xb6\x08\xe0\x83^\x1a\x0b!\x98\x11\x0f\\\x07\x06\xaa\xde\x8b\x0c\x0eY\x90\x97\x10\xe5v\xa4\x94o\xe4K\xee\xe8\x99\x1a\x81A\xe2h\x80\xff\x1aqF=\x8aa|\xb0\xff\xa4\xeb\xfc\xcft\xe0\xd7\x86V\x94\xe4\xa3D\xa89~dE\xb7\x10\x8cBe\xac.\xfa\xb9\xeb\x1a\xa8\x93BBX\xbc!\x11y\x82Y$\xf4,\xd7'\xb2\x95\x7f\x14'Q\xe0\xefk\x00w' ^ZJ\xaa(\x95\xdf\xb4U>B \xc3\x87w+J|]\x91\xfc\x952\xa7pOk\xf6H\x8f\xd3s\x80\xd2\xf3%zTb;\n<\x8fs\x7f\x13\xb0\xe9\xc6\xf4\x0c\x16\x8dG= \xfb:\xa9\xc7O\x1f\xb8\x9e\x87\x8f\xe59\xa0}\x8b=\x15P\xd1\xf9\x00 Ii.\xe7\x0d\x16Uj\xd344\xc0~m\x16\xb8\xfb\x94Rv\x86\xd7Xp\xe16\xa8Z\xd5\x927\x07\x8c\x8f\xc0_F\x0b\xe9\xe9\xf4\x17\x19\x10G\x11\xf4\xf9\x16/\xcdA\x17I\xb1m\x0d\x98FFb\x07\x0e\\\x1f\xca;G\xd7@U;\x8c\x17\x1c\xf7\x80W\x16\xe2\xd0\xf2\xf9Mnd%\xf6\xd0\xf5\xf7\x95\xcb\xa9\xce\xe9Ls!\xf9\x81?\x13\x18\x86\x84h\xd9P,\x1a\x0e\x0e\xf1\xf3\x90\xc3g\xbas\x97\xd2]\n| \x99\"\x939\xe5\x94\x1aO\xaaJ\x92(7\xc1\x8d\xa4\x02\\\x18t0\x1f\xd54\x8c\\\x1d\x0dPR\xad\x1a\xa6z\xd5luK|z\x90>.p9\xa5*^{Ga\x9c-\x82\xc4G\xe6eN\xaby\x93?\xe4A\x08g\x13\xce\x7f1\x0f\x88B\xbd\x14\xe2\x04\xc5#O\xe1\xe4\xa2#E\xd6\xe9\xdcVq\xc1\xf4q0\x8a\x83\xbc\xc8\xbd\x8ay\xcatQ\xa8\x9d\xc8\xa9\x85|\xa8\xeeT:R\xeb\xc4\xef_\xe0zRN\x17\x8e\x02\x83s\xd9\xa79\xdf\x18\xc9\x1f\x0d\xe5\xc3{\xbe_A\x1e\xccv\x96\n\xd4\xe31R\x02\x86^\x91\xfb\x95\xb9\xde\x1c8\xb0\xc6^2\x0b\x96G\xa38Y\x13\xb4K\xc8\x12*\x8c\xb8\xe8[f\xe4\xe7\x0c0\x82\x91F\x19\xd6%\xd8*$g\x8e\xe8\xe0\x11\xec63B\xf3D\xf3\x13\x95\xd8)\x94t\xbc\xa6Z\x08*\".\xc5iA\xe0\xe9\xb8g\\\xf2*\x08\xc1P]\x15\x13\xc1c\xf7\x818\xad\xd4_]W\x07\x05\xd1\x19\x1e\xb8 \xf3$+1.\xd5\x07\x11\x8a\x82E5\xcf\x13s\xb9\xd6S_Q\x9c\xc7\xccuUgi\x0c\xa4\x8aV<-\xe5 \xee9S\x903\x0b\x88A.\x82J\x01\xad\x98D\xd0\xab\x06\xab4q\x15Hy\xccQ\xae\x87\xad \xb3\xfb\n\xa5\xfeX\xec\xa2\xb8\xfaD\x0dF'\x0b\xb5\x00OU\xdf3\x88\x9f\xdf%\n{\x9b?*\x1c\xc9\x02\x9dR\x10x\xfd\x02iT\xd0\x8a\xb0\x0e\x967\x82Y\xbc\x15L\x03\xf5\x1a\xd1\xc3r\x11v\xcb\x8f\xe1v\xa5R\xd9\xba\x08nx\xc1!\xd2o\x13\xc0\xce\xe41\xb8\xb8\x85\x9e\xdc\x84\xdf\x1fC\xdf\x86`\x8f\xd8Os\x15\xbec\xf9I\x02\xd4\x8d\x11M!\x91O\xb8\xa8\xe6Y}\xa4\xd9W\x00\xa8\x00&\xb7.\xe0\x0b\xd7\xb7+\xec)\xb1\x8eV\x00@[<\xff\x18\xdbN#h'\x1a\xfdl\xbb\x91\xedA\xf6\x0dz\x9e\x1b\xc6\xe9\xd70\xf0\x8e\xf7\x03\x9f\x87tuo\xafu\xfd\xcaN\x05`K\xdf-\xd8\x03\x17\xb6\x1b\x9d\xfa\xde\xd5\xacH\xcf\\z\\\xa7\x1c\xde*\x97\x14\xabW\xb3=\xd7\xbeEL'h|\xf9\x18\x90J\xcd\x8a\xa2\xe0\x10\x9d\x85\x1e\xc7\xc1<)r\xd8\x9a\xba\x1f\xc1cR\x0d:\xfb\x10\xd7\xa8\x85\xac\x1e\xc39\xab\x99\xc7\xba\xd6bx#\x00\x8f\xa5X\xe7Mw\x17`\x17\xfd\xec\xcc\x18\x92\xed\x8dc\xa4\xd1\xa0)\xe7\xa7\xb1\xbd\xd7\xdek\xb7\xf8i\x94X\x95\xf0k\xcdlm\xce\x98X\x06]&\xf7\x8d\xed\x1b7n\\\xc7u\x10+S\x13\xf2\x13A\xe0%n\x88+*\xd6\xbez\x14\x15\xc0\x9b\xb9\xcd:Z\xf1\xa8=w\xe3j\x87\xb0\"o\x8a\xa7\xa5\xfcR\xe4\x86\xac\x8c (\x1e\xbc\xa47P\xd8\x94#tx\x00\xfd$\xa6r\xa9\x022io\x90\xa3p\xa5f\xd9\xd8\x04\xc5\xb3l\xd7\xb8j\x10\x96\x15\xd9\x1a\xd5G\x93\xc0\xb5!\x15\xfa\x9ee\xdf\xe2\xc9\xc5\x0e\x06\x14<\x8d\xecT\xb2\x18\xae5\x82ql\xed\xc3\xc7\\\x1f\xd6\xb9z\x1c\xd7qtv\xacxhE\x11\xd2K7L`n\xec\x94\x82k\x9c \xdc\x0b\xe9\x1a+^_1\x15k\x7fM\xcd\xa9i=\xac\xd5\x100\xac\x8e/\xd7\x91\xba\xb3\xa3 \x8eqwa\xc1\x9a\xe6\xf5d\x15\xda\xb2\xb3\x80\xcd\xcc\x13\xe2\xdaP\xac|\x9e\x98XH]\x0d\x8e\xca\xb3\x0bi\xf2\x84,p\xcd\xc6^\xb3S\xd0G\x10\x84\x0bb\x15\x04\xe1c\"\x83\xa5+\xb2$y\xd9\x18\xa8\xa0MDf\x9d\xb9\xfe\xe8\x90\x06\x83A\xcbr2\x00\xd2\x10\xd4+$/\x1a\x9aLxa\x8b\x07\xb6$\xd5\xf9\x99\x1b4\xd1\x8f\x80z\xbb\xdd\x96\x9b\x18\x8b71K6\xd9\xba\x88vl\x92R\"N\x8e=\xa4U\xa0}7fi&\x14c\xe4\xbcV&[\x1c\xb6<4\xf5$\x13\xb3c\xda@\xc0\xf2\xfa\x95\xeb\xdd\xab\xdb\xc2sCK?6rKn\x16&O\xb8\x89\x07\xeb\x05\xbb\x84P\xc9(S\xc9,S\xa9Q\xa6\x12\xaeCv\x1e\x1c\xd4\x84\xd5\xd8\x08/r\x9eo\x0c\"\xf6q\xc5\xd4\x10\xd0$\x85\x84l\xe0;\x91\xeb\xe08G\xeb\xc8%\xba\xd2>*\xa9%\xae}\xab@6\x8b3\xd6\xc0\x0c1\xb4B\xa8G\xd0w`D\x8c\x0b\x91\x1b\x87\xd7\x9d}\x18\x13\xe41PI\xa5HO\xb0\x94\x85\xc0\x13\x81c\x1dcc+F$\xc1_\xb3\xd9`\x8c\x93r\xdf\xde\xd5V\xa7\xc5 \x8ct}\xd3\xe5\x82@Z\xf1-\x9e#\xb7.\x82=r>\x07 z\x84\xfbA\x1fTR\x02c\x8e\x9e\xa6\xcbV\x98r\x1a\xeeF\xe72\xad\x88C\x1b\x9fL\xc9\xf0\x14 \x18\x95\xc9\"4\xf9\x9bq\x12\xbb\x0e\xfc[\xec\xd9VJ\xc4\xd2\xb4V\xc1}\x0c\x0e\xca\x80\x85\xbe\xa3\x02Jg\xf2f\x08m\xd7\xf2\x80m\xc5\x10d\n'\x9b\xc02:(CkN\xdd\x0c\xd1z\xbda\xb4\x1b\xd2.&\xd8\xee\xfb\x81\xe7(\xe7\x12\x8d\xf9~\xf4\x83iv\x1a\x1d\xb1)\x1f\xb86a\xd5\x18&\x89\xeb\xef\xc7`\x10D \x19B@\x05B\xccH\x80\xb0\xaak\xd9g\x83\xfblr\x9f\x1b\n^H\x87\xc6\x00\xb0\xc6\xac\xa1\xd0(\x7f\xa0Q\xcb\xe9\xd6\xa6r\xcex$i\x91\xa9\xdc\xd8U\x8d\x8d|\xe3Fa\xe3\xad\x8b\xe0\nv\x12d\xab\x9d8\x0d\x10\n\xe4\x93\x91~2\xd3O\xc2`3E\xa6\xdc` \x08F\x8c\xec\x9b!|3\x85o\n\x92\xc8\xba\xd9\xd6Ep-\x18\x85\x1eL\xa0\x93\x8d\xc6 |<\x16\xf4\xd7\xa0\x7fM\xfa\xb7!\xc8o&\xba\x0b\x95'I\xee!\x08l\x10\xec\xb3\xc1}6\xb9\xcf\xe5\xd0G\xb28\x06\x81\x8f\x99\xd7\x8e\\\x1c\xd0\x95\x89{T\x82:C\x7f\x0d\xfa\xd7\xa4\x7f\xc5\xa1\\\xb8\xdemt\x1a\x1dn]e\x05\xca\xb1\x90\x89\xbeF;\xc8\xbe\x19\xc27S\xf86\xafK\x99-\x14\xd3\xc7:d\x9f\x0d\xee\xb3\xc9}\x9e\xd3\xd5LZ\xa9\xc5\xcd\xdc\xfdx\xe4z0N\x02\xaa\x1es\xe1\x89Q\x90X |\xa4\xd9r\xe0\xfe&\xc0\xd9\x01\x1f\xa9\xd7p\xde\x8f\xee\xa6\xd46\xd3f\x95n\xd0l\x80<\x1f\xb1\xef\x86\xf4\xdd\x94\xbe\x97\xe0)\x8eZ\xe2r\xe3\xfb\x10K\xcc\\I\x99~p\xe6\xc3yJ\x00\xbfov\xc9f\x9c\xdbo\x11\xb4\xfd\x9a\xedYq\xfc\x9d(\x18\x879+\x84l\x17*<\x12P+\"\x00yp\x04]\x0cU\xb1\xb9\xd0\xcdXh&[Z\x8a\x0dV\xb9\xa6\xb93|\xb1i\x8b\xcc%nK,D\xb5\xbeth\x145\xbcL?Ta\xc6\xa9\xa0\xad<\xe4\xccp\x96\x9bX\xc5,Vj\xc4\xf2-\x9dTf\x0c\xa5\"(\xa5\x00\x1f\xc7\x83\x11\xb3\x10\xddD\n\xdal\xb2\xce\x99&\x1e\xdau\xdfY\x0d\x96\xb5\xbf\x1f\xe1\xc8\n\x05f\x8b\xd8&s\xd0$\xcc\x16\x87\xe5\xc0\x10\xc9(\xdf>>\x81)\xcb\x80\xad/\xad0\x824\xff$\x8bF\xc5\xb3\xc7\\2\xfdn\x7f`\xb7\xf1\x9a\x90[\xa4!\xc7B\x83k\xdd\xee\x8d\xbd\xba\xb2\x01\x8e\x9a\x14*w\xf6n4\xdbW\x94\x95\xb9\x90I\xb1I\xab\xd3\xba\xde$M\xe6\xbb\x18qS\xca\xa2\xf46o\xea$I\xc7\x86\xeap14*lX|\xaf\x80\xca\x9e\xd9\xbe\xda\xbe\xaa\xac\xcfbn+\xb5\x82\x98[\x11\xd0\x95m\xb3\xa9\x9e\x06\x1a\xa2\x98+w\xe0 \x07\\Q\xb7\xc6B\xad\x85\xfe\xbaN\xd7\x19\xa8iDbEe\x104VT\x00\xd2\xeaXv\xab\xa3F:\x92\xea:]\xa7\xe5\xb4\x94uI0\xa4X\xbb\xe14\xe0@Y;\x8b\x02\x15[l\xb7\xea-\xf5\x80\xb2hP\x11\xfb:l\x17\xb4\xc8b\x9c\x85\x16\x83\x82\xea#\x98Xy\xfap!\xcc\xe2z\xea\xf4\x1b}5\x8f\xa5\xf1\xcbb\xb7\x8d~c\xd0U7`\x01\x92B\x83\xedm5Qp\xb0\xbfLX\x12\xec/2G\xf3j\xfb\x9a\x9a\xab9\xafSV\xbf\xdd\xbcr\xb5{CY?\x8b\xda/+S\xf8\xa8}qT7\xae^\xd9Ss\x10\x1f\xb2/\xb41l\xbb\xddm\xc9\xa2\x82f\xed\x83\x0e\x12\xd3\x9a\xf2 \x93\x1b\xbc\xbb\xb5iu\xb7\x1d{G\xa4\xcd` )\x15@\xbc\xf3J\xb6\x14\xa9\xff4\xee\x8f\x17N|4H\xe5\xf6\x0c\xf1?\xb2\xa2[Np\xe8\x03\x92\xb5\x1e\\\xc4\xf6\x114#4\x8b\x0e\x8d1\xe3\xa5\x07(\xae\xc1\xc4~E\x9c:\x1b\xf6m\xdb\x92QWBQJ^\x85\xe0m\xe4\x05oJL\x05bjI\xb9}\xe3\xaaq\xe3\xc6\xec\x96JY}\xa5s\xa5\xb3\xb7=\xb3\x1d\x7f\x1bF\xf1X\xc9\xfc\xddf\xb7\xbd\xdd\xe2\x15\x0e\xc1\xd21\xb3?\x85\xe4\xdbn]\xddn^\x9d\xd1\x8c\x9b\xe8\x82\x08\xe7\x1c\xe7b\x8d\xabeh\x80\xfd\xaf\xd7:\xe6f\xa9\xc9\x9f1\x1b\xe4\xbe}\xe5O\xd2\x89\xb0,\x03\x1a;\x95\xdb\x7fA\xd6\x03\xa8Vw*E\x9f+\xf4\xe5\x07\x18W\xee\x05\x08\x00\\h\xb4\x1bW\x9b\xf5\x1d\xfa$v\x1d4\xba\x88\xabr\xc1\xbc\xde\xa87\x1a\xac\x06^n\xb2`a\xad\x95\xafK`\x0b\x99Vr\x138\xd2q\x9e\x15\xbe\x93\xbaUw\x8c6\xeb\xc4\x0e\xfc$\n<\xa9\xb3\x0e\xfaQV!\xe0XEz\x90A\xb5\xc85R=\x1f\xb0A\xb4iZ\x8bPV\x1f\xb8\x1e\xe4pb\x97\x18X\xf0\xbb\xaa\xb2\x90^ \xdd\xe6i\xcd0rGVt\x9c>\xb6\x1aNk\x00\x95\xbd\xf2\x03M]\xa1\xc5\xb3\xd5\xa9w\x8c\x8e\xa3\xa8S\x08G\xc6\xa5\xed\xd8\x06\xec\xa4\xb8\xb04\xcb:t\xdc\x84\\\x8f\xd5\xfb\x0e\x0d\xbc\xca\xc8kE\xf6\x90Q\x99C\xa7iv\xfbTx\x91\x0co\xdc\xf2$\xdaw\x1bi\xdf\xf4\xb1F\xdf\x11A\xa0\xb2$\x83X\x8a\xbb\xcf\xc04\x8d\x1a\x0e5$uF\xf1\xcc\xe7\xc2\xb6\xc1\xf3s\xfe\xdd \xd9\xfb\x12\x08\x8a\xf8\xbc`\x8f\xa3\x08\xfa\xc95\xf4\x80\x94\x8bWTkm\x13'lG\x03`\x99\xd9p5.\xb0h\x9b\xb8r\x85!j,\x03;I\xa3\xaeUh\x06v\x1c\xa1cE\xd0\xd2pt\x13Z\xd9zBb\x9b\xb2\xfcJ\x82\x98\xe2\x15\xe7\xc2\xbcu\xb8w\x19\xa0F\xae\xc7\x12\xe6pC|}\x15\x87\xc6\xb0\x04-\x8a\x19lPu)\x0cB\xd4\xaeF\x8e\x9a`\x17\xd4\x14-\xf9\xd3h\xca\x10\x0c\xc4\x02s\xc13\xcd\x92\xaf(\xc1=2\xc4\x18\xf5h\xb4U\x16\xb4Ey\x83\x85Y\x9b\xcd\xf0\x08\xd4es\xc4\xd0\xd0*CS\xab\x0c\x1bZe\xd8\xd4*\xc3\x96\x86\xd3\x96+p\xa4ov\xf9Nd\xf9\x0eB\xb1z-\x88\xfaP\xba\x7f\x9e\xb5;\x143\x18\x102{\xd0\x8a\xd0q4\x19f\x1c\x8fc\xa8s\x11\xd7\xf8\xf1\x8cGi\xf48\xf9\x9a\xdd\x7f\x15\x15\xa9\xbd\xeb\xe8\x87\x8c4\xbf\\M\xf2\x82\x02\\\xbcu\x114\xda\xe1\x11R\x87r+\xc3\xacu\x84\x8a\xcdzVQ\xba\xae`\x88 \xf1\xac3\x90R\xfa\x05\x9dD0b\xe4L\x85,\xa9\xb5\x1b\x85\xa0\xc4u\xdb\x15\xd1k\x9c\x10z\x8c\xa3\x14\xb6\x04\x84sC\x85\xb3\xd1\xe1\xe1\x1b\xddB\x9c\x0b\xf18E\x8c\x9bJ\x8cM\x01\xe3\"\x1e0j\x0dq\x96Ms\x19\x9c\xc5=\x0b!\xd5\xca#U\xafmwJ\"e\x9e\x00N\x05\xb3\xd5V\"\xd6(\x87XQ-9a\x06\xbf\xb2rsCc\xbf\x1f\xe1\xd2\x83lw\xd3\x97\xad\x000l\xe0\\\\\x81=\x8e\xe9\x0b64R\xde,(o\x15\x94\xb7\xe5r\xda\x01\xe0M\x01\x0e\x15=\x00\xa8\"cU\xcf\x85\xf1*#K\xe7V\xdf\xd1*I\xa2U\x0e,\xd5\x99 \x9b|h\"\x11\x0d\xd4k\xf5\x16;`\xf2:\x02\x8f\xacp\x86\xf8n\xe0[v\xa0\x81\xf4\xa5\x90\xa0z\xc5w,\x0f\xa2'\x01:S\xec\xc1\xa7\xad\xbf\x1b\x83\x9b\x96\x1f\xb3\xb2\xecu\x91\x08W\x9c\x1fS\xe4&\xf6.!\x19\x91mvn\xce\xf1\xaaB\xbe5\x98\xe5\xa7h\xe7\xe6\xb3\\\xe2\xfc\x94\xa0\x8e\x15NPg\xb3\x82~\x88\xc3\x91\xcci\x8d{\x87\x88\x88\xb1\x81\x13b\xa6\xbfr\xe4a\x02\xc5L\x1f\xa8\x0f\xae\xc5\xb6\xdf\\u\x90\xb3\x08\xab\x93\xd2c\xb8\xd0\xc7fo\x9aE,\x06\xc2`h\x9e/va\xa4\xfa\x14\x90\x0f\xe2e\x9a\xd3\xdcb\xcb4\xe52\x8c\xf1\xad\xd9\xd0\xd9\xbdq\x13\xa7\x9d\xc6e\xdc\xbb]\xd0\xf8\nY]`)\xb6:\x12\xce\xd1 '\x12\xc3\x85\xb9\x82\xccr!\xbf\xbd\x88\x7f\x9c\xbd\xc1\x88t3\xd4*I\x8e\xb7[\x94GHK\xd5\xdb5x\xdd!52q)\xef,\xcf\x035\x93\xa4\xbc\xd3]_\x0f\xc6\xcc\x9f0\xaf\x12oe\x10\xf7\x01N\xd0l]L/BR\xd1\x9d\x1e\xb4yYcR\xb3\x12\xc345a\xd6\xd1\x0f\xb7\x83\xa8\xd3\x96\xe4\xd5\x1daM#\x9e\x95\xb2K\xc6v\xe4\x86\xb2y\xa9k\xf4\x0d\xa7?S\xe0\x8b\x86\x83\xa21\x924n\xf9\x99\xd9k\xec5\xeb\xd7\xd5R\x9fA\xde\xde\xbbb^is\xac%\xcf\xc9\xec\xae\x05>a\xc7\x10\xb2\x8e\x0dy\x17\xc8\xb7\xce\x12\xc3+\xf7\xa2z:\x97r\xbaxE\xe5\xf4\xf0\xa9n\x92\xe6\x8d'\xc4E\\\x80\xafr\xcf\xae.\xaa\xd0YR\xe5*\x19\x91:\x0d\x04\xbf\x0c\xc5\xed\x87{\x95\x02WZ\x90C8\xe3\xc2\xdc\xd5J\xe5\x89O\xb0V*\xb6\x14\xceB\xcb\xaf\x05\xbdN\xde\x1d8g\"\xf0\x9b\xe2\xa0\x93\x1eT\x8b\xab>I\xab>U0y\x1b\xff\xc34\x8d+\x1b\x8c'\xfe\xdf\x9bo\xf1\x07\x7f\xb6k\xb79S\x82pT\x13\x1f\xc9\\J/b}\x7f\xec\xda\xb7@\x10B\x9f8\x0e\x11\xa9\xf5x\xbc\xbf\x0f\xe3\x84\xbd\x9cK2\xa6r' \xd5\x81\xc3l]k\x13\x1f\xeb\x05\xea&\xc2}\xe8\xb8\x0fy\xed\x95l\x86\xa7kn[\x90\xb7\xa2\xe6\x83\x91(\x13\x90\xa86\xbc\xb2\xe4\xde\xf8\x95\x93\xef\x8d]\x8c\xe7'5-+\x86G\x03\x89\xb5\xa2\xe7\x82\x90T\x8c\xb1\xb9\xd7\xbdJ\"\xb5\x14\xf6\x96\xe5`b\xf5\xa7\xd3I3@m\xd3\xb8m\x1e\"s\xa9\x84\xc4\xbb\xbeK\xdeq\xa4\xbe\xac\xcdD:\xf7\x827\xe9\xc5)E$q\xacx\x08\x1dp\xc1\xb6\xed\xbc\xda$h9\xbc4\xcblQm%\xff\x1b\xa4\x1c\x8d\xa6\x9f\xa03g?\xf1\x01\xfa\x95\xfa-\xf3;A\xae3\xd9\xc3A\xe6\x86\xbcj\x10;\xa3\xc4Q\x91\xd7zq\\f\x1d\xcf\x953\x9c+C\xe7\xa3\xb1\x0b\xf1b\xdb\xe6\xc8\xda\x87\xbbY\xbeO\x01\xdf\xab\xf8\xdf\x9c\xd9\xa4\x80\xe0Qh\xf9\xce\\\x90{{{\x1c}w\x87\x8dT\xc4\xb1\x92f\xae\xa4\x95+i\x8b\xc2\x8e\xcf\xd4#,Y>\x8b\x8d\x80F\xb7\xdb\x9d\xafw\x00\xde&\xd7L\x85 }!\x90\xc9\xc4x:\x94\x02S\x10\xd1\x81\xa8B\x99\x8e\xb2L\xe5\x9a\xf0n\x10a\x00W\xba\xd7\xcc\xbdk\xfcR\xc19\x04H}I)\x9e\xcd5N\x14\x84Np\xe8\xeb#\xe8\x8f1k\x93%\xaf)\x1fa\x94\xb5\xf45,Bu\xbe\x8c7@\x89\xa2Ff&\x1a\xc8\x88ev`\xeb\xa8O\xdc]\x9e\x7fI\xccA]\x03\xad\xa6\x06ZX\xd2l76\x8bvh\x1a\x13\xda\xd0@\xf6\xab^3\xe8\x11M\xec\xab\xe6\xb8\x07\xae3Ktn\xf7\xd1\x0f\x991jP\xa3,\x0f\xa3Y\xe4f\"\xd0u`?;U\xcb\xef\xfd\x11x6;\xa1\x11\x91\xa6z\x9a\x85\x00Q\xc8H\x98\xc4J\xe1XW!B\xa5z:\x14,2\xb2\xdcY<\x98,\xc0\x91\xa1\xc3\xcf\x81\xd8\x10KG\x81\xe8\xc2\xae\xc6\x1a\xe5\xf7\x151\xc4@\xed\xdeV>\xcemcC#]Y\xc4\x0f\x9c$\x91V\x19\x9a\xca\xd2\x86\xb2\xb4\xa9,m)K\xdb\xaaR\xf4\x89\xbe\xdd6\x0e-?}\"\xad\xe1\xee\xb5\xee\xf5m\x93\x8cC\xce\x17\xcf\x96\xbf:`\xa9em\xb7 =\xd8\xa7A\xc3F&\xd4\xf9\xb3\xd0\x81|\xba\xe1v\xa1\x0b\xf8\xcd\x04\xb4z\x18\xc1\x03\x17\x1e\x96\xf0\x9egK\xb66\n\x1c\xcb\x93\x1c\xfas#\x128\xfd*\x83\x91\xbbC\xc4|t\xadT]\x16\xfa\xe2\x0f\x18\x05z N_k\x90\xff\xf5Z\xcb\xd8,R}D\xc85\x0c\x9a\xbc\x98X\xb7\x1c'\xf0\x95\x82,k\x88\xbaG\xb2d66F\x07g#\x02b\x86\xb5#\x9bwq\xc5\x8b\xcb\xa4\xae \x90P\x91yp\x95\x8b(\xe3\x87\x8c\x0dL_O9!\x8f^\xc1[q\x97b8\xb3\x88\xa9\x0b\x17&\xb7\xd8\xf3\x93\x9a\x06\xf9\xce>}\x07\xe3\x18\xeaT\xa3Gg?t\x1ep\xed\xc0\xafq\xa7\xb3\x0b\x87\xfa`\xecy|e\\\x18\xba\xbe\x00\xa0h\x96T\xe6)?p`\xaf\xc7\x1c\x0f\\\xb6d\x0c\x83ni\xcc\x952\xa7\x91\x9e\x0c\xc7\xa3~\x8fYg\x15\x02\x90\x1e\xe8p\xe2o\xf6\x0bgfZ\x1c:]\x1a\xa5;a\x87\xc6C}\xec\x87\xee\x0c\x0bd\xd3\xe8\x9a6\xb9Gv! B\xa2\xd4\xe2\x1c\xd8\xc2WpQd\x13\xb2\xcf)C\x9c%S\x1a\xbe\x9e\x81\xd4t=\xf0\x11L\xb4d\xf9Rl\xa4\xca\xabE\xe9\x19=9\xd6\xe3ap\xc8=\x02\xf9\xda\x8ayA\xaa\"\xd3\x16\xd5\xdd*=4J\x06\xe6a!\xe5\x8f\x1c\x9fF\x81C\xd2r\x04>9P\xe9\xb8\x04\xdb\x96|\x87{Ooz\xe2\xda\x14\xbff\x96\xe6My~/\xb4\xbb\xe8'\xb7\xbc\xcbw\x85_\x7f\xacj Z\xe5\x0b1\xda\xe5\x8f+\xe9\x91\xad\x1c^\x9e\xfb\xa4\xed:O\x15\xc2V\x0d\xb5h|\xc4\x99\xca\x1dZ\xa5T\xc2\xbb\x17I7\xca+!\x9b\xe0b~\x0e\xe4\x8b<\xd8\xc8\x9b\x03\x82[@g\x05\xca0\xcbS\xc1\x93\x9c\x9f\x81u\x90: \xc5\x86\x17\x84\x15\xb5\xf0<\x94\x1aEe\xeb\"sf_\xdcJ\xdfb-\x18\x0c\xc4\x97u\x15n\x04\xe9\xee*\xbcT5\x8by\xa5\xb2\x96\xbcZ\xb9^\xff3\xf0\xe7\xc0\x0c\x8f6\xe9\x12\x9b\x81D\xc6\x13\xfa\xa1\x9b\x0c\xc9\x8b\x85\x82B\xd4\x84\xee\x05\x8f\x9eP\x9b\xbdcB$\x84\xb1]\xefR\xed\x8b^\x0bI\xac>\xde\xbacp\x01\x9f9HcV\xaa\x95\xac\x97\n7\xae655\xe5\xea\xd3\xf2\x85*\x17K\xb6\xdb\x9c\x01\x8eJ\xd1\xcc\xb6\xa5\x91w\xd0\ne\xf3\xf4\xfa\xbc\x9e\x8a\x04\xb6\xeb a\x1b%H\xcc\xf2> \xf1I\xda+\xbbp\xa8\x0fp\x12\x01\xa2n\x90\x97D\x0b&\x9d\xfc\x11>\x1d\x0b\x9ak\x9d7\xc3\x12\x96L]\xcdL\x0d\xcc\xec\x98M\x12\x88\x83\xc3Vr\xa1B\x9d\xed\xcd\"\xe5Py\xc4\xc3\xea\x9c\xec\x80\x16\xd5\xc4\xec\xbc!V\xd6\xe3\xf1\x883\x9d\xc8\xd61\xb12\x9d\xcc\xde\xc0\x8d\xe2\x84\xbd\x94\x9e\xef\x8c;i\xa7\xad\x91\x8a\x91\xbf\x1c\x97\xbe\x8a<5\xde\xd3\xae\xfa\x91\x15\x1d\xe3F\xb4\xb7]\x0eLv&V\"V\xa8\x80\x0b\x93\xa7\xbec\xb5Y\x1e\x8bl0E\x8a\x8d\xf2\x9aX\x111U7\xcd063\x07yZ\x9dV.$\xc7s=\"\\\xce\x87\xdc\x02\xcfm\xf2\xc2^. \xe4ZC\x0c\xe6\xa3\x87\x95Z\x16\xca\xcdtS+\x0c\xa1\x15Y\xbe\x0d\x85\xc33y[\xec\xd3\xd6\x91>r\x1d\x8f\xe5\xe8P\x9b]:m\xa3\xddW\x9a\x1aR\x80D\x02!Y\xa1:I\xa5\xdbW\x0b\xfd\xa4\x02'\x8c\xe0\x00F$zMq\x1e\x9cq\x18\x99\xd5\xbaP\x02\xa6\x03QtOdY\xf1\xc6\xb8\xdd\xddnoS_^zc\xc7\xc8\x8e\x1c\"6\xbeu@M\x99\xb3\xf8O\xa1\x8fK\xf2I\x82\xcb\x19\x8a\xaa\xc4\x04Z}J=\xd1Y8\x9e\xa4\x0f\x93R\xf1\xe2\x91t\xab+\xf5f\xb6%o\xa6\xb8[\xccCLp\xcd\xe5=Q\xb7+\x95'\x1d+\xb1t7\xd6\xc9\xcb\\\x83\xe8\xf8r5\x89\xc68\xda/'\xba\xc4e \x9d\xe1\xb3\xda\xbcu_1JTY|\xdfr\xd6\x14\x87\"\xe0D(\x1a(\xac\x84\x8e\xbbJ\xf0\xecRf\xe1\xa1PA~r\x08l\xa0\xf3_\xfa\xab^k\xd47gC\x9aw\xaa\x94\x01\xb6\xea\xa9\x80R\xf8\xe6\x1c\xf7 \xd5-\xd8\x9bsXTO!\x06\xc3 r\x9fA\x8ap\xf1\x01\xbahX\xd2\xe2(\x1aLv\xd4\xa5\xb1fTj\xd1 \x8f\xa1\xab\x92\x12\xc4\xff\xb1\xbd\xad\x01\xa3Y\xd7@\x1bw\xdc\x95\xfc1\x82\xc4\xe5\xc0\xc9\xba6\xab&;\x1cx\xa9\x9c[\x19\x80\x9d\xa0\xe1\x11\xbe0\xee\xb9\x9a\\\x14\xca%\x1a\x005v9\x82\xa6\x8eW\xbd\xf1\xe86\xa8\\\xda\xc2\x91\xdf\xbb\x95K[Ch9\xbb\x95K\xd8F\x84\xf3\xc3]\xde\x10\xbb\xc9\xde\xfb\xbb\x01v+\x97\x1c\xf7\x00\x00\xd7\xb9\xbc\x81\x15\xf3\x0d@\x1a\x81\xcb`\xc3\x8d1ko\xec\xe2:\x14\x16\xf1\xb1o\x80\x91C\xed\xd2\x81\xbd\xb1{)\xa4\x8f\xab4\x00\x80\xf2I\x15D\x81\x07/W\xd1\xceU\xdd\xbd\x84}\x91Y \xa2rUj\x88\xd5-\xfayhV\x01\x96\x04\x11\x1c\\\xae\xfaM\xa3]\xdd\xbdd\xc9\x0d\xd0i\xaf\x8a\x13\x1e\xc3\xcbU91d\x15\x0cq\xe3\x0b\x830\xfc\xfa\xf3\xf7\xef\xbc\xfe\xc2\xf4\x93w\xa7\xaf\xfej\xf2\xda\xfb\xd3\x9f\xfep\xfa\xde\xfb\xd5\xdd\xa2'\x97\xb6\xac\xddK[\x08\xe5\xc5\x11oH\x88w\x97G\xdc\xd0\xa7\xef\xbc2\xf9\xf1{\xd3\xdf\xbft\xf7\xe37\x08\x82\xd5]\xa3\x06\xf2\xc5\xab`\xdc\x940\xde^\x1ec\x82\xcc\xe4\xc7\xbf\xbc\xfb\xf7_Vw\xf9o'\x88\xa0i.\x8f\xe08\xf2\xaa\xbb\xdf\xfb\xdb\xc7N\x12\x9d\xd6\xf2\xe8L\x7f\xf2\xf1\xf4\xe5\xe7\xa7\xef~1\xf9\xe2\xb5\xea.\xff\xed$\x11\\\x81\x05\x87I\x12\xde\xfd\xf8\x8f\xd3O~0\xfd\xe9\xff\xc5H\xfe\xe5\x13O<\xce\x97\x9c \xa2\x0dcyD J\x93\xd7~0}\xe3w\xd5]\xfe\xdb \"\xd8l\xac\x80\xe0W?\x99\xfc\xfc\x17\x93\xdf\xbe9\xfd\xe8\xf7\xd5]\xfe\xdb \"\xd8^a\x06\xc9j\xbd\xf3\xc1g_\xff\xd7Kl\xed\x92o\xab (\x89\xc3\xf6\nr\xdc\xd4\xef|\xfe\xcb\xbb\x1f\xbd\xc7\x04\xa1Y\x03|\xc1*X\xca\xd3\xd8Y\x1eK^\xe8\xe9\x04\xd4)\xc9\xc1N}y,\xc7\x91\xa7\x13\x08'+\x0c;+\xac\x10^\xfc\xe9\x04\xd4)I\xc4\xce\n\\(KD\x9d\x80;E\xb1\xd8YaC\xe6\x05\xa1N@\x9d\x92l\xdc^A\xf4\xf0\xd2P'\xa0NG@\xb6\x8cU\xf8\x93\x13\x89:\x01u:R\xb2\xb5\x8a\xd2\xd8\xd0\xef\xbd\xfd\xda\xf4\x9d\xcf\xee\xbe\xfa\xc7\xc9k?\xfd\xfa\xd3\xe7\xbe\xfe\xf4\xdf\xdd\x90\xc9\xccF\x0d\xe4\x1f?\xfa\xf8IK\xd0\xd6I)\x91:\x01u:\x12\xb4\xb5\xa2&\xa9\x13\x08'*A['\xa5N\xea\x04\xd4\xe9H\xd0\xd6I\xea\x94:\x01wz\x12\xb4uR\x8a\xa5N@\x9d\x8e\x04m\xb5V\x98SQ\x82bP\xa7#A\xdb\x8d\x15\xd6\x8c(A1\xa8\xd3\x91\xa0\xed\xc6\n\x1a\\S\x9f\xfe\xf2Ww?\xfe\xd7\xc9?~x\xef\xef?\xccdg\xb3\x06\xf8\x07'/5\xdb\x8d\x158@\x94\x9a\x18\xd4\xe9H\xcdvs\x85\xd5\x84\xa5&\x86p\xa2R\xb3\xddl.\x8f\x93(51\xa8\xd3\x91\x9a\xed\xe6\n\\\x99\x97\x9a\x18\xdc\xe9I\xcdvk\x85\xf3\x85(51\xa8\xd3\x91\x9a\xed\xee\ns*JM\x0c\xeat\xa4f\xe7\xa4\x0e\xe6:\x01u:R\xb3\xb3\xca\xe9\xbc\xa5O^|\xf7\xde\xcf>\xc8\xe4e\xab\x06H\xd1\xc9K\xca\xce\xc9\x9d\xd0;\xa7wB\xef\xac|B\xef\x9c\xf8 \xbdsr'\xf4\xce\xe9\x9d\xd0;'{B\xef\x9c\xee \xbdsr'\xf4\xce\xe9\x9d\xd0\xbb\xf5\x15\xe6T\x94\x94\x18\xd4\xe9H\xca\xae\xb9\xcaN.HJ\x0c\xaaHRn\x85\xbb\x97\xb6\x1c\xf7`\xf7R\xb8\xfb\xdf\xfc~\x1c\xee\xe0\xa2\xa1\x89:\xf7\xad\x11\xbc\\-\xf4\x1bq\xc8\xd1\x9bv\xe4\xfd1\xd5]\xdc\x03\xee\xa0\xd8\xb5D\xfbG=\x0d\x1bYoJgO\x99\x9e\n\xfcA\xac\x9b\xc6\xee\xa5a3\xebF\xf0\xd0\x94\x01/\x8aF\x06\xb5\xb9{)\xcb3\x82\xe6\x90V&(\x10t\xde\xfax\xf2\xfa\xaf'\xef|x\xe7\xb3\xaf\x84Y\xe7\x1b\xf2\xa8\x8d#\xaf\x14FX\x1e\x16#b\x11n\xd8\xd8\xb2Bw\xeb\xc0\xd8\x8a\xdd\x04n\x85\xae\xbf\xbfA\xdb\xe7\x1e\xa4\xd0\xac9\x18\n\xee\x9a2\xa8\x8a\xd2q\xce\xe4\xfd\xd5\xcd\xbf\xf9\xebR\x13\x95s\xca\x94A%/\xfe\xe6\xa0\xf3\x9d\xebO\x94\xc2F\xf0\xbc\x94\xc1D\x14o\x1c\x16\x03w\x7f\x1c\xc1\xddK\xf8\xc2\xce\xee\xa5\x04\xbb\xb3/%\x11\xfaH\xd7\xfe\x06\x97%\x05'\x9c\xdc\x00\x14\xaa\x04/\x19\x96k\xf5\xd5?\xde{\xeeG\x8b\xb6\xba\xf3\xc9\xe7\x93_\xbc$\xb5JG\xf7\x9f\xd3\xb7^\x15\x9em\xa1!l\xb1\xe1\xf4\x03\xe7\x98\x8e\xca\x99\xdbS\x12\xdc\x82~\x06\xcc)\xd5h\xfa\xd6\xc7\x8b6!o\x18\x90Z1px9O^\xffx\xf2\xe3\x0f\xa7o\xfe\xe1\xde?\xber\xe7\xcb\x8f\xee\xbc\xfd\x0f_\x7f\xfe\xc1\x9d\x1f\xbd,\xb4\xa1#%C\xdc\xa2t\xdcbt\x15\xb8\x86w\x87\x95\xe2\x1aa\xbb9)\xaeQB-\xc9\x05\xa4\x95\x92\x17\xe6\xb6=Y.\xb9\x168pQ\x8a\x93\x97?,\xda\xea\xce\x8f\xff0}\xee\xf9;\xef>\x0f\xea\xd3\xdf\xbe?\xf9\xf4Sp\xef_~Q\x9f|\xf1\x83\xc9\xa7\x9f\xe69\xa1$\xfa\xdf\x8de\xce\x9b\xdbD\xc9\xafs[\x11\xec\xbf\xfe\xea\xbd\xe9\xf3\x1f/\x8d\xed\x9e\x95X\x8bv\xbc$\xba?zq\xfa\xceo&\xaf\xfdq\xc95&xt\xcb\xac1QY\xe2\xd6\x98j\x7f\x98\xbc\xf3\xbb\xc9\xbf<\xf7\xcd\x17o\xb3\x9al\xe3E\xdbSok\xeb\xf0\xf0\xb0\x06\x8f\xacQ\xe8\xc1\x9a\x1d\x8c\n\xb7\xe3\x92\xd5gn\xd2a\x04\x01\xbe\xa2\x8doh_\xae\x0e,/\x86\xfc\x90i:D\xfe\x92\x0dP\xdf>H\x8e\xd9m\x0e\xdd\x1e\xe1\x8bA\x8f\x05\x96\x03\x9d* I\x00\x9f\x8e\x03\xbf\xca\x87EU\xb9wvq\xef\xf1\xe1.o\x1cFV\xa8hN\xb5\xd9\xe0\x00F\x03/8\xec\x01\x92\x91u\x87\xcb\xd3L^\xd4y\x00w\xd8%\x8bFx\xb4\x93\xe5\x16C_\xd2\x8c\xbe4]L\xc3\x08\x8fv\xaa\xbb\x97\xd2\xb4\xff\xd68 \xec \x8a\xa0\x9d\\\xae\x06\x83A\x95\x94X!\xce\x14\xfa\x0c\xa4\x85\x8a\xe9K\xac>~\x85\xd8\xe5j=\xd5\xbd\x159\xa4\xb3\xcc\x87\x06\x1c\xedp\xc9V\x11R\xc2\xdd\xf2\x0cu\\3M\x0e\x8c\xa3?\x11#2\xa4\x99n\xae\x9ce.\xf0p\xe0z\x1e:\x18\xd8#\xdd\x0f\xd2\x10E\x1a1:\x1b\x08\xc9[\xb9\n\x04\x82\x06?M\xbaQ\xc4\x198\xa1Dv\x82\x11\x02\xb7\xcd\x0e\x9a\x199\x88\x15\x95\xf1yW\xd2\xb7\xf7\xa1\x07R\xaal\xa1\x8c\x87 r\x9a\x8a\xafR\xf6)\xc4\x1c\x91(f\xd1|a\x04c\xe8'\xf8\xde5m\xa1x0\xab\xc3\x1c\xd1\xd5\xbd\x8e\xa0\x15\x8f#D\x82\x10\x895\xbc\xf6\x8f\xd2\x7f\x99b\x8a\x1e\x16\xe3M\xdfp\x94';\xf4\x12\xd2\xf5n\x8b\xd2\x98\xff=\x0f\xa5\xac\xd6\x8c\x81\xa6\xef\xdf3v\xe60\x92\x1d8P9\xc1\x0c|\xe1,e\xd7\xc2f\xcdy\x89\xe6\xfc\x9d\x12i\x0e\xb3'\xe5V\x94\x1a\x12\xd7\x15\xa9\x97\"Lo/\x90U\xc0\xde\xf4\xdd!\x1cY\xa2;\x96\xfeg\xe1\xbef2\xfc,\xc6\x91'\x88\xd4\xc5W[\xa5\x8e\x04 \xd8\xc5}\x1a\x02\xa3\xa1}\x8b\" C\x05\x05K.\xce\xcc+\xea5'\x89\x86\x1a\xd9\x0f\x9e\x15\x97L\x19\x0e.\x9c q\xe6Ovr\xe7\xcf\xa0\xf9\x80f\x10\x103\x11\xfdC\xa0\xd0\x9e\xb3W\xb4q\xef\x9f\xab\xee\xe2qW\xe9\xc4\xf7\xe4\x16d\x16\xaa\xbbuZA;7\x14j<<\x14B\xca|!\x85H\x8b\xean\xb5^\xab\xd7\x8c\xea\xb9\xa3S\xf3\xe1\xa1\xd3w\xe3\xfd\xf9d\xaa\xa6\x94y(\x08\xb4\xd2>\xd2z@\xb4\xbb\xad\x9a\xdc\xd9\xbf\x95S\x9f\x9d\x1e\x84\xd3\x0c\x1b$\xa7\xfdr\xfa\xac\"m\x8c\xb5\x0fcA\x0f\xc4\xda\x87\xb1\xf6a \xe2v\nN\xdd\xb3P*c\x16Y\xfb0\xd6>\x8c\xb5\x0fc\xe1\xc9\x9d?\x83k\x1f\xc6Y\xa7\xd0\xc3\xea\xc38?k\xe4lx'\x96\xa0\x07>c\xcd]2\xc6vJ\xa9sB\xb0\x07\xe5\x92`$\x02\xb7\xcf\x8f\xf8i?\xe0\xb9\\\x80\xdd\xd7\xae9\x89v\x9d\x07D\xbb3\xea\x9a\xebl\x97\xd5\x83\x15\xae9\xd6xV\xb3Ss\xcd\xcdL\xa5\xc1\xd0\x99e\x8d\x9a\x93m\x83Rk\xb6\xdb\xaee\x94\xf3\xa4,\xe4\xb6+\x81\xd5\\\x9f]\xcb4K!\xb6\xa0\xcf.\x8c\x82\xa3\xe3\xad\xc8\xf2\x9d`${\xed\xf8g3mi\xc5~\xbb\x96\xd9*\x85\xf5\xfd\xf2\xdb\xb5\xccn)|\xee\x93\xdf\xae\xd5(\xe9|Y\xfb\xedJ\xdb\xdc\x1fF\xbf]\xc9\xa1Y~\xe0\x1f\x8f\x82q\xbc(\xae\x93\xd7\x7f\xbdh\x13\xa5\x17\x82\x81{\xf9\xab\xc9\xeb\xaf\xdc\xf9\xf0\xa5\xc9g\xbf\x06\xf7\x9e{m\xfa\xd6\xab=\x03L\x7f\xf6\xd1\xbd\xe7\xde&\xcfz&\x98\xfe\xe6\xb3{\xff\xfe&\xfd\xda\x00\xf7\xfe\xe3\xad\xc9\xcb_\xf5\x9aK\x0f>\x8c\x82$\xb0\x03o\xd1\x81\x9c\xf4\xd8_y\xed\xeeG\x1f\x01$ z\x06\xfes\xb3g\xe2\xbf[\xe4Kc\xe9\x11b\xa7Et\xbc(\xb6K\x0cp\x16\xefN~\xfe\xe5\xe4\xa3?\x80\xaf\xff\xeb\xa5\xc9\xaf\x7f\xf0\xcd\x17o\x7f\xfd\xe9o'?\xff2?\xa8Rn\xa5|\xa2\x9cR\xe2n\xedk^\xfb\x9aWu\xde~\xdb|\xcd\x8f>\xbeh\x9f\xcba\xfa\xe8\xe3\xc4\x91\xbb4\xa2\x8f\x07\xd1\xc2>\xf1\xe5P\xbd\xf3\x1f\x1f\xf3:\xf6\xc2\x88.\xb9\xe3,\xb7\xdc\x88\n|\x8a\xdb\xcb\x95e\xb5\x87\xe5\xc6\xf3@\x94\x84k\xcbm\xa1\xcb\xb1\x17\xd9)\x97\xc6\xf5\xf1(8p}{ai\xbe\x1c\xb2w\xdey\xfe\xeb\xcf\x15\xdbxId\xaf$I\xe4\xf6\xc7\x89\x1b,\xac[/\x87\xef\xe4\xcb\x7f\x9a|\xf2\x8b\xc9;\x8a\x80\x96\x92(?z\xf3>I\xc4\xbb_\xbd~\xf7\xf5_M\xdexaiTo\xdaA\xb40\x1f,\xb9,q\xf0\xf0\xe4\xc5\x17\xa6o\xfc\x0eL?\x7fo\xf2\xe2\x0bF}\xf2\xe2\n\xb8\xe3\xb7\xaa-\x8a\xc6r3=\xfd\x97_M?{}\xc5\xfd\xe7f\x08\xa1\xb3h\xcfKN\xf6?\xbf2\xf9\xec'\xf7\x9e\xfb%\x92\x81\x93W\xde\xf8\xfa\xcbWF\xf1\xd2\x88_\x8b\xa0\x95\xc0'\xdc\xd1}\x9a\xed\xc9\x8b?\x9f|\xfe\x19:\xbf\xbe\xf9\x9fK#\xfd\x98\x15'\xf7\x0f\xe5\xe9;\xcfM^\x7fu\xfa\xfes\xd3\xdf\xbf\xb4\"\xe27,\xd7\x83\xce}\x8c\xe0\xbb\xfb\xd5/\xee|\xfe\xdb\xc9\x07\x9f\xdc\xfd\xcf_M\x7f\xf3\xdeI\xc5\xf2\xb5\x1b\xe5\x0c\x87\xf7#\x96Oem,\xdf\"\xc5\xea,\xc4\xf3QrV\xfb\x11\xb4n\xe9\xae\x1f\xbb\x0e\xec\x81\xb1\x1f\xc3\xa4\xd0\x82~&\x82\xfd\xb6\xd7\xc1~eB\xf5\xceN\xb0_\xa3\xb5\x0e\xf6\xcb\xf5z\xdf\x82\xfd\xccz\x81#o\x16Ne<\xad\xebh\xbf\xc5\xa2\xfd\xc82`r\xaeU\xd6\xcb\xb9T\xb4\x1f\x03\xbf\x1c\xe7,\xe7\xce\xa6\x0e\xd3\xf3\x1f\xed\xb7\xda\xe4\xce\x9f\xc1u\xb4\xdfY\xa7\xd0:\xda\xefAS\xe0\xa1\x8d\xf6{\xf4\xf1\xf9\xd1O\x86\xd1\xa8\x99F\xb3f4\x1eD\x1a\x89\xd3&\xdd\x83\x8e\xfb[\x9at\x8f\x07Qq\x9cfF\xbcz\xb7~\xfe\x88v6\x02\x0c\x97!\x1a\xf5\x81\xcc\xdd\xa5\x8csG\xb3\x07\x15X\xb82\xcdRG\xcf\\\xa25\xcf\x1d\xd1\xba\x0f+\xd1\xa8\xe7j\xbe\x80$\xf1\x18\xe7ODn?\xac\x94c~\xbc\xf9\xa4\x9b\xfe\xfeg\xd3O~y\xe7\x9d\xe7\x1f6\xea\xadv\xa2\xad?\xac\x94\xe5\x9c\x9e\xf3\x89;\xf9\xe8\xf9\xe9\xff\xf9\xd7\xc9\xa7?x\xd8\x88[\x82\x82\x0f\xca(\xb12\x05\x1f\xbdY\xe2\xbc0y\xe5\xd5\xe9\x1b\xbf;\x87d;\x1b\x96\x90%\xc8\x86\xfd\xe1s\xb5\x17\xe3\xfcYF\x8c\xb3a\x1aY\x86d8\x0c`\xfebs\xc3\xda\xd3\xae\xe5\xef\x1f\xb9\x96\xef\xb95;\x18\x9d\xc3\x85\xf7\xd0\x9aWpl\xc4\xfc\x85\xd7\xea\x9c\xbf\x93\x83\xf1\xd0\x1aV\xb2\xb8\x90\xf9\xcb\xcf\xac\x9b\x86^o\xe8F\x1b\xd4\xb7{\xa6\xd13\xcds\xb8\xfe\x1eZs\x0b\x8b\x96Y\x94\x90\x0d\xa3\xd7h\x9fCB>\xb46\x18.zh\xae8\xad\xa7\xf4:/d{\xd0V\x98\xfby7\xfd\xd4'\xf3l\x18FJq\xfd\xd9\xbc\x9c\xbe\x1a\x85V\xb2}\x98\x0f\xca\xf6q6o\xa7\xb7\x0c\x93\x90\"\xeb\xb1\x88\x98\xf9\xdb\xe9i\xe3Y\xcd\xd4\xd4\xca\xddNo4y<\x14\xbfs\xb7\xd3\x0b^S\xce\x10\x99\x15YY\xf8&sJ\xa1\xd97\xd2\xdb\x8dr\x97\xe6\x16\xba\x91>y\xf1\x85{\xef\xfd^\xc4\xea\xce\xdb\xff\xb0\xe8\xa5\xf4v\xb3\xdc\xfd\xe5\xa5.\xa5[\x9e\xa7\xbe\x91ny\xd9\x95\x9c\xc5\xae\xa3\xb7\x9b\xcdR\xf8\xde\xaf\xeb\xe8\xedf\xb9$\xe1\xf7\xe9:z\xbbU./\xf8\xfa:z\xf9\xfb\x9a\xeb;\xdbK\xdc\xa0;\xe9\xb1\x9f\xde\xa5\xba\x87\xeb\xcev\xc9A\x85\xd6\xfe\xc2\xf75\x96\x18\xd1,\x92\xa1\xdd\xe9\x8d\xdf}\xf3\xc5\xcb\xf7>\x7f\xeb\xeeG\x1f\x18\x93/\x9e\x9b\xfc\xdbKK\x8f(v\x9fy\xd0#\x9a~\xfc\x1a\x19\xd4\xbd\x1f\xbe\x96\x8d\xab\xbe\xf4\x90\x1e\xc6<\x17\xa5\xae\xaf\x889\x03\xda\xddr[\xe4:g@1\xb4k\xeb\x9c\x01\xa5Z\xads\x06\xccm\xb1\x1c\xa6\xeb\x9c\x01\xf3\xda-\xb7\xdc\xd69\x03N@I]\xe7\x0c(\\\x0b\xeb\x9c\x01\xf3\x9b,\x87\xea:g\xc0\xfdb\xe2s\x913`\xfa\xf1\x7f\xdc\xf9\xb7\x7fZ\x1a\xfdu\xe6\x80\xb9\xad\xd6\x99\x03r\x99\x03:\xedr\xd6\xdd\xfb\x979\x803 \x97\xac\x9e\xe2\xb3\xce\x19\xb0\xce\x190\xc3\xe9\xb4\xce\x19\xb0\xce\x19@%\xc5 \xe4\x0c0\x0b\xdc\xab\xb3p*\xe3\x00_\xe7\x0cX\xe7\x0cX\xe7\x0cXxr\xe7\xcf\xe0\xd9\x88\x94'Ph\xcf\xeb\x9c\x01\xe7\"g\xc0\x93\xe7\x86\x02g+\xa8\x1d\x9c\x1f\xe1s\x16\x03\xcf\x97`\xfbuj\x86\xb3\x18v\xbe\x04!\xbf\xcd\x89\x1a\xceb\xc0\xf92$\xfc\xf6\xa6mx\xd0\xb1\xe7\xf3\xff\x94\xa2\xe0\xb79\x89\xc3\xd9\x88x_\x99\x84\xe7<\xa5\xc3jG\xd6\x07\x15\x18_\x82\xb8\x8b\xd0\xf8[\x90\xfc\xa1\x041\x1f\x94\x01\xa2\x04\x15\x17!\xe6:\xdf\xc3\x19N\x1c\xb0\x04=\xbf\xdd\xd9\x1f\xce\x86\xc5de\"~{sA\x9c-\x83\xcb\xf2\x04\\g\x868\xdbI\x06\x96\xa1\xe9\xb78O\xc49\xb1\xf2\xac\xb3F<\x0c\xc9\x06\x96 \xec:\x87\xc4\xd9JF0\xffO)\xb2~\xbb3J\x9c-\x93\x10P^\xaf?\x833\xbb\x92\x8d\xe6\x81%/H\xa7\xf9\xa9\xf3#\x86\xcc\xb3a#)%j\xd6i\x9c\xbe\xf9\xee\xf4\x8d\x17\xa7o\xfea\xf2\xda\xc7\xb4\xe4\xb7\x1f\x90>\xee~\xf5\xf3\xbb\xef\xbdL@d\xac5/\xcdG\xa7S./\xc4Ri>\x1c\xe8\xc1\x04\xaa3}\x90g)\xcc\xc5\x92}t:\x8dRX\xdf\xafd\x1f\x9dN\xbb\x14>\xf7)\xd9G\xa7\xb3]\n\x9du\xb2\x8f\xf2\x17\xad\xdd0\x83T\xee\xea\xc6I_\xc8\xff\xcd\xfff\x92j\xc5\x1bO\xe1\x127nOk0\xe4N\xee*\x17\x9d\xb18Y\x14\xb7%\x12@\x94\x1b\xce\xdb\xff\xf0\xf5\xe7\xef\xdfy\xfd\x85o\xbex\x99\x88\x9do\xbex\xdb\x0d{h\xca\xbf\xf9\xe2e\x8c+Y\x1f\x93\xdf\xbe5y\xe7\xc3\xe9\x9b\x7fp\xc3\xc9?\xbd\x8c\x9e\xd3\xf2\x0f>\x99\xbe\xf1\xe2\xd2\xb3\xf1-I\x1d\xd1\xad\x97\x13\xb8\xeb\xd4\x113n\x0e\xaeSG\x94j\xf5\x90\xa5\x8e\x98\xbe\xf9\xee\x92KL\xbc\"\xd85K&T\xbboW\x04Eu\xb2|\x8b\x14\xab\xb3pQ\xb0\xe0pt&\xee\x02\x92\x83\xe9\xfa.`\xa9\xe3\xef*\x10N\xea.\xa0\xd9 \xd7w\x01\xe5^\xef\xdb]\xc0V\x81\x89f\x16Je\x8ch\xeb\xab\x80\x8b]\x05$\xab\x80\x99\xf5:e\xedWK]\x05d\xe0\x97c\x9c\xe5,\x95F\x978J\x1e\x90\xa1\xf2\xfe\xdd\xc6Ymr\xe7\xcf\xe0\x832\xf5.a\xa7\xbfv6\xaf\x02\x9e6\x85\xceF`[)\n W\x01\xd5-8W\xca\xf9!\xd1\xd9\x08]+E\xa2\xb3\xe9\xecZ\x8d@+m!\x0f*D\xedl\xfa\xba\x0c\xb3[VW\xc8\xfb\xba\xd2\xc6\xb3\x9a\x95\xf4uQWd\xf1\x8c\xe0Y\x0bw)\xeb\xe33,zP\xb9\xb4\x85\x8f\xf5\x95K[\xc3d\xe4\xed\xfe\xff\x00\x00\x00\xff\xffPK\x07\x08Lok\xe7\x987\x00\x00\x03\x8e\x01\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x92*wRLok\xe7\x987\x00\x00\x03\x8e\x01\x00\n\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb6\x81\x00\x00\x00\x00index.htmlUT\x05\x00\x01\xa4zY`PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00A\x00\x00\x00\xd97\x00\x00\x00\x00" 11 | fs.Register(data) 12 | } 13 | --------------------------------------------------------------------------------