├── .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 |
--------------------------------------------------------------------------------