diff --git a/.gitignore b/.gitignore
index 93e5a6e..4cfd383 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,4 +20,5 @@ config.yml
#Goreleaser
/dist
#Logs
-/log
\ No newline at end of file
+/log
+/.idea
\ No newline at end of file
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 6216c8b..8d8c66f 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -4,6 +4,7 @@ env:
builds:
- env:
- CGO_ENABLED=0
+ ldflags: -s -w
goos:
- linux
- windows
@@ -28,6 +29,11 @@ archives:
format_overrides:
- goos: windows
format: zip
+ files:
+ - README.md
+ - README_zhCN.md
+ - config.yml.example
+ - LICENSE
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
diff --git a/README.md b/README.md
index b81acd7..45707a4 100644
--- a/README.md
+++ b/README.md
@@ -4,20 +4,13 @@


-English | [简体中文](https://github.com/iyear/E5SubBot/blob/master/README_zhCN.md)
+English | [简体中文](https://github.com/iyear/E5SubBot/blob/master/README_zhCN.md) | [交流群组](https://t.me/e5subbot)
A Simple Telebot for E5 Renewal
`Golang` + `MySQL`
-DEMO: https://t.me/E5Sub_bot (all new functions will be tested in DEMO)
-
-Communication: [Telegram Group](https://t.me/e5subbot)
-
-## Preview
-
- 

-
+DEMO: https://t.me/E5Sub_bot
## Feature
@@ -25,7 +18,7 @@ Communication: [Telegram Group](https://t.me/e5subbot)
- Manageable Simple Account System
- Available Task Execution Feedback
- Convenient Authorization
-
+- Use concurrency to speed up
## Principle
@@ -60,15 +53,16 @@ docker-compose up -d
```
### Binary Deployment
-Download the binary files of the corresponding system on the [Releases](https://github.com/iyear/E5SubBot/releases) page and upload it to the server
+Download the binary files of the corresponding system on the [Releases](https://github.com/iyear/E5SubBot/releases) page
+and upload it to the server
-Windows: Start `E5SubBot.exe` in `cmd`
+Windows: Start `E5SubBot.exe`
-Linux:
+Linux:
```bash
screen -S e5sub
-chmod 773 E5SubBot
+chmod +x E5SubBot
./E5SubBot
(Ctrl A+D)
```
@@ -77,7 +71,7 @@ chmod 773 E5SubBot
Download the source code and install the GO environment
```shell
-go build
+git clone https://github.com/iyear/E5SubBot.git && cd E5SubBot && go build
```
## Configuration
@@ -91,6 +85,7 @@ bot_token: YOUR_BOT_TOKEN
socks5: 127.0.0.1:1080
notice: "first line \n second line"
admin: 66666,77777,88888
+goroutine: 10
errlimit: 5
cron: "1 */3 * * *"
bindmax: 3
@@ -100,21 +95,25 @@ mysql:
user: e5sub
password: e5sub
database: e5sub
+ table: users
```
-`bindmax`, `notice`, `admin`, `errlimit` can be hot updated, just update `config.yml` to save.
-| Configuration | Explanation|
-| ---- | ---- |
-| bot_token | Change to your own `BotToken` |
-| socks5 | `Socks5` proxy,if you do not need ,you should delete it. For example: `127.0.0.1:1080` |
-|notice|Announcement. Merged into `/help`|
-|admin|The administrator's `tgid`, go to https://t.me/userinfobot to get it, separated by `,`; Administrator permissions: manually call the task, get the total feedback of the task|
-|errlimit|The maximum number of errors for a single account, automatically unbind the single account and send a notification when it is full, without limiting the number of errors, change the value to a negative number `(-1)`; all errors will be cleared after the bot restarts|
-|cron|API call frequency, using `cron` expression|
-|bindmax|Maximum number of bindable|
-|mysql|Mysql configuration, please create database in advance|
+`bindmax`, `notice`, `admin`,`goroutine`, `errlimit` can be hot updated, just update `config.yml` to save.
+
+| Configuration | Explanation|Default|
+| ---- | ---- |----|
+| bot_token | Change to your own `BotToken` |-|
+| socks5 | `Socks5` proxy,if you do not need ,you should delete it. For example: `127.0.0.1:1080` |-|
+|notice|Announcement. Merged into `/help`|-|
+|admin|The administrator's `tgid`, go to https://t.me/userinfobot to get it, separated by `,`; Administrator permissions: manually call the task, get the total feedback of the task|-|
+|goroutine|Concurrent number, don’t be too big|10|
+|errlimit|The maximum number of errors for a single account, automatically unbind the single account and send a notification when it is full, without limiting the number of errors, change the value to a negative number `(-1)`; all errors will be cleared after the bot restarts|5|
+|cron|API call frequency, using `cron` expression|-|
+|bindmax|Maximum number of bindable|5|
+|mysql|Mysql configuration, please create database in advance(If you upgrade the old version, please set table to users, otherwise the data table cannot be read)|-|
### Command
+
```
/my View bound account information
/bind Bind new account
@@ -141,25 +140,14 @@ Suspected memory leak. Not yet resolved, please run the daemon or restart Bot re
https://t.me/e5subbot/5201
-## Third-Party
-- [Telebot](https://gopkg.in/tucnak/telebot)
-- [Mysql_driver](https://github.com/go-sql-driver/mysql)
-- [Gjson](https://github.com/tidwall/gjson)
-- [Cron](https://github.com/robfig/cron/)
-- [Viper](https://github.com/spf13/viper)
-- [Goreleaser](https://https://github.com/goreleaser/goreleaser)
-
## Contributing
- Provide documentation in other languages
- Provide help for code operation
- Suggests user interaction
- ……
## More Functions
-If you still want to support new features, please use FeatHub to vote. We will consider the voting results and other factors to determine the development priority.
-[](http://feathub.com/NervJS/taro)
-
-[](https://feathub.com/iyear/E5SubBot)
+If you still want to support new features, please initiate an issue.
## License
diff --git a/README_zhCN.md b/README_zhCN.md
index 019784e..b382260 100644
--- a/README_zhCN.md
+++ b/README_zhCN.md
@@ -4,20 +4,14 @@


-[English](https://github.com/iyear/E5SubBot) | 简体中文
+[English](https://github.com/iyear/E5SubBot) | 简体中文 | [交流群组](https://t.me/e5subbot)
A Simple Telebot for E5 Renewal
`Golang` + `MySQL`
-DEMO: https://t.me/E5Sub_bot (长期运行,所有新功能会在DEMO测试)
+DEMO: https://t.me/E5Sub_bot
-[交流群组](https://t.me/e5subbot)
-
-## 预览
-
- 

-
## 特性
@@ -25,6 +19,7 @@ DEMO: https://t.me/E5Sub_bot (长期运行,所有新功能会在DEMO测试)
- 可管理的简易账户系统
- 完善的任务执行反馈
- 极为方便的授权方式
+- 使用并发加快运行速度
## 原理
@@ -61,13 +56,13 @@ docker-compose up -d
在[Releases](https://github.com/iyear/E5SubBot/releases)页面下载对应系统的二进制文件,上传至服务器
-Windows: 在`cmd`中启动 `E5SubBot.exe`
+Windows: 启动 `E5SubBot.exe`
-Linux:
+Linux:
```bash
screen -S e5sub
-chmod 773 E5SubBot
+chmod +x E5SubBot
./E5SubBot
(Ctrl A+D)
```
@@ -76,7 +71,7 @@ chmod 773 E5SubBot
下载源码,安装GO环境
```shell
-go build
+git clone https://github.com/iyear/E5SubBot.git && cd E5SubBot && go build
```
## 部署配置
@@ -90,6 +85,7 @@ bot_token: YOUR_BOT_TOKEN
socks5: 127.0.0.1:1080
notice: "第一行\n第二行"
admin: 66666,77777,88888
+goroutine: 10
errlimit: 5
cron: "1 */3 * * *"
bindmax: 3
@@ -99,21 +95,25 @@ mysql:
user: e5sub
password: e5sub
database: e5sub
+ table: users
```
-`bindmax`,`notice`,`admin`,`errlimit`可热更新,直接更新`config.yml`保存即可
-| 配置项 | 说明 |
-| ---- | ---- |
-| bot_token | 更换为自己的`BotToken` |
-| socks5 | `Socks5`代理,不需要删去即可.例如:`127.0.0.1:1080` |
-|notice|公告.合并至`/help`|
-|admin|管理员`tgid`,前往 https://t.me/userinfobot 获取,用`,`隔开;管理员权限: 手动调用任务,获得任务总反馈|
-|errlimit|单账户最大出错次数,满后自动解绑单账户并发送通知,不限制错误次数将值改为负数`(-1)`即可;bot重启后会清零所有错误次数|
-|cron|API调用频率,使用cron表达式|
-|bindmax|最大可绑定数|
-|mysql|mysql配置,请提前创建数据库|
+`bindmax`,`notice`,`admin`,`goroutine`,`errlimit`可热更新,直接更新`config.yml`保存即可
+
+| 配置项 | 说明 |默认值|
+| ---- | ---- | ---- |
+| bot_token | 更换为自己的`BotToken` | -|
+| socks5 | `Socks5`代理,不需要删去即可.例如:`127.0.0.1:1080` |-|
+|notice|公告.合并至`/help`|-|
+|admin|管理员`tgid`,前往 https://t.me/userinfobot 获取,用`,`隔开;管理员权限: 手动调用任务,获得任务总反馈|-|
+|goroutine|并发数,不要过大|10|
+|errlimit|单账户最大出错次数,满后自动解绑单账户并发送通知,不限制错误次数将值改为负数`(-1)`即可;bot重启后会清零所有错误次数|5|
+|cron|API调用频率,使用cron表达式|-|
+|bindmax|最大可绑定数|5|
+|mysql|mysql配置,请提前创建数据库(旧版本升级请设置table为users,否则读不到数据表)|-|
### 命令
+
```
/my 查看已绑定账户信息
/bind 绑定新账户
@@ -143,54 +143,17 @@ mysql:
> 无法通过Bot创建应用程序
https://t.me/e5subbot/5201
+
## 更多功能
-如果你还想支持新的特性,请使用 FeatHub 进行投票,我们将综合考虑投票结果等因素来确定开发的优先级。
-[](http://feathub.com/NervJS/taro)
+如果你还想支持新的特性,请发起issue.
-[](https://feathub.com/iyear/E5SubBot)
## 做出贡献
+
- 提供其他语言的文档
- 为代码运行提供帮助
- 对用户交互提出建议
- ……
-## 小总结
-#### 得到了什么?
-- git的基本操作:add,commit,pull,push.但是对代码合并与冲突解决没有得到足够的实践和深入
-- github版本库方面的使用(issue,pull request...),还有一些没玩过
-- 体验了Docker Hub 的自动构建
-- sql的CRUD,但都只是浮于表面,有时间再去琢磨
-- telegram bot的golang基本框架和一些有意思的玩法
-- 一些著名第三方库(viper,gjson)的基本用法
-- docker以及docker-compose的基本用法
-- 一些工具:gox;goreleaser的基本用法
-- ……
-#### 还缺点什么?
-
-- 对`CGO`知难而退,编译各种出错。下个项目如果用到`sqlite`一定解决`CGO`交叉编译
-- `DockerFile` 还不会写
-- 对项目的概念和意识不太行,目录太乱,随缘写全局变量
-- 看`telebot`的文档还好,看`stackflow` 只能看看代码。搜索还是习惯性带中文导致`stackflow`很难出现,降低了解决问题的效率
-
-#### 最后
-
-从2020.3.28开发至2020.4.12,一共经历14天,利用课余时间最终完成。
-
-就要开学了,因为马上步入高三,学习紧迫,遂不再进行功能开发。
-
-项目差不多就停更了,最多也就是每个星期看看issue、tg群组,修一修bug
-
-**DEMO依旧能保持服务水平,不会因为个人问题在这段时间停止运行**
-
-如果一年后还有人在用,一定会继续
-
-## Third-Party
-- [telebot](https://gopkg.in/tucnak/telebot)
-- [mysql_driver](https://github.com/go-sql-driver/mysql)
-- [gjson](https://github.com/tidwall/gjson)
-- [cron](https://github.com/robfig/cron/)
-- [viper](https://github.com/spf13/viper)
-- [goreleaser](https://https://github.com/goreleaser/goreleaser)
## License
diff --git a/bots/bots.go b/bots/bots.go
new file mode 100644
index 0000000..ecb6682
--- /dev/null
+++ b/bots/bots.go
@@ -0,0 +1,96 @@
+package bots
+
+import (
+ "fmt"
+ "github.com/iyear/E5SubBot/config"
+ "github.com/iyear/E5SubBot/logger"
+ "github.com/iyear/E5SubBot/model"
+ "go.uber.org/zap"
+ "golang.org/x/net/proxy"
+ tb "gopkg.in/tucnak/telebot.v2"
+ "net/http"
+ "strconv"
+ "time"
+)
+
+var (
+ bot *tb.Bot
+)
+
+const (
+ logo = `
+ ______ _____ _____ _ ____ _
+ | ____| ____/ ____| | | | _ \ | |
+ | |__ | |__| (___ _ _| |__ | |_) | ___ | |_
+ | __| |___ \\___ \| | | | '_ \| _ < / _ \| __|
+ | |____ ___) |___) | |_| | |_) | |_) | (_) | |_
+ |______|____/_____/ \__,_|_.__/|____/ \___/ \__|
+`
+)
+
+func BotStart() {
+ var err error
+ fmt.Printf("%s\n", logo)
+ //read config
+ config.InitConfig()
+ //Init Logger
+ logger.InitLogger()
+ //InitDB
+ model.InitDB()
+ //Init Task
+ InitTask()
+
+ Poller := &tb.LongPoller{Timeout: 15 * time.Second}
+ spamPoller := tb.NewMiddlewarePoller(Poller, func(upd *tb.Update) bool {
+ if upd.Message == nil {
+ return true
+ }
+ if !upd.Message.Private() {
+ return false
+ }
+ return true
+ })
+ botSetting := tb.Settings{
+ Token: config.BotToken,
+ Poller: spamPoller,
+ }
+ //set socks5
+ if config.Socks5 != "" {
+ fmt.Println("Proxy: " + config.Socks5)
+ dialer, err := proxy.SOCKS5("tcp", config.Socks5, nil, proxy.Direct)
+ if err != nil {
+ zap.S().Errorw("failed to get dialer",
+ "error", err, "proxy", config.Socks5)
+ }
+ httpTransport := &http.Transport{}
+ httpTransport.Dial = dialer.Dial
+ httpClient := &http.Client{Transport: httpTransport}
+
+ botSetting.Client = httpClient
+ }
+ //create bot
+ bot, err = tb.NewBot(botSetting)
+ if err != nil {
+ zap.S().Errorw("failed to create bot", "error", err)
+ return
+ }
+ fmt.Println("Bot: " + strconv.Itoa(bot.Me.ID) + " " + bot.Me.Username)
+
+ MakeHandle()
+ fmt.Println("Bot Start")
+ fmt.Println("------------")
+ bot.Start()
+}
+func MakeHandle() {
+ //所有用户
+ bot.Handle("/start", bStart)
+ bot.Handle("/my", bMy)
+ bot.Handle("/bind", bBind1)
+ bot.Handle("/unbind", bUnBind)
+ bot.Handle("/export", bExport)
+ bot.Handle("/help", bHelp)
+ bot.Handle(tb.OnText, bOnText)
+ //管理员
+ bot.Handle("/task", bTask)
+ bot.Handle("/log", bLog)
+}
diff --git a/bots/control.go b/bots/control.go
new file mode 100644
index 0000000..5178fc6
--- /dev/null
+++ b/bots/control.go
@@ -0,0 +1,73 @@
+package bots
+
+import (
+ "errors"
+ "fmt"
+ "github.com/iyear/E5SubBot/model"
+ "github.com/iyear/E5SubBot/util"
+ "github.com/tidwall/gjson"
+ tb "gopkg.in/tucnak/telebot.v2"
+ "strings"
+)
+
+// BindUser If Successfully return "",else return error information
+func BindUser(m *tb.Message, ClientId, ClientSecret string) error {
+ tmp := strings.Split(m.Text, " ")
+ if len(tmp) != 2 {
+ return errors.New("wrong format")
+ }
+ code := util.GetURLValue(tmp[0], "code")
+ Alias := tmp[1]
+ cli := model.NewClient(ClientId, ClientSecret)
+ if err := cli.GetTokenWithCode(code); err != nil {
+ return err
+ }
+ bot.Send(m.Chat, "Token获取成功!")
+
+ info, err := cli.GetUserInfo()
+ if err != nil {
+ return err
+ }
+ var u = &model.Client{
+ TgId: m.Chat.ID,
+ RefreshToken: cli.RefreshToken,
+ MsId: util.Get16MD5Encode(gjson.Get(info, "id").String()),
+ Alias: Alias,
+ ClientId: ClientId,
+ ClientSecret: ClientSecret,
+ Other: "",
+ }
+
+ //MS User Is Exist
+ if MSAppIsExist(u.TgId, u.ClientId) {
+ return errors.New("该应用已经绑定过了,无需重复绑定")
+ }
+ //MS information has gotten
+ bot.Send(m.Chat,
+ fmt.Sprintf("ms_id(MD5):%s\nuserPrincipalName:%s\ndisplayName:%s",
+ u.MsId,
+ gjson.Get(info, "userPrincipalName").String(),
+ gjson.Get(info, "displayName").String(),
+ ),
+ )
+
+ if result := model.DB.Create(&u); result.Error != nil {
+ return result.Error
+ }
+ return nil
+}
+
+// GetBindNum get bind num
+func GetBindNum(TgId int64) int {
+ var bindings []*model.Client
+ result := model.DB.Where("tg_id = ?", TgId).Find(&bindings)
+ return int(result.RowsAffected)
+}
+
+// MSAppIsExist return true => exist
+func MSAppIsExist(TgId int64, ClientId string) bool {
+ result := model.DB.
+ Where("tg_id = ? AND client_id = ?", TgId, ClientId).
+ First(&model.Client{})
+ return util.IF(result.RowsAffected == 0, false, true).(bool)
+}
diff --git a/bots/handle.go b/bots/handle.go
new file mode 100644
index 0000000..c068981
--- /dev/null
+++ b/bots/handle.go
@@ -0,0 +1,280 @@
+package bots
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/iyear/E5SubBot/config"
+ "github.com/iyear/E5SubBot/model"
+ "github.com/iyear/E5SubBot/util"
+ "go.uber.org/zap"
+ tb "gopkg.in/tucnak/telebot.v2"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var (
+ UserStatus map[int64]int
+ UserClientId map[int64]string
+ UserClientSecret map[int64]string
+)
+
+const (
+ USNone = iota
+ USBind1
+ USBind2
+)
+
+func init() {
+ UserStatus = make(map[int64]int)
+ UserClientId = make(map[int64]string)
+ UserClientSecret = make(map[int64]string)
+}
+
+func bStart(m *tb.Message) {
+ bot.Send(m.Sender, config.WelcomeContent)
+ bHelp(m)
+}
+
+func bMy(m *tb.Message) {
+ var data []*model.Client
+ model.DB.Where("tg_id = ?", m.Chat.ID).Find(&data)
+ var inlineKeys [][]tb.InlineButton
+ for _, u := range data {
+ inlineBtn := tb.InlineButton{
+ Unique: "my" + strconv.Itoa(u.ID),
+ Text: u.Alias,
+ Data: strconv.Itoa(u.ID),
+ }
+ bot.Handle(&inlineBtn, bMyInlineBtn)
+ inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
+ }
+
+ bot.Send(m.Chat,
+ fmt.Sprintf("选择一个账户查看具体信息\n\n绑定数: %d/%d", GetBindNum(m.Chat.ID), config.BindMaxNum),
+ &tb.ReplyMarkup{InlineKeyboard: inlineKeys})
+}
+func bMyInlineBtn(c *tb.Callback) {
+ var u *model.Client
+ model.DB.Where("id = ?", c.Data).First(&u)
+ bot.Send(c.Message.Chat,
+ fmt.Sprintf("信息\n别名:%s\nms_id: %s\nclient_id: %s\nclient_secret: %s\n最近更新时间: %s",
+ u.Alias,
+ u.MsId,
+ u.ClientId,
+ u.ClientSecret,
+ time.Unix(u.Uptime, 0).Format("2006-01-02 15:04:05"),
+ ),
+ )
+ bot.Respond(c)
+}
+
+func bBind1(m *tb.Message) {
+ bot.Send(m.Chat,
+ fmt.Sprintf("应用注册: [点击直达](%s)", model.GetMSRegisterAppUrl()),
+ tb.ModeMarkdown,
+ )
+
+ bot.Send(m.Chat,
+ "请回复 `client_id(空格)client_secret`",
+ &tb.SendOptions{ParseMode: tb.ModeMarkdown,
+ ReplyMarkup: &tb.ReplyMarkup{ForceReply: true}},
+ )
+
+ UserStatus[m.Chat.ID] = USBind1
+ UserClientId[m.Chat.ID] = m.Text
+}
+func bBind2(m *tb.Message) {
+ tmp := strings.Split(m.Text, " ")
+ if len(tmp) != 2 {
+ bot.Send(m.Chat, "错误的格式")
+ return
+ }
+ ClientId := tmp[0]
+ ClientSecret := tmp[1]
+ bot.Send(m.Chat,
+ "授权账户: [点击直达]("+model.GetMSAuthUrl(ClientId)+")",
+ tb.ModeMarkdown,
+ )
+
+ bot.Send(m.Chat,
+ "请回复`http://localhost/……(空格)别名`(用于管理)",
+ &tb.SendOptions{ParseMode: tb.ModeMarkdown,
+ ReplyMarkup: &tb.ReplyMarkup{ForceReply: true},
+ },
+ )
+
+ UserStatus[m.Chat.ID] = USBind2
+ UserClientId[m.Chat.ID] = ClientId
+ UserClientSecret[m.Chat.ID] = ClientSecret
+}
+
+func bUnBind(m *tb.Message) {
+ var data []*model.Client
+ model.DB.Where("tg_id = ?", m.Chat.ID).Find(&data)
+ var inlineKeys [][]tb.InlineButton
+
+ for _, u := range data {
+ inlineBtn := tb.InlineButton{
+ Unique: "unbind" + strconv.Itoa(u.ID),
+ Text: u.Alias,
+ Data: strconv.Itoa(u.ID),
+ }
+ bot.Handle(&inlineBtn, bUnBindInlineBtn)
+ inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
+ }
+
+ bot.Send(m.Chat,
+ fmt.Sprintf("选择一个账户将其解绑\n\n当前绑定数: %d/%d", GetBindNum(m.Chat.ID), config.BindMaxNum),
+ &tb.ReplyMarkup{InlineKeyboard: inlineKeys},
+ )
+}
+func bUnBindInlineBtn(c *tb.Callback) {
+ if result := model.DB.Where("id = ?", c.Data).Delete(&model.Client{}); result.Error != nil {
+ zap.S().Errorw("failed to delete db data",
+ "error", result.Error,
+ "id", c.Data,
+ )
+ bot.Send(c.Message.Chat, "解绑失败!")
+ return
+ }
+ bot.Send(c.Message.Chat, "解绑成功!")
+ bot.Respond(c)
+}
+func bExport(m *tb.Message) {
+ type ClientExport struct {
+ Alias string
+ ClientId string
+ ClientSecret string
+ RefreshToken string
+ Other string
+ }
+ var exports []ClientExport
+ var data []*model.Client
+ model.DB.Where("tg_id = ?", m.Chat.ID).Find(&data)
+ if len(data) == 0 {
+ bot.Send(m.Chat, "你还没有绑定过账户嗷~")
+ return
+ }
+ for _, u := range data {
+ var cExport = ClientExport{
+ Alias: u.Alias,
+ ClientId: u.ClientId,
+ ClientSecret: u.ClientSecret,
+ RefreshToken: u.RefreshToken,
+ Other: u.Other,
+ }
+ exports = append(exports, cExport)
+ }
+ export, err := json.MarshalIndent(exports, "", "\t")
+ if err != nil {
+ zap.S().Errorw("failed to marshal json",
+ "error", err)
+ bot.Send(m.Chat, fmt.Sprintf("获取JSON失败!\n\nERROR: %s", err.Error()))
+ return
+ }
+ fileName := fmt.Sprintf("./%d_export_tmp.json", m.Chat.ID)
+ if err = ioutil.WriteFile(fileName, export, 0644); err != nil {
+ zap.S().Errorw("failed to write file",
+ "error", err)
+ bot.Send(m.Chat, "写入临时文件失败~\n"+err.Error())
+ return
+ }
+ exportFile := &tb.Document{
+ File: tb.FromDisk(fileName),
+ FileName: strconv.FormatInt(m.Chat.ID, 10) + ".json",
+ MIME: "text/plain",
+ }
+ bot.Send(m.Chat, exportFile)
+
+ if exportFile.InCloud() != true || os.Remove(fileName) != nil {
+ zap.S().Errorw("failed to export files")
+ }
+}
+func bHelp(m *tb.Message) {
+ bot.Send(
+ m.Sender,
+ config.HelpContent+"\n"+config.Notice,
+ &tb.SendOptions{DisableWebPagePreview: false})
+}
+func bOnText(m *tb.Message) {
+ switch UserStatus[m.Chat.ID] {
+ case USNone:
+ {
+ bot.Send(m.Chat, "发送 /help 获取帮助嗷")
+ return
+ }
+ case USBind1:
+ {
+ if !m.IsReply() {
+ bot.Send(m.Chat, "请通过回复方式绑定")
+ return
+ }
+ bBind2(m)
+ }
+ case USBind2:
+ {
+ if !m.IsReply() {
+ bot.Send(m.Chat, "请通过回复方式绑定")
+ return
+ }
+ if GetBindNum(m.Chat.ID) == config.BindMaxNum {
+ bot.Send(m.Chat, "已经达到最大可绑定数")
+ return
+ }
+ bot.Send(m.Chat, "正在绑定中……")
+ err := BindUser(m, UserClientId[m.Chat.ID], UserClientSecret[m.Chat.ID])
+ if err != nil {
+ bot.Send(m.Chat, err.Error())
+ } else {
+ bot.Send(m.Chat, "绑定成功!")
+ }
+ UserStatus[m.Chat.ID] = USNone
+ }
+ }
+}
+func bTask(m *tb.Message) {
+ for _, a := range config.Admins {
+ if a == m.Chat.ID {
+ SignTask()
+ return
+ }
+ }
+ bot.Send(m.Chat, "只有Bot管理员才有权限执行此操作")
+}
+func bLog(m *tb.Message) {
+ flag := 0
+ for _, a := range config.Admins {
+ if a == m.Chat.ID {
+ flag = 1
+ }
+ }
+ if flag == 0 {
+ bot.Send(m.Chat, "只有Bot管理员才有权限执行此操作")
+ return
+ }
+ logs := util.GetRecentLogs(config.LogBasePath, 5)
+ var inlineKeys [][]tb.InlineButton
+ for _, log := range logs {
+ inlineBtn := tb.InlineButton{
+ Unique: "log" + strings.Replace(strings.TrimSuffix(filepath.Base(log), ".log"), "-", "", -1),
+ Text: filepath.Base(log),
+ Data: filepath.Base(log),
+ }
+ bot.Handle(&inlineBtn, bLogsInlineBtn)
+ inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
+ }
+ bot.Send(m.Chat, "请选择日志", &tb.ReplyMarkup{InlineKeyboard: inlineKeys})
+}
+func bLogsInlineBtn(c *tb.Callback) {
+ logfile := &tb.Document{
+ File: tb.FromDisk(config.LogBasePath + c.Data),
+ FileName: c.Data,
+ MIME: "text/plain",
+ }
+ bot.Send(c.Message.Chat, logfile)
+ bot.Respond(c)
+}
diff --git a/bots/sender.go b/bots/sender.go
new file mode 100644
index 0000000..8dcc8ed
--- /dev/null
+++ b/bots/sender.go
@@ -0,0 +1,86 @@
+package bots
+
+import (
+ "fmt"
+ "go.uber.org/zap"
+ "gopkg.in/tucnak/telebot.v2"
+ "strconv"
+ "sync"
+)
+
+type Sender struct {
+ done chan struct{}
+ in chan *Msg
+ wg sync.WaitGroup
+}
+type Msg struct {
+ To telebot.Recipient
+ What interface{}
+ Options []interface{}
+}
+
+//牺牲错误处理和解耦,加快发送速度
+
+func NewSender() *Sender {
+ return &Sender{}
+}
+func (s *Sender) Init(goroutine int) {
+
+ s.done = make(chan struct{})
+ s.in = make(chan *Msg)
+
+ for i := 0; i < goroutine; i++ {
+ go func() {
+ s.sender()
+ }()
+ }
+}
+
+//Stop until all messages were sent
+func (s *Sender) Stop() {
+ s.wg.Wait()
+ close(s.done)
+ fmt.Println("task finished")
+}
+func (s *Sender) SendMessageByID(ID int64, what interface{}, options ...interface{}) {
+ s.wg.Add(1)
+ go func() {
+ chat, err := bot.ChatByID(strconv.FormatInt(ID, 10))
+ if err != nil {
+ zap.S().Errorw("failed to get chat",
+ "error", err,
+ "id", ID,
+ )
+ s.wg.Done()
+ return
+ }
+ s.SendMessage(chat, what, options...)
+ }()
+}
+func (s *Sender) SendMessage(to telebot.Recipient, what interface{}, options ...interface{}) {
+ s.in <- &Msg{
+ To: to,
+ What: what,
+ Options: options,
+ }
+}
+func (s *Sender) sender() {
+ for {
+ select {
+ case msg, f := <-s.in:
+ if !f {
+ continue
+ }
+ fmt.Println("send to " + msg.To.Recipient())
+ if _, err := bot.Send(msg.To, msg.What, msg.Options...); err != nil {
+ zap.S().Errorw("failed to send msg",
+ "error", err,
+ "id", msg.To.Recipient(),
+ )
+ }
+ s.wg.Done()
+ case <-s.done:
+ return
+ }
+ }
+}
diff --git a/bots/task.go b/bots/task.go
new file mode 100644
index 0000000..fb655bc
--- /dev/null
+++ b/bots/task.go
@@ -0,0 +1,148 @@
+package bots
+
+import (
+ "fmt"
+ "github.com/iyear/E5SubBot/config"
+ "github.com/iyear/E5SubBot/model"
+ "github.com/iyear/E5SubBot/task"
+ "github.com/robfig/cron/v3"
+ "go.uber.org/zap"
+ tb "gopkg.in/tucnak/telebot.v2"
+ "strconv"
+ "time"
+)
+
+var errorTimes map[int]int
+var signErr map[int64]int
+var unbindUsers []int64
+var msgSender *Sender
+
+func InitTask() {
+ errorTimes = make(map[int]int)
+ msgSender = NewSender()
+
+ c := cron.New()
+ c.AddFunc(config.Cron, SignTask)
+ c.Start()
+}
+func SignTask() {
+ msgSender.Init(config.MaxGoroutines)
+
+ signErr = make(map[int64]int)
+ unbindUsers = nil
+
+ var clients []*model.Client
+ if result := model.DB.Find(&clients); result.Error != nil {
+ zap.S().Errorw("failed to get all clients",
+ "error", result.Error)
+ return
+ }
+
+ fmt.Printf("clients: %d goroutines:%d\n",
+ len(clients),
+ config.MaxGoroutines,
+ )
+
+ start := time.Now()
+
+ errClients := task.Sign(clients)
+
+ for _, errClient := range errClients {
+ if errClient.Err != nil {
+ opErrorSign(errClient)
+ continue
+ }
+ //请求一次成功清零errorTimes,避免接口的偶然错误积累导致账号被清退
+ errorTimes[errClient.ID] = 0
+ model.DB.Save(&errClient.Client)
+ }
+
+ //fmt.Println(signErr)
+ //fmt.Println(errorTimes)
+ timeSpending := time.Since(start).Seconds()
+ summarySignTaskForUsers(errClients)
+ summarySignTaskForAdmins(errClients, timeSpending)
+
+ msgSender.Stop()
+}
+
+func summarySignTaskForAdmins(errClients []*model.ErrClient, timeSpending float64) {
+ var Count = len(errClients)
+ var ErrCount int
+ var ErrUserStr string
+ var UnbindUserStr string
+ for err, count := range signErr {
+ ErrCount += count
+ ErrUserStr += fmt.Sprintf("[%d](tg://user?id=%d)\n", err, err)
+ }
+ for _, unbindUser := range unbindUsers {
+ UnbindUserStr += fmt.Sprintf("[%d](tg://user?id=%d)\n", unbindUser, unbindUser)
+ }
+ for _, admin := range config.Admins {
+ a := admin
+ msgSender.SendMessageByID(a, fmt.Sprintf("任务反馈(管理员)\n完成时间: %s\n用时: %.2fs\n结果: %d/%d\n错误账户: \n%s\n清退账户: \n%s",
+ time.Now().Format("2006-01-02 15:04:05"),
+ timeSpending,
+ Count-ErrCount, Count,
+ ErrUserStr, UnbindUserStr,
+ ),
+ tb.ModeMarkdown,
+ )
+ }
+}
+func summarySignTaskForUsers(errClients []*model.ErrClient) {
+
+ var isSent map[int64]bool
+ isSent = make(map[int64]bool)
+
+ for _, errClient := range errClients {
+ errClient := errClient
+ //pending SignErrNum
+ if errorTimes[errClient.ID] > config.MaxErrTimes {
+ if result := model.DB.Delete(&errClient.Client); result.Error != nil {
+ zap.S().Errorw("failed to delete data",
+ "error", result.Error,
+ "id", errClient.ID,
+ )
+ continue
+ }
+
+ unbindUsers = append(unbindUsers, errClient.TgId)
+
+ msgSender.SendMessageByID(errClient.TgId, fmt.Sprintf("您的账户因达到错误上限而被自动解绑\n后会有期!\n\n别名: %s\nclient_id: %s\nclient_secret: %s",
+ errClient.Alias,
+ errClient.ClientId,
+ errClient.ClientSecret,
+ ))
+ continue
+
+ }
+ if isSent[errClient.TgId] {
+ continue
+ }
+ signOK := GetBindNum(errClient.TgId) - signErr[errClient.TgId]
+
+ msgSender.SendMessageByID(errClient.TgId,
+ fmt.Sprintf("任务反馈\n时间: %s\n结果:%d/%d",
+ time.Now().Format("2006-01-02 15:04:05"),
+ signOK,
+ signErr[errClient.TgId]+signOK,
+ ),
+ )
+ isSent[errClient.TgId] = true
+ time.Sleep(time.Millisecond * 100)
+ }
+}
+func opErrorSign(errClient *model.ErrClient) {
+ errorTimes[errClient.ID]++
+ signErr[errClient.TgId]++
+
+ UnBindBtn := tb.InlineButton{Unique: "un" + errClient.MsId, Text: "点击解绑", Data: strconv.Itoa(errClient.ID)}
+ bot.Handle(&UnBindBtn, bUnBindInlineBtn)
+
+ msgSender.SendMessageByID(errClient.TgId,
+ fmt.Sprintf("您的帐户 %s 在执行时出现了错误\n您可以选择解绑该用户\n错误: %s",
+ errClient.Alias, errClient.Err),
+ &tb.ReplyMarkup{InlineKeyboard: [][]tb.InlineButton{{UnBindBtn}}},
+ )
+}
diff --git a/config.yml.example b/config.yml.example
index c9fdae3..167d375 100644
--- a/config.yml.example
+++ b/config.yml.example
@@ -2,6 +2,7 @@ bot_token: xxx
#socks5: 127.0.0.1:1080
bindmax: 3
admin: 555,666,777
+goroutine: 10
errlimit: 5
notice: "aaa\nbbb"
cron: "1 */3 * * *"
@@ -11,4 +12,5 @@ mysql:
port: 3306
user: root
password: root
- database: e5sub
\ No newline at end of file
+ database: e5sub
+ table: users
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..b0a4e9b
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,93 @@
+package config
+
+import (
+ "github.com/fsnotify/fsnotify"
+ "github.com/spf13/viper"
+ "go.uber.org/zap"
+ "strconv"
+ "strings"
+)
+
+const (
+ LogBasePath string = "./log/"
+ WelcomeContent string = "欢迎使用E5SubBot!"
+ HelpContent string = `
+ 命令:
+ /my 查看已绑定账户信息
+ /bind 绑定新账户
+ /unbind 解绑账户
+ /export 导出账户信息(JSON)
+ /help 帮助
+ 源码及使用方法:https://github.com/iyear/E5SubBot
+`
+)
+
+var (
+ BotToken string
+ Socks5 string
+ BindMaxNum int
+ MaxGoroutines int
+ MaxErrTimes int
+ Cron string
+ Notice string
+ Admins []int64
+ Mysql MysqlConfig
+)
+
+type MysqlConfig struct {
+ Host string
+ Port int
+ User string
+ Password string
+ DB string
+ Table string
+}
+
+func InitConfig() {
+ viper.SetConfigName("config")
+ viper.AddConfigPath(".")
+ err := viper.ReadInConfig()
+ if err != nil {
+ zap.S().Errorw("failed to read config", "error", err)
+ }
+ BotToken = viper.GetString("bot_token")
+ Cron = viper.GetString("cron")
+ Socks5 = viper.GetString("socks5")
+
+ viper.SetDefault("errlimit", 5)
+ viper.SetDefault("bindmax", 5)
+ viper.SetDefault("goroutine", 10)
+
+ BindMaxNum = viper.GetInt("bindmax")
+ MaxErrTimes = viper.GetInt("errlimit")
+ Notice = viper.GetString("notice")
+
+ MaxGoroutines = viper.GetInt("goroutine")
+ Admins = getAdmins()
+
+ Mysql = MysqlConfig{
+ Host: viper.GetString("mysql.host"),
+ Port: viper.GetInt("mysql.port"),
+ User: viper.GetString("mysql.user"),
+ Password: viper.GetString("mysql.password"),
+ DB: viper.GetString("mysql.database"),
+ Table: viper.GetString("mysql.table"),
+ }
+ viper.WatchConfig()
+ viper.OnConfigChange(func(e fsnotify.Event) {
+ MaxGoroutines = viper.GetInt("goroutine")
+ BindMaxNum = viper.GetInt("bindmax")
+ MaxErrTimes = viper.GetInt("errlimit")
+ Notice = viper.GetString("notice")
+ Admins = getAdmins()
+ })
+}
+func getAdmins() []int64 {
+ var result []int64
+ admins := strings.Split(viper.GetString("admin"), ",")
+ for _, v := range admins {
+ id, _ := strconv.ParseInt(v, 10, 64)
+ result = append(result, id)
+ }
+ return result
+}
diff --git a/control.go b/control.go
deleted file mode 100644
index 2187998..0000000
--- a/control.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package main
-
-import (
- "errors"
- "fmt"
- "github.com/spf13/viper"
- "github.com/tidwall/gjson"
- tb "gopkg.in/tucnak/telebot.v2"
- "io"
- "log"
- "os"
- "strconv"
- "strings"
- "time"
-)
-
-var SignOk map[int64]int
-
-//If Successfully return "",else return error information
-func BindUser(m *tb.Message, cid, cse string) error {
- logger.Printf("%d Begin Bind\n", m.Chat.ID)
- tmp := strings.Split(m.Text, " ")
- if len(tmp) != 2 {
- logger.Printf("%d Bind error:Wrong Bind Format\n", m.Chat.ID)
- return errors.New("绑定格式错误")
- }
- logger.Println("alias: " + tmp[1])
- alias := tmp[1]
- code := GetURLValue(tmp[0], "code")
- //fmt.Println(code)
- access, refresh, err := MSFirGetToken(code, cid, cse)
- if err != nil {
- logger.Printf("%d Bind error:GetRefreshToken %s \n", m.Chat.ID, err.Error())
- return err
- }
-
- //token has gotten
- bot.Send(m.Chat, "Token获取成功!")
- info, err := MSGetUserInfo(access)
- //fmt.Printf("TGID:%d Refresh Token: %s\n", m.Chat.ID, refresh)
- if err != nil {
- logger.Printf("%d Bind error:Getinfo %s \n", m.Chat.ID, err.Error())
- return err
- }
-
- var u MSData
- u.tgId = m.Chat.ID
- u.refreshToken = refresh
- //TG的Data传递最高64bytes,一些msid超过了报错BUTTON_DATA_INVALID (0),采取md5
- u.msId = Get16MD5Encode(gjson.Get(info, "id").String())
- u.uptime = time.Now().Unix()
- logger.Println(u.uptime)
- u.alias = alias
- u.clientId = cid
- u.clientSecret = cse
- u.other = ""
- //MS User Is Exist
- if MSAppIsExist(u.tgId, u.clientId) {
- logger.Printf("%d Bind error:MSUserHasExisted\n", m.Chat.ID)
- return errors.New("该应用已经绑定过了,无需重复绑定")
- }
- //MS information has gotten
- bot.Send(m.Chat, "MS_ID(MD5): "+u.msId+"\nuserPrincipalName: "+gjson.Get(info, "userPrincipalName").String()+"\ndisplayName: "+gjson.Get(info, "displayName").String()+"\n")
- if ok, err := AddData(u); !ok {
- logger.Printf("%d Bind error: %s\n", m.Chat.ID, err)
- return err
- }
- logger.Printf("%d Bind Successfully!\n", m.Chat.ID)
- return nil
-}
-
-//get bind num
-func GetBindNum(tgId int64) int {
- data := QueryDataByTG(tgId)
- return len(data)
-}
-
-//return true => exist
-func MSAppIsExist(tgId int64, clientId string) bool {
- data := QueryDataByTG(tgId)
- var res MSData
- for _, res = range data {
- if res.clientId == clientId {
- return true
- }
- }
- return false
-}
-
-//SignTask
-func SignTask() {
- var (
- SignOk map[int64]int
- SignErr []string
- UnbindUser []string
- num, signOk int
- )
- SignOk = make(map[int64]int)
- fmt.Println("----Task Begin----")
- fmt.Println("Time:" + time.Now().Format("2006-01-02 15:04:05"))
- data := QueryDataAll()
- num = len(data)
- fmt.Println("Start Sign")
- //签到任务
- for _, u := range data {
- pre := "您的账户: " + u.alias + "\n在任务执行时出现了错误!\n错误:"
- chat, err := bot.ChatByID(strconv.FormatInt(u.tgId, 10))
- if err != nil {
- logger.Println(err)
- continue
- }
- //生成解绑按钮
- var inlineKeys [][]tb.InlineButton
- UnBindBtn := tb.InlineButton{Unique: "un" + u.msId, Text: "点击解绑该账户", Data: u.msId}
- bot.Handle(&UnBindBtn, bUnBindInlineBtn)
- inlineKeys = append(inlineKeys, []tb.InlineButton{UnBindBtn})
- tmpBtn := &tb.ReplyMarkup{InlineKeyboard: inlineKeys}
-
- se := u.msId + " ( @" + chat.Username + " )"
- access, newRefreshToken, err := MSGetToken(u.refreshToken, u.clientId, u.clientSecret)
-
- if err != nil {
- logger.Println(u.msId+" ", err)
- bot.Send(chat, pre+gjson.Get(err.Error(), "error").String(), tmpBtn)
- SignErr = append(SignErr, se)
- ErrorTimes[u.msId]++
- continue
- }
- if ok, err := OutLookGetMails(access); !ok {
- logger.Println(u.msId+" ", err)
- bot.Send(chat, pre+gjson.Get(err.Error(), "error").String(), tmpBtn)
- ErrorTimes[u.msId]++
- SignErr = append(SignErr, se)
- continue
- }
- u.uptime = time.Now().Unix()
- u.refreshToken = newRefreshToken
- if ok, err := UpdateData(u); !ok {
- logger.Println(u.msId+" ", err)
- bot.Send(chat, pre+err.Error(), tmpBtn)
- SignErr = append(SignErr, se)
- ErrorTimes[u.msId]++
- continue
- }
- fmt.Println(u.msId + " Sign OK!")
- SignOk[u.tgId]++
- signOk++
- }
- fmt.Println("Sign End,Start Send")
- var isSend map[int64]bool
- isSend = make(map[int64]bool)
- //用户任务反馈
- for _, u := range data {
- chat, err := bot.ChatByID(strconv.FormatInt(u.tgId, 10))
- if err != nil {
- logger.Println("Send Result ERROR: ", err)
- continue
- }
- //错误上限账户清退
- if ErrorTimes[u.msId] == ErrMaxTimes {
- logger.Println(u.msId + " Error Limit")
- if ok, err := DelData(u.msId); !ok {
- logger.Println(err)
- } else {
- UnbindUser = append(UnbindUser, u.msId+" ( @"+chat.Username+" )")
- _, err = bot.Send(chat, "您的账户因达到错误上限而被自动解绑\n后会有期!\n\n别名: "+u.alias+"\nclient_id: "+u.clientId+"\nclient_secret: "+u.clientSecret)
- if err != nil {
- logger.Println(err)
- }
- }
-
- }
- if !isSend[u.tgId] {
- //静默发送,过多消息很烦
- _, err = bot.Send(chat, "任务反馈\n时间: "+time.Now().Format("2006-01-02 15:04:05")+"\n结果: "+strconv.Itoa(SignOk[u.tgId])+"/"+strconv.Itoa(GetBindNum(u.tgId)), &tb.SendOptions{DisableNotification: true})
- if err != nil {
- logger.Println(err)
- }
- isSend[u.tgId] = true
- }
- }
- //管理员任务反馈
- var ErrUserStr string
- var UnbindUserStr string
- for _, eu := range SignErr {
- ErrUserStr = ErrUserStr + eu + "\n"
- }
- for _, ubu := range UnbindUser {
- UnbindUserStr = UnbindUserStr + ubu + "\n"
- }
- for _, a := range admin {
- chat, err := bot.ChatByID(strconv.FormatInt(a, 10))
- if err != nil {
- logger.Println(err)
- continue
- }
- bot.Send(chat, "任务反馈(管理员)\n完成时间: "+time.Now().Format("2006-01-02 15:04:05")+"\n结果: "+strconv.Itoa(signOk)+"/"+strconv.Itoa(num)+"\n错误账户:\n"+ErrUserStr+"\n清退账户:\n"+UnbindUserStr)
- }
- fmt.Println("----Task End----")
-}
-func GetAdmin() []int64 {
- var result []int64
- admins := strings.Split(viper.GetString("admin"), ",")
- for _, v := range admins {
- id, _ := strconv.ParseInt(v, 10, 64)
- result = append(result, id)
- }
- return result
-}
-func InitLogger() {
- if !PathExists(bLogBasePath) {
- os.Mkdir(bLogBasePath, 0773)
- }
-
- path := bLogBasePath + time.Now().Format("2006-01-02") + ".log"
- f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0773)
- if err != nil {
- logger.Println(err)
- }
- writers := []io.Writer{
- f,
- os.Stdout}
- faoWriter := io.MultiWriter(writers...)
- logger = log.New(faoWriter, "【E5Sub】", log.Ldate|log.Ltime|log.Lshortfile)
-}
diff --git a/go.mod b/go.mod
index 772d1bd..52b4f31 100644
--- a/go.mod
+++ b/go.mod
@@ -1,14 +1,19 @@
-module main
+module github.com/iyear/E5SubBot
go 1.13
require (
github.com/fsnotify/fsnotify v1.4.7
- github.com/go-sql-driver/mysql v1.5.0
+ github.com/guonaihong/gout v0.1.12
+ github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.0
github.com/spf13/viper v1.6.2
github.com/tidwall/gjson v1.6.0
github.com/tidwall/pretty v1.0.1 // indirect
+ go.uber.org/zap v1.10.0
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/tucnak/telebot.v2 v2.0.0-20200328014118-dd123e949ee1
+ gorm.io/driver/mysql v1.1.1
+ gorm.io/gorm v1.21.9
)
diff --git a/go.sum b/go.sum
index c334d46..e511291 100644
--- a/go.sum
+++ b/go.sum
@@ -1,60 +1,14 @@
-bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-code.gitea.io/sdk/gitea v0.0.0-20200128061546-ea2b5af37297 h1:2jEu+HD5Hk87b8HiztAoTo+bSkerjFik3mU5z8sJycg=
-code.gitea.io/sdk/gitea v0.0.0-20200128061546-ea2b5af37297/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
-contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
-contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
-contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
-contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
-github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
-github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
-github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
-github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
-github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
-github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
-github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
+github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
+github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
-github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
-github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
-github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
-github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
-github.com/caarlos0/ctrlc v1.0.0 h1:2DtF8GSIcajgffDFJzyG15vO+1PuBWOMUdFut7NnXhw=
-github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
-github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e h1:V9a67dfYqPLAvzk5hMQOXYJlZ4SLIXgyKIE+ZiHzgGQ=
-github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
-github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
-github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
-github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -63,140 +17,109 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
-github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.0 h1:Lb3veSYoGaNck69fV2+Vf2juLSsHpMTf3Vk5+X+EDJg=
+github.com/gin-gonic/gin v1.6.0/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
-github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
+github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
-github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
-github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbCZ+KyX/OB4Ks9/MNMhWjqPPkZxsE=
-github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
-github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
-github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/goreleaser/goreleaser v0.129.0 h1:hJHsgxxNck3oe/MU4K+yznH2qgAVGNcxARTRP8ODSRk=
-github.com/goreleaser/goreleaser v0.129.0/go.mod h1:tzHV+xcU9AJMYNby0QS0lHm1DRrTCBkJXW9ic2reepY=
-github.com/goreleaser/nfpm v1.2.1 h1:AEnu9XVmupRDTR930Z2rAs31Mj6sLIPxFcR9ESYvgDA=
-github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
-github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
-github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/guonaihong/gout v0.1.12 h1:PE2nJofrLpxpoHOTJ4NWcyPBU6gbjWjp5Du3DMsAv2Y=
+github.com/guonaihong/gout v0.1.12/go.mod h1:JkjNv1G2oRWvFgP/r4DUbYhoeIBB0zMP2j1ID+5CYpU=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
-github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
-github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
+github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kamilsk/retry/v4 v4.7.2 h1:8C33aqTQtTSvPf7MpLZ4xSY4JZK2YCvY+hTlsbiHNq8=
-github.com/kamilsk/retry/v4 v4.7.2/go.mod h1:pIQtBtycHTXScrJmpu3N2SSBT07s07Uruq2Au1aRLks=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
-github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
-github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
-github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
+github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
-github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
-github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615 h1:/mD+ABZyXD39BzJI2XyRJlqdZG11gXFo0SSynL+OFeU=
+github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
@@ -207,20 +130,18 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
-github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
+github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
+github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
@@ -235,184 +156,99 @@ github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/techoner/gophp v0.2.0 h1:vVFoS2XC/NZariagUOhAGqC8p/Ws8R8ARYpj/9lTIAo=
-github.com/techoner/gophp v0.2.0/go.mod h1:NOxB/qoTl4+G82CkQp60T18Az5rOTufCqm83x9i/u7M=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
-github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
-github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
-github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
-github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
-github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
-github.com/xanzy/go-gitlab v0.28.0 h1:nsyjDVvBrP4KRXEN4b1m1ewiqmTNL4BOWW041nKGV7k=
-github.com/xanzy/go-gitlab v0.28.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
-github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-gocloud.dev v0.19.0 h1:EDRyaRAnMGSq/QBto486gWFxMLczAfIYUmusV7XLNBM=
-gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
-golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/tucnak/telebot.v2 v2.0.0-20200301001213-9852df39ae6c h1:+7l/yyky9hchNME0hDMl+cA+wVlHQGte/5EwKHeSqoc=
-gopkg.in/tucnak/telebot.v2 v2.0.0-20200301001213-9852df39ae6c/go.mod h1:+//wyPtHTeW2kfyEBwB05Hqnxev7AGrsLIyylSH++KU=
gopkg.in/tucnak/telebot.v2 v2.0.0-20200328014118-dd123e949ee1 h1:L9z5n/aaxpAjeEMHu4PfZ7GgQa0lRirKW5G+LHuNQDU=
gopkg.in/tucnak/telebot.v2 v2.0.0-20200328014118-dd123e949ee1/go.mod h1:+//wyPtHTeW2kfyEBwB05Hqnxev7AGrsLIyylSH++KU=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gorm.io/driver/mysql v1.1.1 h1:yr1bpyqiwuSPJ4aGGUX9nu46RHXlF8RASQVb1QQNcvo=
+gorm.io/driver/mysql v1.1.1/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU=
+gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
+gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
+gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.21.9 h1:INieZtn4P2Pw6xPJ8MzT0G4WUOsHq3RhfuDF1M6GW0E=
+gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/handle.go b/handle.go
deleted file mode 100644
index f8b111b..0000000
--- a/handle.go
+++ /dev/null
@@ -1,308 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "github.com/fsnotify/fsnotify"
- "github.com/spf13/viper"
- tb "gopkg.in/tucnak/telebot.v2"
- "io/ioutil"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-)
-
-const (
- bLogBasePath string = "./log/"
- bStartContent string = "欢迎使用E5SubBot!"
- bHelpContent string = `
- 命令:
- /my 查看已绑定账户信息
- /bind 绑定新账户
- /unbind 解绑账户
- /export 导出账户信息(JSON格式)
- /help 帮助
- /task 手动执行一次任务(管理员)
- /log 获取最近日志文件(管理员)
- 源码及使用方法:https://github.com/iyear/E5SubBot
-`
-)
-
-var (
- UserStatus map[int64]int
- UserCid map[int64]string
- UserCSecret map[int64]string
- ErrorTimes map[string]int //错误次数
- BindMaxNum int
- ErrMaxTimes int
- notice string
- admin []int64
-)
-
-const (
- USNone = iota
- USBind1
- USBind2
-)
-
-func init() {
- //read config
- viper.SetConfigName("config")
- viper.AddConfigPath(".")
- err := viper.ReadInConfig()
- CheckErr(err)
-
- viper.SetDefault("errlimit", 5)
- viper.SetDefault("bindmax", 5)
-
- BindMaxNum = viper.GetInt("bindmax")
- ErrMaxTimes = viper.GetInt("errlimit")
- notice = viper.GetString("notice")
- admin = GetAdmin()
-
- viper.WatchConfig()
- viper.OnConfigChange(func(e fsnotify.Event) {
- BindMaxNum = viper.GetInt("bindmax")
- ErrMaxTimes = viper.GetInt("errlimit")
- notice = viper.GetString("notice")
- admin = GetAdmin()
- })
-
- UserStatus = make(map[int64]int)
- UserCid = make(map[int64]string)
- UserCSecret = make(map[int64]string)
- ErrorTimes = make(map[string]int)
-}
-
-func bStart(m *tb.Message) {
- bot.Send(m.Sender, bStartContent)
- bHelp(m)
-}
-
-func bMy(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Manager Users")
- data := QueryDataByTG(m.Chat.ID)
- var inlineKeys [][]tb.InlineButton
- for _, u := range data {
- inlineBtn := tb.InlineButton{
- Unique: "my" + u.msId,
- Text: u.alias,
- Data: u.msId,
- }
- bot.Handle(&inlineBtn, bMyInlineBtn)
- inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
- }
- bot.Send(m.Chat, "选择一个账户查看具体信息\n\n绑定数: "+strconv.Itoa(GetBindNum(m.Chat.ID))+"/"+strconv.Itoa(BindMaxNum), &tb.ReplyMarkup{InlineKeyboard: inlineKeys})
-}
-func bMyInlineBtn(c *tb.Callback) {
- logger.Println(strconv.FormatInt(c.Message.Chat.ID, 10) + " Get User Info")
- r := QueryDataByMS(c.Data)
- u := r[0]
- bot.Send(c.Message.Chat, "信息\n别名:"+u.alias+"\nMS_ID(MD5): "+u.msId+"\nclient_id: "+u.clientId+"\nclient_secret: "+u.clientSecret+"\n最近更新时间: "+time.Unix(u.uptime, 0).Format("2006-01-02 15:04:05"))
- bot.Respond(c)
-}
-
-func bBind1(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Bind")
- logger.Println("ReApp: " + strconv.FormatInt(m.Chat.ID, 10))
- bot.Send(m.Chat, "应用注册: [点击直达]("+MSGetReAppUrl()+")", tb.ModeMarkdown)
- _, err := bot.Send(m.Chat, "请回复client_id+空格+client_secret", &tb.ReplyMarkup{ForceReply: true})
- if err != nil {
- logger.Println(err)
- return
- }
- UserStatus[m.Chat.ID] = USBind1
- UserCid[m.Chat.ID] = m.Text
-}
-func bBind2(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Bind2")
- logger.Println("Auth: " + strconv.FormatInt(m.Chat.ID, 10))
- tmp := strings.Split(m.Text, " ")
- if len(tmp) != 2 {
- logger.Printf("%d Bind error:Wrong Bind Format\n", m.Chat.ID)
- bot.Send(m.Chat, "错误的格式")
- return
- }
- logger.Println("client_id: " + tmp[0] + " client_secret: " + tmp[1])
- cid := tmp[0]
- cse := tmp[1]
- bot.Send(m.Chat, "授权账户: [点击直达]("+MSGetAuthUrl(cid)+")", tb.ModeMarkdown)
- _, err := bot.Send(m.Chat, "请回复http://localhost/…… + 空格 + 别名(用于管理)", &tb.ReplyMarkup{ForceReply: true})
- if err != nil {
- logger.Println(err)
- return
- }
- UserStatus[m.Chat.ID] = USBind2
- UserCid[m.Chat.ID] = cid
- UserCSecret[m.Chat.ID] = cse
-}
-
-func bUnBind(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Unbind")
- data := QueryDataByTG(m.Chat.ID)
- var inlineKeys [][]tb.InlineButton
- for _, u := range data {
- inlineBtn := tb.InlineButton{
- Unique: "unbind" + u.msId,
- Text: u.alias,
- Data: u.msId,
- }
- bot.Handle(&inlineBtn, bUnBindInlineBtn)
- inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
- }
- bot.Send(m.Chat, "选择一个账户将其解绑\n\n当前绑定数: "+strconv.Itoa(GetBindNum(m.Chat.ID))+"/"+strconv.Itoa(BindMaxNum), &tb.ReplyMarkup{InlineKeyboard: inlineKeys})
-}
-func bUnBindInlineBtn(c *tb.Callback) {
- logger.Println(strconv.FormatInt(c.Message.Chat.ID, 10) + " Unbind: " + c.Data)
- r := QueryDataByMS(c.Data)
- u := r[0]
- if ok, _ := DelData(u.msId); !ok {
- logger.Println(u.msId + " UnBind ERROR")
- bot.Send(c.Message.Chat, "解绑失败!")
- return
- }
- logger.Println(u.msId + " UnBind Success")
- bot.Send(c.Message.Chat, "解绑成功!")
- bot.Respond(c)
-}
-func bExport(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Export")
- type MsMiniData struct {
- Alias string
- ClientId string
- ClientSecret string
- RefreshToken string
- Other string
- }
- var MsMini []MsMiniData
- data := QueryDataByTG(m.Chat.ID)
- if len(data) == 0 {
- bot.Send(m.Chat, "你还没有绑定过账户嗷~")
- return
- }
- for _, u := range data {
- var ms MsMiniData
- ms.RefreshToken = u.refreshToken
- ms.Alias = u.alias
- ms.ClientId = u.clientId
- ms.ClientSecret = u.clientSecret
- ms.Other = u.other
- MsMini = append(MsMini, ms)
- }
- //MarshalIndent是为json+美化,/t表缩进
- export, err := json.MarshalIndent(MsMini, "", "\t")
- if err != nil {
- logger.Println(err)
- bot.Send(m.Chat, "获取JSON失败~\n"+err.Error())
- return
- }
- //fmt.Println(string(export))
- fileName := "./" + strconv.FormatInt(m.Chat.ID, 10) + "_export_tmp.json"
- if err = ioutil.WriteFile(fileName, export, 0644); err != nil {
- logger.Println(err)
- bot.Send(m.Chat, "写入临时文件失败~\n"+err.Error())
- return
- }
- exportFile := &tb.Document{File: tb.FromDisk(fileName), FileName: strconv.FormatInt(m.Chat.ID, 10) + ".json", MIME: "text/plain"}
- _, err = bot.Send(m.Chat, exportFile)
- if err != nil {
- logger.Println(err)
- return
- }
- //不遗留本地文件
- if exportFile.InCloud() == true && os.Remove(fileName) == nil {
- logger.Println(fileName + " Has Removed")
- } else {
- logger.Println(fileName + " Removed ERROR")
- }
-}
-func bHelp(m *tb.Message) {
- bot.Send(m.Sender, bHelpContent+"\n"+notice, &tb.SendOptions{DisableWebPagePreview: false})
-}
-func bOnText(m *tb.Message) {
- switch UserStatus[m.Chat.ID] {
- case USNone:
- {
- bot.Send(m.Chat, "发送/help获取帮助嗷")
- return
- }
- case USBind1:
- {
- if !m.IsReply() {
- bot.Send(m.Chat, "请通过回复方式绑定")
- return
- }
- bBind2(m)
- }
- case USBind2:
- {
- if !m.IsReply() {
- bot.Send(m.Chat, "请通过回复方式绑定")
- return
- }
- if GetBindNum(m.Chat.ID) == BindMaxNum {
- bot.Send(m.Chat, "已经达到最大可绑定数")
- return
- }
- bot.Send(m.Chat, "正在绑定中……")
- err := BindUser(m, UserCid[m.Chat.ID], UserCSecret[m.Chat.ID])
- if err != nil {
- bot.Send(m.Chat, err.Error())
- } else {
- bot.Send(m.Chat, "绑定成功!")
- }
- UserStatus[m.Chat.ID] = USNone
- }
- }
-}
-func bTask(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start SignTask")
- for _, a := range admin {
- if a == m.Chat.ID {
- SignTask()
- return
- }
- }
- bot.Send(m.Chat, "您没有权限执行此操作~")
-}
-func bLog(m *tb.Message) {
- logger.Println(strconv.FormatInt(m.Chat.ID, 10) + " Start Get Logs")
- flag := 0
- for _, a := range admin {
- if a == m.Chat.ID {
- flag = 1
- }
- }
- if flag == 0 {
- bot.Send(m.Chat, "您没有权限执行此操作~")
- return
- }
- logs := GetRecentLogs(bLogBasePath, 5)
- var inlineKeys [][]tb.InlineButton
- for _, log := range logs {
- inlineBtn := tb.InlineButton{
- Unique: "log" + strings.Replace(strings.TrimSuffix(filepath.Base(log), ".log"), "-", "", -1),
- Text: filepath.Base(log),
- Data: filepath.Base(log),
- }
- bot.Handle(&inlineBtn, bLogsInlineBtn)
- inlineKeys = append(inlineKeys, []tb.InlineButton{inlineBtn})
- }
- _, err := bot.Send(m.Chat, "选择一个日志", &tb.ReplyMarkup{InlineKeyboard: inlineKeys})
- if err != nil {
- logger.Println(err)
- }
-}
-func bLogsInlineBtn(c *tb.Callback) {
- logger.Println(strconv.FormatInt(c.Message.Chat.ID, 10) + " Get Logs: " + c.Data)
- //fmt.Println(c.Data)
- //logger.Println(bLogBasePath + c.Data + ".log")
- logfile := &tb.Document{File: tb.FromDisk(bLogBasePath + c.Data), FileName: c.Data, MIME: "text/plain"}
- _, err := bot.Send(c.Message.Chat, logfile)
- if err != nil {
- logger.Println(err)
- return
- }
- bot.Respond(c)
-}
diff --git a/logger/logger.go b/logger/logger.go
new file mode 100644
index 0000000..66aabdc
--- /dev/null
+++ b/logger/logger.go
@@ -0,0 +1,37 @@
+package logger
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
+ "os"
+)
+
+// 日志切割设置
+func getLogWriter() zapcore.WriteSyncer {
+ lumberJackLogger := &lumberjack.Logger{
+ Filename: "./log/latest.log", // 日志文件位置
+ MaxSize: 1, // 日志文件最大大小(MB)
+ MaxBackups: 5, // 保留旧文件最大数量
+ MaxAge: 30, // 保留旧文件最长天数
+ Compress: true, // 是否压缩旧文件
+ }
+ return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(lumberJackLogger))
+}
+
+// 编码器
+func getEncoder() zapcore.Encoder {
+ // 使用默认的JSON编码
+ encoderConfig := zap.NewDevelopmentEncoderConfig()
+ encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
+ encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
+ return zapcore.NewJSONEncoder(encoderConfig)
+}
+
+// InitLogger 初始化Logger
+func InitLogger() {
+ writeSyncer := getLogWriter()
+ encoder := getEncoder()
+ core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
+ zap.ReplaceGlobals(zap.New(core, zap.AddCaller()))
+}
diff --git a/main.go b/main.go
index f8da15c..43a131a 100644
--- a/main.go
+++ b/main.go
@@ -1,135 +1,7 @@
package main
-import (
- "database/sql"
- "fmt"
- "github.com/robfig/cron/v3"
- "github.com/spf13/viper"
- "golang.org/x/net/proxy"
- tb "gopkg.in/tucnak/telebot.v2"
- "log"
- "net/http"
- "strconv"
- "strings"
- "time"
-)
-
-var (
- BotToken string
- Socks5 string
- bot *tb.Bot
- logger *log.Logger
-)
-
-const (
- dbDriverName = "mysql"
- logo = `
- ______ _____ _____ _ ____ _
- | ____| ____/ ____| | | | _ \ | |
- | |__ | |__| (___ _ _| |__ | |_) | ___ | |_
- | __| |___ \\___ \| | | | '_ \| _ < / _ \| __|
- | |____ ___) |___) | |_| | |_) | |_) | (_) | |_
- |______|____/_____/ \__,_|_.__/|____/ \___/ \__|
-`
-)
-
-var dbPath string
+import "github.com/iyear/E5SubBot/bots"
func main() {
- BotStart()
-}
-func BotStart() {
- MakeHandle()
- TaskLaunch()
- logger.Println("Bot Start")
- fmt.Println("------------")
- bot.Start()
-}
-func MakeHandle() {
- logger.Println("Make Handle……")
- //所有用户
- bot.Handle("/start", bStart)
- bot.Handle("/my", bMy)
- bot.Handle("/bind", bBind1)
- bot.Handle("/unbind", bUnBind)
- bot.Handle("/export", bExport)
- bot.Handle("/help", bHelp)
- bot.Handle(tb.OnText, bOnText)
- //管理员
- bot.Handle("/task", bTask)
- bot.Handle("/log", bLog)
-}
-func TaskLaunch() {
- task := cron.New()
- //每三小时执行一次
- task.AddFunc(viper.GetString("cron"), SignTask)
- //log分为每天
- task.AddFunc(" 0 0 * * *", InitLogger)
- // */1 * * * * 1 */3 * * *
- logger.Println("Cron Task Start……")
- task.Start()
-}
-func init() {
- fmt.Println(logo)
- InitLogger()
- //read config
- logger.Println("Read Config……")
- viper.SetConfigName("config")
- viper.AddConfigPath(".")
- err := viper.ReadInConfig()
- if err != nil {
- logger.Fatal(err)
- }
- host := viper.GetString("mysql.host")
- user := viper.GetString("mysql.user")
- port := viper.GetString("mysql.port")
- pwd := viper.GetString("mysql.password")
- database := viper.GetString("mysql.database")
- dbPath = strings.Join([]string{user, ":", pwd, "@tcp(", host, ":", port, ")/", database, "?charset=utf8"}, "")
- //fmt.Println(path)
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Fatal(err)
- }
- logger.Println("Connect MySQL Success!")
- if ok, err := CreateTB(); !ok {
- logger.Fatal(err)
- }
- defer db.Close()
- BotToken = viper.GetString("bot_token")
- Socks5 = viper.GetString("socks5")
- //set bot
- logger.Println("Bot Settings……")
- Poller := &tb.LongPoller{Timeout: 15 * time.Second}
- spamProtected := tb.NewMiddlewarePoller(Poller, func(upd *tb.Update) bool {
- if upd.Message == nil {
- return true
- }
- if !upd.Message.Private() {
- return false
- }
- return true
- })
- botsettings := tb.Settings{
- Token: BotToken,
- Poller: spamProtected,
- }
- //set socks5
- if Socks5 != "" {
- logger.Println("Proxy:" + Socks5)
- dialer, err := proxy.SOCKS5("tcp", Socks5, nil, proxy.Direct)
- if err != nil {
- logger.Println(err)
- }
- httpTransport := &http.Transport{}
- httpClient := &http.Client{Transport: httpTransport}
- httpTransport.Dial = dialer.Dial
- botsettings.Client = httpClient
- }
- //create bot
- bot, err = tb.NewBot(botsettings)
- if err != nil {
- logger.Fatal(err)
- }
- logger.Println("Bot: " + strconv.Itoa(bot.Me.ID) + " " + bot.Me.Username)
+ bots.BotStart()
}
diff --git a/model/client.go b/model/client.go
new file mode 100644
index 0000000..6797c1c
--- /dev/null
+++ b/model/client.go
@@ -0,0 +1,151 @@
+package model
+
+import (
+ "github.com/guonaihong/gout"
+ "github.com/iyear/E5SubBot/config"
+ "github.com/pkg/errors"
+ "github.com/tidwall/gjson"
+ "net/url"
+)
+
+type Client struct {
+ ID int `gorm:"unique;primaryKey;not null"`
+ TgId int64 `gorm:"not null"`
+ RefreshToken string `gorm:"not null"`
+ MsId string `gorm:"not null"`
+ Uptime int64 `gorm:"autoUpdateTime;not null"`
+ Alias string `gorm:"not null"`
+ ClientId string `gorm:"not null"`
+ ClientSecret string `gorm:"not null"`
+ Other string
+}
+type ErrClient struct {
+ *Client
+ Err error
+}
+
+const (
+ msApiUrl string = "https://login.microsoftonline.com"
+ msGraUrl string = "https://graph.microsoft.com"
+ redirectUri string = "http://localhost/e5sub"
+ scope string = "openid offline_access mail.read user.read"
+)
+
+func (c *Client) TableName() string {
+ return config.Mysql.Table
+}
+func NewClient(clientId string, clientSecret string) *Client {
+ return &Client{
+ ClientId: clientId,
+ ClientSecret: clientSecret,
+ }
+}
+func GetMSAuthUrl(clientId string) string {
+ return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + clientId + "&response_type=code&redirect_uri=" + url.QueryEscape(redirectUri) + "&response_mode=query&scope=" + url.QueryEscape(scope)
+}
+func GetMSRegisterAppUrl() string {
+ ru := "https://developer.microsoft.com/en-us/graph/quick-start?appID=_appId_&appName=_appName_&redirectUrl=http://localhost:8000&platform=option-windowsuniversal"
+ deeplink := "/quickstart/graphIO?publicClientSupport=false&appName=e5sub&redirectUrl=http://localhost/e5sub&allowImplicitFlow=false&ru=" + url.QueryEscape(ru)
+ appUrl := "https://apps.dev.microsoft.com/?deepLink=" + url.QueryEscape(deeplink)
+ return appUrl
+}
+
+func (c *Client) GetTokenWithCode(code string) error {
+ var content string
+ err := gout.POST(msApiUrl + "/common/oauth2/v2.0/token").
+ SetWWWForm(gout.H{
+ "client_id": c.ClientId,
+ "client_secret": c.ClientSecret,
+ "grant_type": "authorization_code",
+ "scope": scope,
+ "code": code,
+ "redirect_uri": redirectUri,
+ }).
+ BindBody(&content).Do()
+
+ if err != nil {
+ return err
+ }
+
+ if gjson.Get(content, "token_type").String() == "Bearer" {
+ c.RefreshToken = gjson.Get(content, "refresh_token").String()
+ return nil
+ }
+ return errors.New(content)
+}
+
+//getToken return accessToken and error
+func (c *Client) getToken() (string, error) {
+
+ var content string
+ err := gout.POST(msApiUrl + "/common/oauth2/v2.0/token").
+ SetWWWForm(gout.H{
+ "client_id": c.ClientId,
+ "client_secret": c.ClientSecret,
+ "grant_type": "refresh_token",
+ "scope": scope,
+ "refresh_token": c.RefreshToken,
+ "redirect_uri": redirectUri,
+ }).
+ BindBody(&content).
+ Do()
+ if err != nil {
+ return "", err
+ }
+
+ if gjson.Get(content, "token_type").String() == "Bearer" {
+ c.RefreshToken = gjson.Get(content, "refresh_token").String()
+ return gjson.Get(content, "access_token").String(), nil
+ }
+ return "", errors.New(gjson.Get(content, "error").String())
+}
+
+// GetUserInfo return infoJSON and error
+func (c *Client) GetUserInfo() (string, error) {
+ var (
+ content string
+ err error
+ accessToken string
+ )
+ if accessToken, err = c.getToken(); err != nil {
+ return "", err
+ }
+ err = gout.GET(msGraUrl + "/v1.0/me").
+ SetHeader(gout.H{
+ "Authorization": accessToken,
+ }).
+ BindBody(&content).
+ Do()
+ if err != nil {
+ return "", err
+ }
+
+ if gjson.Get(content, "id").String() != "" {
+ return content, nil
+ }
+ return "", errors.New(content)
+}
+
+func (c *Client) GetOutlookMails() error {
+
+ var content string
+ var err error
+ var accessToken string
+ if accessToken, err = c.getToken(); err != nil {
+ return err
+ }
+ err = gout.GET(msGraUrl + "/v1.0/me/messages").
+ SetHeader(gout.H{
+ "Authorization": accessToken,
+ }).
+ BindBody(&content).
+ Do()
+ if err != nil {
+ return err
+ }
+ //这里的.需要转义,否则会按路径的方式解析
+ if gjson.Get(content, "@odata\\.context").String() != "" {
+ return nil
+ }
+ return errors.New(gjson.Get(content, "error").String())
+}
diff --git a/model/db.go b/model/db.go
new file mode 100644
index 0000000..672c8ec
--- /dev/null
+++ b/model/db.go
@@ -0,0 +1,32 @@
+package model
+
+import (
+ "fmt"
+ "github.com/iyear/E5SubBot/config"
+ "go.uber.org/zap"
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ "time"
+)
+
+var DB *gorm.DB
+
+func InitDB() {
+ var err error
+ dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+ config.Mysql.User,
+ config.Mysql.Password,
+ config.Mysql.Host,
+ config.Mysql.Port,
+ config.Mysql.DB,
+ )
+ DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
+ NowFunc: func() time.Time {
+ return time.Now()
+ },
+ })
+ if err != nil {
+ zap.S().Errorw("failed to open db", "error", err)
+ }
+ DB.AutoMigrate(&Client{})
+}
diff --git a/mysql.go b/mysql.go
deleted file mode 100644
index af0f152..0000000
--- a/mysql.go
+++ /dev/null
@@ -1,152 +0,0 @@
-package main
-
-import (
- "database/sql"
- _ "github.com/go-sql-driver/mysql"
-)
-
-type MSData struct {
- tgId int64
- refreshToken string
- msId string
- uptime int64
- alias string
- clientId string
- clientSecret string
- other string
-}
-
-//update data by msId
-func UpdateData(u MSData) (bool, error) {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- sqlString := `UPDATE users set tg_id=?,refresh_token=?,uptime=?,alias=?,client_id=?,client_secret=?,other=? where ms_id=?`
- stmt, err := db.Prepare(sqlString)
- if err != nil {
- return false, err
- }
- _, err = stmt.Exec(u.tgId, u.refreshToken, u.uptime, u.alias, u.clientId, u.clientSecret, u.other, u.msId)
- if err != nil {
- return false, err
- }
- return true, nil
-}
-
-//add data
-func AddData(u MSData) (bool, error) {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- sqlString := `
- INSERT INTO users (tg_id, refresh_token,ms_id, uptime,alias,client_id,client_secret,other)
- VALUES (?,?,?,?,?,?,?,?)`
- stmt, err := db.Prepare(sqlString)
- if err != nil {
- return false, err
- }
- _, err = stmt.Exec(u.tgId, u.refreshToken, u.msId, u.uptime, u.alias, u.clientId, u.clientSecret, u.other)
- if err != nil {
- return false, err
- }
- return true, nil
-}
-
-//del data by ms_id
-func DelData(msId string) (bool, error) {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- sqlString := `delete from users where ms_id=?`
- stmt, err := db.Prepare(sqlString)
- if err != nil {
- return false, err
- }
- res, err := stmt.Exec(msId)
- if err != nil {
- return false, err
- }
- _, err = res.RowsAffected()
- if err != nil {
- return false, err
- }
- return true, nil
-}
-func QueryData(rows *sql.Rows) []MSData {
-
- var result = make([]MSData, 0)
- defer rows.Close()
- for rows.Next() {
- var (
- tgIdt, uptimet int64
- refresht, othert, msidt, aliast, clientIdt, clientSet string
- )
- rows.Scan(&tgIdt, &refresht, &msidt, &uptimet, &aliast, &clientIdt, &clientSet, &othert)
- //fmt.Println(string(tgNamet) + "=>" + uptimet.Format("2006-01-02 15:04:05"))
- result = append(result, MSData{tgIdt, refresht, msidt, uptimet, aliast, clientIdt, clientSet, othert})
- }
- return result
-}
-func QueryDataByMS(msId string) []MSData {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- rows, err := db.Query("select * from users where ms_id = ?", msId)
- CheckErr(err)
- return QueryData(rows)
-}
-
-func QueryDataAll() []MSData {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- rows, err := db.Query("select * from users ")
- CheckErr(err)
- return QueryData(rows)
-}
-
-//query data by tg_id
-func QueryDataByTG(tgId int64) []MSData {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- rows, err := db.Query("select * from users where tg_id = ?", tgId)
- CheckErr(err)
- return QueryData(rows)
-}
-func CreateTB() (bool, error) {
- db, err := sql.Open(dbDriverName, dbPath)
- if err != nil {
- logger.Println(err)
- }
- defer db.Close()
- sqltable := `
- create table if not exists users
- (
- tg_id INTEGER,
- refresh_token TEXT,
- ms_id VARCHAR(255),
- uptime INTEGER,
- alias VARCHAR(255),
- client_id VARCHAR(255),
- client_secret VARCHAR(255),
- other TEXT
- );`
- _, err = db.Exec(sqltable)
- if err != nil {
- return false, err
- }
- return true, nil
-}
diff --git a/outlook.go b/outlook.go
index d8f87a1..02e8703 100644
--- a/outlook.go
+++ b/outlook.go
@@ -43,7 +43,6 @@ func MSFirGetToken(code, cid, cse string) (access string, refresh string, Error
logger.Println(err)
return "", "", err
}
- req.Close = true
resp, err := client.Do(req)
if err != nil {
logger.Println(err)
@@ -79,7 +78,6 @@ func MSGetToken(refreshtoken, cid, cse string) (access string, newRefreshToken s
logger.Println(err)
return "", "", err
}
- req.Close = true
resp, err := client.Do(req)
if err != nil {
logger.Println(err)
@@ -107,7 +105,6 @@ func MSGetUserInfo(accesstoken string) (json string, Error error) {
logger.Println(err)
return "", err
}
- req.Close = true
req.Header.Set("Authorization", accesstoken)
resp, err := client.Do(req)
if err != nil {
@@ -134,7 +131,6 @@ func OutLookGetMails(accesstoken string) (bool, error) {
logger.Println(err)
return false, err
}
- req.Close = true
req.Header.Set("Authorization", accesstoken)
resp, err := client.Do(req)
if err != nil {
diff --git a/task/sign.go b/task/sign.go
new file mode 100644
index 0000000..535db56
--- /dev/null
+++ b/task/sign.go
@@ -0,0 +1,59 @@
+package task
+
+import (
+ "fmt"
+ "github.com/iyear/E5SubBot/config"
+ "github.com/iyear/E5SubBot/model"
+ "go.uber.org/zap"
+)
+
+func Sign(clients []*model.Client) []*model.ErrClient {
+ var errClients []*model.ErrClient
+
+ done := make(chan struct{})
+ in := make(chan *model.ErrClient, 5)
+ out := make(chan *model.ErrClient, 5)
+
+ go func() {
+ for _, client := range clients {
+ in <- &model.ErrClient{
+ Client: client,
+ Err: nil,
+ }
+ }
+ close(in)
+ }()
+ for i := 0; i < config.MaxGoroutines; i++ {
+ go func() {
+ for {
+ select {
+ case errCli, f := <-in:
+ if !f {
+ continue
+ }
+
+ err := errCli.GetOutlookMails()
+ errCli.Err = err
+ out <- errCli
+ case <-done:
+ return
+ }
+ }
+ }()
+ }
+ for i := 0; i < len(clients); i++ {
+ errClient := <-out
+ if errClient.Err == nil {
+ fmt.Printf("%s OK\n", errClient.MsId)
+ } else {
+ zap.S().Errorw("failed to sign",
+ "error", errClient.Err,
+ "id", errClient.ID,
+ )
+ //fmt.Printf("%s %s\n",errClient.MsId,errClient.Err)
+ }
+ errClients = append(errClients, errClient)
+ }
+ close(done)
+ return errClients
+}
diff --git a/util.go b/util/util.go
similarity index 65%
rename from util.go
rename to util/util.go
index 52b0e2e..1960b82 100644
--- a/util.go
+++ b/util/util.go
@@ -1,33 +1,21 @@
-package main
+package util
import (
"crypto/md5"
"encoding/hex"
- "fmt"
"io/ioutil"
- "log"
"net/url"
"os"
- "strings"
"time"
)
-//true=>no error
+// Min true=>no error
func Min(x, y int) int {
if x < y {
return x
}
return y
}
-func CheckErr(err error) bool {
- if err != nil {
- log.Println(err)
- fmt.Println("ERROR")
- panic(err)
- return false
- }
- return true
-}
func PathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
@@ -38,21 +26,6 @@ func PathExists(path string) bool {
}
return false
}
-func GetBetweenStr(str, start, end string) string {
- n := strings.Index(str, start)
- if n == -1 {
- n = 0
- } else {
- n = n + len(start)
- }
- str = string([]byte(str)[n:])
- m := strings.Index(str, end)
- if m == -1 {
- m = len(str)
- }
- str = string([]byte(str)[:m])
- return str
-}
func GetURLValue(Url, key string) string {
u, _ := url.Parse(Url)
query := u.Query()
@@ -60,19 +33,17 @@ func GetURLValue(Url, key string) string {
return query.Get(key)
}
-//返回一个32位md5加密后的字符串
func GetMD5Encode(data string) string {
h := md5.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
-//返回一个16位md5加密后的字符串
func Get16MD5Encode(data string) string {
return GetMD5Encode(data)[8:24]
}
-//只返回文件名
+// GetPathFiles only return file name
func GetPathFiles(path string) []string {
files, _ := ioutil.ReadDir(path)
var t []string
@@ -86,13 +57,12 @@ func GetPathFiles(path string) []string {
return t
}
-//输入文件夹路径,返回最近n个log的路径,不到n个返回所有
func GetRecentLogs(path string, n int) []string {
var paths []string
if !PathExists(path) {
return paths
}
- //path末尾检查/
+
if path[len(path)-1:] != "/" {
path += "/"
}
@@ -100,7 +70,6 @@ func GetRecentLogs(path string, n int) []string {
d, _ := time.ParseDuration("-24h")
//不到n个返回所有
nt := Min(n, len(GetPathFiles(path)))
- //fmt.Println(nt)
for i := 1; i <= nt; {
if PathExists(path + data.Format("2006-01-02") + ".log") {
paths = append(paths, data.Format("2006-01-02")+".log")
@@ -110,3 +79,9 @@ func GetRecentLogs(path string, n int) []string {
}
return paths
}
+func IF(f bool, a interface{}, b interface{}) interface{} {
+ if f {
+ return a
+ }
+ return b
+}