├── asserts ├── notice.mp3 └── notice.go ├── .gitignore ├── pkg ├── notice │ ├── music │ │ ├── music_test.go │ │ └── music.go │ ├── log.go │ ├── notice.go │ ├── pushplus.go │ └── bark.go ├── util │ └── util.go └── regular │ ├── config.go │ └── regular.go ├── core ├── core.go └── ddmc │ ├── config.go │ ├── reserve.go │ ├── address.go │ ├── user.go │ ├── cart.go │ ├── order.go │ └── session.go ├── config └── config.yaml ├── cmd └── ddshop │ ├── main.go │ └── app │ └── app.go ├── go.mod ├── README.md ├── .github └── workflows │ └── release.yml ├── LICENSE └── go.sum /asserts/notice.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zc2638/ddshop/HEAD/asserts/notice.mp3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | .DS_Store 19 | testdata 20 | *.local.yaml -------------------------------------------------------------------------------- /pkg/notice/music/music_test.go: -------------------------------------------------------------------------------- 1 | // Package music 2 | // Created by zc on 2022/4/17. 3 | package music 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/zc2638/ddshop/asserts" 9 | ) 10 | 11 | func TestNewMP3(t *testing.T) { 12 | player, err := NewMP3(asserts.NoticeMP3, 180) 13 | if err != nil { 14 | return 15 | } 16 | if err := player.Play(); err != nil { 17 | t.Fatal(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package core 16 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | bark: 2 | server: "https://api.day.app" # 自定义 Bark Server 地址, 不填默认为 https://api.day.app 3 | key: "" # Bark 通知推送的 Key 4 | push_plus: 5 | token: "" # Push Plus 通知推送的 Token 6 | regular: 7 | success_interval: 100 # 执行成功 再次执行的间隔时间(ms), -1为停止继续执行 8 | fail_interval: 100 # 执行失败 再次执行的间隔时间(ms), -1为停止继续执行 9 | periods: # 执行周期 10 | - start: "05:59" 11 | end: "06:10" 12 | - start: "08:29" 13 | end: "08:35" 14 | ddmc: 15 | cookie: "" # 使用抓包工具获取 叮咚买菜上的用户 `cookie` (DDXQSESSID) 16 | pay_type: "wechat" # 支付方式:支付宝、alipay、微信、wechat 17 | channel: 3 # 通道: app => 3, 微信小程序 => 4 18 | interval: 100 # 连续发起请求间隔时间(ms) 19 | retry_count: 100 # 每次请求失败的尝试次数, -1为无限 20 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | func LoopRun(num int, f func()) { 18 | for i := 0; i < num; i++ { 19 | f() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /asserts/notice.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package asserts 16 | 17 | import ( 18 | _ "embed" 19 | ) 20 | 21 | // NoticeMP3 defines the content of notice.mp3 22 | //go:embed notice.mp3 23 | var NoticeMP3 []byte 24 | -------------------------------------------------------------------------------- /core/ddmc/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | type Config struct { 18 | Cookie string `json:"cookie"` 19 | PayType string `json:"pay_type"` 20 | Channel int `json:"channel"` 21 | Interval int64 `json:"interval"` 22 | RetryCount int `json:"retry_count"` 23 | } 24 | -------------------------------------------------------------------------------- /pkg/notice/log.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package notice 16 | 17 | import ( 18 | "github.com/sirupsen/logrus" 19 | 20 | "github.com/zc2638/ddshop/pkg/util" 21 | ) 22 | 23 | func NewLog() Engine { 24 | return &log{} 25 | } 26 | 27 | type log struct{} 28 | 29 | func (l *log) Name() string { 30 | return "Log" 31 | } 32 | 33 | func (l *log) Send(title, body string) error { 34 | util.LoopRun(10, func() { 35 | logrus.Infof("%s: %s", title, body) 36 | }) 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/regular/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package regular 16 | 17 | type Config struct { 18 | SuccessInterval int `json:"success_interval"` // 执行成功 再次执行的间隔时间(ms), -1为停止继续执行 19 | FailInterval int `json:"fail_interval"` // 执行失败 再次执行的间隔时间(ms), -1为停止继续执行 20 | Periods []Period `json:"periods"` // 执行周期 21 | } 22 | 23 | type Period struct { 24 | Start string `json:"start"` // 开始时间, 00:00 25 | End string `json:"end"` // 结束时间, 23:59 26 | 27 | startHour int 28 | startMinute int 29 | endHour int 30 | endMinute int 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ddshop/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/sirupsen/logrus" 21 | 22 | "github.com/zc2638/ddshop/cmd/ddshop/app" 23 | ) 24 | 25 | const TimeFormat = "2006/01/02 15:04:05" 26 | 27 | func main() { 28 | logrus.SetFormatter(&logrus.TextFormatter{ 29 | ForceColors: true, 30 | DisableLevelTruncation: true, 31 | PadLevelText: true, 32 | FullTimestamp: true, 33 | TimestampFormat: TimeFormat, 34 | }) 35 | command := app.NewRootCommand() 36 | if err := command.Execute(); err != nil { 37 | os.Exit(1) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/notice/music/music.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package music 16 | 17 | import ( 18 | "bytes" 19 | "runtime" 20 | "time" 21 | 22 | "github.com/hajimehoshi/go-mp3" 23 | "github.com/hajimehoshi/oto/v2" 24 | ) 25 | 26 | func NewMP3(b []byte, sec int) (*MP3, error) { 27 | decoder, err := mp3.NewDecoder(bytes.NewReader(b)) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &MP3{ 32 | decoder: decoder, 33 | sec: sec, 34 | }, nil 35 | } 36 | 37 | type MP3 struct { 38 | decoder *mp3.Decoder 39 | sec int 40 | } 41 | 42 | func (m *MP3) Play() error { 43 | c, _, err := oto.NewContext(m.decoder.SampleRate(), 2, 2) 44 | if err != nil { 45 | return err 46 | } 47 | player := c.NewPlayer(m.decoder) 48 | player.Play() 49 | 50 | // 异步放歌,需要等待 51 | time.Sleep(time.Duration(m.sec) * time.Second) 52 | runtime.KeepAlive(player) 53 | return nil 54 | } 55 | 56 | func (m *MP3) Name() string { 57 | return "Music" 58 | } 59 | 60 | func (m *MP3) Send(_, _ string) error { 61 | return m.Play() 62 | } 63 | -------------------------------------------------------------------------------- /pkg/notice/notice.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package notice 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "sync" 21 | ) 22 | 23 | type Engine interface { 24 | Name() string 25 | Send(title, body string) error 26 | } 27 | 28 | type Interface interface { 29 | Notice(title, body string) error 30 | } 31 | 32 | func New(engines ...Engine) Interface { 33 | es := make([]Engine, 0, len(engines)) 34 | for _, e := range engines { 35 | if e == nil { 36 | continue 37 | } 38 | es = append(es, e) 39 | } 40 | return ¬ice{engines: es} 41 | } 42 | 43 | type notice struct { 44 | engines []Engine 45 | } 46 | 47 | func (n *notice) Notice(title, body string) error { 48 | var mux sync.Mutex 49 | var errStr string 50 | 51 | var wg sync.WaitGroup 52 | for _, v := range n.engines { 53 | wg.Add(1) 54 | 55 | go func(engine Engine) { 56 | defer wg.Done() 57 | 58 | if err := engine.Send(title, body); err != nil { 59 | mux.Lock() 60 | defer mux.Unlock() 61 | errStr += fmt.Sprintf("%s: %v\n", engine.Name(), err) 62 | } 63 | }(v) 64 | } 65 | wg.Wait() 66 | 67 | if errStr != "" { 68 | return errors.New(errStr) 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zc2638/ddshop 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/AlecAivazis/survey/v2 v2.3.4 7 | github.com/go-resty/resty/v2 v2.7.0 8 | github.com/google/uuid v1.3.0 9 | github.com/hajimehoshi/go-mp3 v0.3.3 10 | github.com/hajimehoshi/oto/v2 v2.0.3 11 | github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a 12 | github.com/sirupsen/logrus v1.8.1 13 | github.com/spf13/cobra v1.4.0 14 | github.com/spf13/viper v1.7.1 15 | github.com/tidwall/gjson v1.14.0 16 | ) 17 | 18 | require ( 19 | github.com/fsnotify/fsnotify v1.4.7 // indirect 20 | github.com/hashicorp/hcl v1.0.0 // indirect 21 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 22 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 23 | github.com/magiconair/properties v1.8.1 // indirect 24 | github.com/mattn/go-colorable v0.1.12 // indirect 25 | github.com/mattn/go-isatty v0.0.14 // indirect 26 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 27 | github.com/mitchellh/go-homedir v1.0.0 // indirect 28 | github.com/mitchellh/mapstructure v1.1.2 // indirect 29 | github.com/pelletier/go-toml v1.2.0 // indirect 30 | github.com/spf13/afero v1.1.2 // indirect 31 | github.com/spf13/cast v1.3.0 // indirect 32 | github.com/spf13/jwalterweatherman v1.0.0 // indirect 33 | github.com/spf13/pflag v1.0.5 // indirect 34 | github.com/subosito/gotenv v1.2.0 // indirect 35 | github.com/tidwall/match v1.1.1 // indirect 36 | github.com/tidwall/pretty v1.2.0 // indirect 37 | golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect 38 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect 39 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 40 | golang.org/x/text v0.3.7 // indirect 41 | gopkg.in/ini.v1 v1.51.0 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddshop 2 | 购物情况有所改善,并且叮咚增加了安全风控检测,导致频繁使用程序 账号会被封禁。 3 | 4 | 团购网站,可以查询所在地区的所有团购信息: 5 | https://tg.xuan-niao.com 6 | 7 | 该项目理论上不会再跟踪 叮咚买菜 的新更新,代码可供大家参考。 8 | 9 | ## 安装 10 | ### Releases 11 | 从 [Github Release](https://github.com/zc2638/ddshop/releases) 下载 12 | ### Docker 13 | ```shell 14 | docker pull zc2638/ddshop:latest 15 | ``` 16 | ### 源码 17 | ```shell 18 | go install github.com/zc2638/ddshop/cmd/ddshop@latest 19 | ``` 20 | 21 | ## 使用 22 | 23 | 1. 创建配置文件`config.yaml` 24 | 2. 修改配置文件中的配置项 25 | 3. 执行程序 26 | 27 | ### 配置 28 | 点击查看 [完整配置](./config/config.yaml) 29 | ```yaml 30 | bark: 31 | key: "" # Bark 通知推送的 Key 32 | push_plus: 33 | token: "" # Push Plus 通知推送的 Token 34 | 35 | # 自动任务的配置,不配置 periods 将持续执行 36 | regular: 37 | success_interval: 100 # 执行成功 再次执行的间隔时间(ms), -1为停止继续执行 38 | fail_interval: 100 # 执行失败 再次执行的间隔时间(ms), -1为停止继续执行 39 | periods: # 执行周期 40 | - start: "05:59" 41 | end: "06:10" 42 | - start: "08:29" 43 | end: "08:35" 44 | 45 | # 叮咚买菜的配置 46 | ddmc: 47 | cookie: "" # 使用抓包工具获取 叮咚买菜上的用户 `cookie` (DDXQSESSID) 48 | pay_type: "wechat" # 支付方式:支付宝、alipay、微信、wechat 49 | channel: 3 # 通道: app => 3, 微信小程序 => 4 50 | interval: 100 # 连续发起请求间隔时间(ms) 51 | retry_count: 100 # 每次请求失败的尝试次数, -1为无限 52 | ``` 53 | ### 命令行工具 54 | 执行以下命令前,将 `` 替换为实际的配置文件路径,例如:`config/config.yaml` 55 | ```shell 56 | ddshop -c 57 | ``` 58 | ### Docker 59 | 执行以下命令前,将 `` 替换成宿主机存放配置文件的目录 60 | ```shell 61 | docker run --name ddshop -it -v :/work/config zc2638/ddshop 62 | ``` 63 | 64 | ## 抓包 65 | [Charles抓包教程](https://www.jianshu.com/p/ff85b3dac157) 66 | [Charles 抓包 PC端微信小程序](https://blog.csdn.net/z2181745/article/details/123002569) 67 | 微信小程序支持PC版,所以只需要安装抓包程序,打开 `叮咚买菜微信小程序`,直接进行抓包即可,无须进行手机配置。 68 | 69 | ## 声明 70 | 本项目仅供学习交流,严禁用作商业行为! 71 | 因他人私自不正当使用造成的违法违规行为与本人无关! 72 | 如有任何问题可联系本人删除! -------------------------------------------------------------------------------- /pkg/notice/pushplus.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package notice 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "errors" 21 | "fmt" 22 | "io/ioutil" 23 | "net/http" 24 | ) 25 | 26 | const pushPlusURL = "http://www.pushplus.plus/send" 27 | 28 | type PushPlusConfig struct { 29 | Token string `json:"token"` 30 | } 31 | 32 | func NewPushPlus(cfg *PushPlusConfig) Engine { 33 | if cfg.Token == "" { 34 | return nil 35 | } 36 | return &pushPlus{token: cfg.Token} 37 | } 38 | 39 | type pushPlusResult struct { 40 | Code int `json:"code"` 41 | Msg string `json:"msg"` 42 | } 43 | 44 | type pushPlus struct { 45 | token string 46 | } 47 | 48 | func (p *pushPlus) Name() string { 49 | return "PushPlus" 50 | } 51 | 52 | func (p *pushPlus) Send(title, body string) error { 53 | data := map[string]string{ 54 | "token": p.token, 55 | "title": title, 56 | "content": body, 57 | } 58 | b, err := json.Marshal(data) 59 | if err != nil { 60 | return err 61 | } 62 | resp, err := http.Post(pushPlusURL, "application/json", bytes.NewReader(b)) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | ba, err := ioutil.ReadAll(resp.Body) 68 | if err != nil { 69 | return err 70 | } 71 | defer resp.Body.Close() 72 | 73 | if resp.StatusCode != http.StatusOK { 74 | return fmt.Errorf("statusCode: %d, body: %v", resp.StatusCode, string(ba)) 75 | } 76 | 77 | var res pushPlusResult 78 | if err := json.Unmarshal(ba, &res); err != nil { 79 | return err 80 | } 81 | if res.Code != 200 { 82 | return errors.New(res.Msg) 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/notice/bark.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package notice 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "path" 24 | ) 25 | 26 | const barkURL = "https://api.day.app/push" 27 | 28 | type BarkConfig struct { 29 | Server string `json:"server"` 30 | Key string `json:"key"` 31 | } 32 | 33 | func NewBark(cfg *BarkConfig) Engine { 34 | if cfg.Key == "" { 35 | return nil 36 | } 37 | return &bark{cfg: cfg} 38 | } 39 | 40 | type bark struct { 41 | cfg *BarkConfig 42 | } 43 | 44 | func (b *bark) Name() string { 45 | return "Bark" 46 | } 47 | 48 | func (b *bark) Send(title, body string) error { 49 | data := &barkData{ 50 | DeviceKey: b.cfg.Key, 51 | Title: title, 52 | Body: body, 53 | Sound: "alarm.caf", 54 | } 55 | bs, err := json.Marshal(data) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | uri := barkURL 61 | if b.cfg.Server != "" { 62 | uri = path.Join(b.cfg.Server, "push") 63 | } 64 | resp, err := http.Post(uri, "application/json; charset=utf-8", bytes.NewReader(bs)) 65 | if err != nil { 66 | return err 67 | } 68 | result, err := ioutil.ReadAll(resp.Body) 69 | if err != nil { 70 | return err 71 | } 72 | defer resp.Body.Close() 73 | 74 | if resp.StatusCode != http.StatusOK { 75 | return fmt.Errorf("statusCode: %d, body: %v", resp.StatusCode, string(result)) 76 | } 77 | return nil 78 | } 79 | 80 | type barkData struct { 81 | DeviceKey string `json:"device_key"` 82 | Title string `json:"title"` 83 | Body string `json:"body,omitempty"` 84 | Badge int `json:"badge,omitempty"` 85 | Sound string `json:"sound,omitempty"` 86 | Icon string `json:"icon,omitempty"` 87 | Group string `json:"group,omitempty"` 88 | Url string `json:"url,omitempty"` 89 | } 90 | -------------------------------------------------------------------------------- /core/ddmc/reserve.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "net/http" 22 | "strings" 23 | 24 | "github.com/tidwall/gjson" 25 | ) 26 | 27 | type ReserveTime struct { 28 | StartTimestamp int `json:"start_timestamp"` 29 | EndTimestamp int `json:"end_timestamp"` 30 | SelectMsg string `json:"select_msg"` 31 | } 32 | 33 | func (s *Session) GetMultiReserveTime(ctx context.Context, products []map[string]interface{}) ([]ReserveTime, error) { 34 | urlPath := "https://maicai.api.ddxq.mobi/order/getMultiReserveTime" 35 | productsList := [][]map[string]interface{}{products} 36 | productsJson, err := json.Marshal(productsList) 37 | if err != nil { 38 | return nil, fmt.Errorf("marshal products info failed: %v", err) 39 | } 40 | 41 | params := s.buildURLParams(true) 42 | params.Add("products", string(productsJson)) 43 | params.Add("group_config_id", "") 44 | params.Add("isBridge", "false") 45 | 46 | req := s.client.R() 47 | req.Header = s.buildHeader() 48 | req.SetBody(strings.NewReader(params.Encode())) 49 | resp, err := s.execute(ctx, req, http.MethodPost, urlPath, s.cfg.RetryCount) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | reserveTimes := gjson.Get(resp.String(), "data.0.time.0.times").Array() 55 | reserveTimeList := make([]ReserveTime, 0, len(reserveTimes)) 56 | for _, reserveTimeInfo := range reserveTimes { 57 | if reserveTimeInfo.Get("disableType").Num != 0 { 58 | continue 59 | } 60 | reserveTime := ReserveTime{ 61 | StartTimestamp: int(reserveTimeInfo.Get("start_timestamp").Num), 62 | EndTimestamp: int(reserveTimeInfo.Get("end_timestamp").Num), 63 | SelectMsg: reserveTimeInfo.Get("select_msg").Str, 64 | } 65 | reserveTimeList = append(reserveTimeList, reserveTime) 66 | } 67 | return reserveTimeList, nil 68 | } 69 | -------------------------------------------------------------------------------- /cmd/ddshop/app/app.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package app 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "os" 21 | 22 | "github.com/pkgms/go/server" 23 | "github.com/sirupsen/logrus" 24 | "github.com/spf13/cobra" 25 | "github.com/spf13/viper" 26 | 27 | "github.com/zc2638/ddshop/asserts" 28 | "github.com/zc2638/ddshop/core/ddmc" 29 | "github.com/zc2638/ddshop/pkg/notice" 30 | "github.com/zc2638/ddshop/pkg/notice/music" 31 | "github.com/zc2638/ddshop/pkg/regular" 32 | ) 33 | 34 | type Config struct { 35 | Bark notice.BarkConfig `json:"bark"` 36 | PushPlus notice.PushPlusConfig `json:"push_plus"` 37 | Regular regular.Config `json:"regular"` 38 | DDMC ddmc.Config `json:"ddmc"` 39 | } 40 | 41 | type Option struct { 42 | ConfigPath string 43 | Cookie string 44 | BarkKey string 45 | PayType string 46 | Interval int64 47 | } 48 | 49 | func (o *Option) Config() *Config { 50 | return &Config{ 51 | Regular: regular.Config{ 52 | SuccessInterval: 100, 53 | FailInterval: 100, 54 | }, 55 | DDMC: ddmc.Config{ 56 | Cookie: o.Cookie, 57 | PayType: o.PayType, 58 | Interval: o.Interval, 59 | RetryCount: 100, 60 | }, 61 | Bark: notice.BarkConfig{ 62 | Key: o.BarkKey, 63 | }, 64 | } 65 | } 66 | 67 | func NewRootCommand() *cobra.Command { 68 | opt := &Option{} 69 | cmd := &cobra.Command{ 70 | Use: "ddshop", 71 | Short: "叮咚买菜自动抢购下单程序", 72 | SilenceUsage: true, 73 | RunE: func(cmd *cobra.Command, args []string) error { 74 | cfg := opt.Config() 75 | if opt.ConfigPath != "" { 76 | viper.SetConfigType("yaml") 77 | if err := server.ParseConfigWithEnv(opt.ConfigPath, cfg, "DDSHOP"); err != nil { 78 | return err 79 | } 80 | } else { 81 | logrus.Warning("未设置配置文件,使用参数解析") 82 | } 83 | if cfg.DDMC.Cookie == "" { 84 | return errors.New("请输入用户Cookie.\n你可以执行此命令 `ddshop --cookie xxx` 或者 `DDSHOP_COOKIE=xxx ddshop`") 85 | } 86 | 87 | mp3Entry, err := music.NewMP3(asserts.NoticeMP3, 180) 88 | if err != nil { 89 | logrus.Warning("提醒歌曲解析失败: %v", err) 90 | } 91 | bark := notice.NewBark(&cfg.Bark) 92 | pushPlus := notice.NewPushPlus(&cfg.PushPlus) 93 | noticeIns := notice.New(notice.NewLog(), bark, pushPlus, mp3Entry) 94 | 95 | session, err := ddmc.NewSession(&cfg.DDMC, noticeIns) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | engine, err := regular.New(&cfg.Regular) 101 | if err != nil { 102 | return err 103 | } 104 | return engine.Start(context.Background(), session) 105 | }, 106 | } 107 | 108 | configEnv := os.Getenv("DDSHOP_CONFIG") 109 | cookieEnv := os.Getenv("DDSHOP_COOKIE") 110 | barkKeyEnv := os.Getenv("DDSHOP_BARKKEY") 111 | payTypeEnv := os.Getenv("DDSHOP_PAYTYPE") 112 | cmd.Flags().StringVarP(&opt.ConfigPath, "config", "c", configEnv, "设置配置文件路径") 113 | cmd.Flags().StringVar(&opt.Cookie, "cookie", cookieEnv, "设置用户个人cookie") 114 | cmd.Flags().StringVar(&opt.BarkKey, "bark-key", barkKeyEnv, "设置bark的通知key") 115 | cmd.Flags().StringVar(&opt.PayType, "pay-type", payTypeEnv, "设置支付方式,支付宝、微信、alipay、wechat") 116 | cmd.Flags().Int64Var(&opt.Interval, "interval", 100, "设置请求间隔时间(ms),默认为100") 117 | return cmd 118 | } 119 | -------------------------------------------------------------------------------- /pkg/regular/regular.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package regular 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/sirupsen/logrus" 23 | ) 24 | 25 | type TaskInterface interface { 26 | Run(ctx context.Context) error 27 | } 28 | 29 | func New(cfg *Config) (*Engine, error) { 30 | for k, v := range cfg.Periods { 31 | start, err := time.Parse("15:04", v.Start) 32 | if err != nil { 33 | return nil, fmt.Errorf("解析时间段 %d 开始时间(%s)失败: %v", k, v.Start, err) 34 | } 35 | end, err := time.Parse("15:04", v.End) 36 | if err != nil { 37 | return nil, fmt.Errorf("解析时间段 %d 结束时间(%s)失败: %v", k, v.Start, err) 38 | } 39 | cfg.Periods[k].startHour = start.Hour() 40 | cfg.Periods[k].startMinute = start.Minute() 41 | cfg.Periods[k].endHour = end.Hour() 42 | cfg.Periods[k].endMinute = end.Minute() 43 | } 44 | return &Engine{cfg: cfg}, nil 45 | } 46 | 47 | type Engine struct { 48 | cfg *Config 49 | cancel context.CancelFunc 50 | } 51 | 52 | func (e *Engine) Start(ctx context.Context, task TaskInterface) error { 53 | for { 54 | second := time.Now().Second() 55 | if second == 0 { 56 | break 57 | } 58 | sleepInterval := 60 - second 59 | logrus.Warningf("当前秒数不为 0,需等待 %ds 后开启自动助手", sleepInterval) 60 | time.Sleep(time.Duration(sleepInterval) * time.Second) 61 | } 62 | 63 | if len(e.cfg.Periods) == 0 { 64 | return e.run(ctx, task) 65 | } 66 | 67 | currentStartHour, currentStartMinute := -1, -1 68 | ticker := time.NewTicker(time.Minute) 69 | for { 70 | if e.cancel == nil { 71 | logrus.Infof("开始任务侦查") 72 | } 73 | now := time.Now() 74 | hour := now.Hour() 75 | minute := now.Minute() 76 | 77 | for _, v := range e.cfg.Periods { 78 | if currentStartHour > -1 && (currentStartHour != v.startHour || currentStartMinute != v.startMinute) { 79 | continue 80 | } 81 | 82 | start, end := false, false 83 | if v.startHour < hour { 84 | start = true 85 | } 86 | if v.startHour == hour && v.startMinute <= minute { 87 | start = true 88 | } 89 | if v.endHour < hour { 90 | end = true 91 | } 92 | if v.endHour == hour && v.endMinute <= minute { 93 | end = true 94 | } 95 | 96 | if start && !end && currentStartHour != v.startHour { 97 | currentStartHour = v.startHour 98 | currentStartMinute = v.startMinute 99 | 100 | ctx, e.cancel = context.WithCancel(ctx) 101 | go func() { 102 | if err := e.run(ctx, task); err != nil { 103 | logrus.Errorf("执行结束: %v", err) 104 | } 105 | logrus.Infof("当前时间段执行结束,请等待下个时间段") 106 | }() 107 | break 108 | } 109 | if start && end && e.cancel != nil { 110 | e.cancel() 111 | e.cancel = nil 112 | } 113 | } 114 | <-ticker.C 115 | } 116 | } 117 | 118 | func (e *Engine) run(ctx context.Context, task TaskInterface) error { 119 | for { 120 | select { 121 | case <-ctx.Done(): 122 | return ctx.Err() 123 | default: 124 | } 125 | 126 | if err := task.Run(ctx); err != nil { 127 | if e.cfg.FailInterval < 0 { 128 | return err 129 | } 130 | logrus.Errorf("执行出错: %v", err) 131 | logrus.Warningf("将在 %dms 后继续执行", e.cfg.FailInterval) 132 | fmt.Println() 133 | time.Sleep(time.Duration(e.cfg.FailInterval) * time.Millisecond) 134 | continue 135 | } 136 | 137 | if e.cfg.SuccessInterval < 0 { 138 | return nil 139 | } 140 | logrus.Infof("执行成功, 将在 %dms 后继续执行", e.cfg.SuccessInterval) 141 | time.Sleep(time.Duration(e.cfg.SuccessInterval) * time.Millisecond) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /core/ddmc/address.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "errors" 21 | "fmt" 22 | "net/http" 23 | "net/url" 24 | ) 25 | 26 | type AddressResult struct { 27 | Success bool `json:"success"` 28 | Code int `json:"code"` 29 | Message string `json:"message"` 30 | Data AddressData `json:"data"` 31 | } 32 | 33 | type Address struct { 34 | Id string `json:"id"` 35 | Name string `json:"name"` 36 | StationId string `json:"station_id"` 37 | CityNumber string `json:"city_number"` 38 | Longitude float64 `json:"longitude"` 39 | Latitude float64 `json:"latitude"` 40 | UserName string `json:"user_name"` 41 | Mobile string `json:"mobile"` 42 | Address string `json:"address"` 43 | AddrDetail string `json:"addr_detail"` 44 | } 45 | 46 | type AddressData struct { 47 | ValidAddress []AddressItem `json:"valid_address"` 48 | InvalidAddress []AddressItem `json:"invalid_address"` 49 | MaxAddressCount int `json:"max_address_count"` 50 | CanAddAddress bool `json:"can_add_address"` 51 | } 52 | 53 | type AddressItem struct { 54 | Id string `json:"id"` 55 | Gender int `json:"gender"` 56 | Mobile string `json:"mobile"` 57 | Location AddressLocation `json:"location"` 58 | Label string `json:"label"` 59 | UserName string `json:"user_name"` 60 | AddrDetail string `json:"addr_detail"` 61 | StationId string `json:"station_id"` 62 | StationName string `json:"station_name"` 63 | IsDefault bool `json:"is_default"` 64 | CityNumber string `json:"city_number"` 65 | InfoStatus int `json:"info_status"` 66 | StationInfo AddressStationInfo `json:"station_info"` 67 | VillageId string `json:"village_id"` 68 | } 69 | 70 | type AddressLocation struct { 71 | TypeCode string `json:"typecode"` 72 | Address string `json:"address"` 73 | Name string `json:"name"` 74 | Location []float64 `json:"location"` 75 | Id string `json:"id"` 76 | } 77 | 78 | type AddressStationInfo struct { 79 | Id string `json:"id"` 80 | Address string `json:"address"` 81 | Name string `json:"name"` 82 | Phone string `json:"phone"` 83 | BusinessTime string `json:"business_time"` 84 | CityName string `json:"city_name"` 85 | CityNumber string `json:"city_number"` 86 | } 87 | 88 | func (s *Session) GetAddress() (map[string]AddressItem, error) { 89 | u, err := url.Parse("https://sunquan.api.ddxq.mobi/api/v1/user/address/") 90 | if err != nil { 91 | return nil, fmt.Errorf("address url parse failed: %v", err) 92 | } 93 | 94 | params := s.buildURLParams(false) 95 | params.Set("source_type", "5") 96 | u.RawQuery = params.Encode() 97 | urlPath := u.String() 98 | 99 | req := s.client.R() 100 | req.SetHeader("Host", "sunquan.api.ddxq.mobi") 101 | resp, err := s.execute(context.Background(), req, http.MethodGet, urlPath, s.cfg.RetryCount) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | var addressResult AddressResult 107 | if err := json.Unmarshal(resp.Body(), &addressResult); err != nil { 108 | return nil, fmt.Errorf("parse response failed: %v", err) 109 | } 110 | if len(addressResult.Data.ValidAddress) == 0 { 111 | return nil, errors.New("未查询到有效收货地址,请前往 app 添加或检查填写的 cookie 是否正确!") 112 | } 113 | 114 | result := make(map[string]AddressItem) 115 | for _, v := range addressResult.Data.ValidAddress { 116 | str := fmt.Sprintf("%s %s %s", v.UserName, v.Location.Address, v.AddrDetail) 117 | result[str] = v 118 | } 119 | return result, nil 120 | } 121 | -------------------------------------------------------------------------------- /core/ddmc/user.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "net/http" 22 | "net/url" 23 | 24 | "github.com/sirupsen/logrus" 25 | ) 26 | 27 | type UserResult struct { 28 | Success bool `json:"success"` 29 | Code int `json:"code"` 30 | Message string `json:"message"` 31 | Data UserData `json:"data"` 32 | } 33 | 34 | type UserData struct { 35 | DoingRefundNum int `json:"doing_refund_num"` 36 | NoCommentOrderPoint int `json:"no_comment_order_point"` 37 | NameNotice string `json:"name_notice"` 38 | NoPayOrderNum int `json:"no_pay_order_num"` 39 | DoingOrderNum int `json:"doing_order_num"` 40 | UserVip UserVIP `json:"user_vip"` 41 | UserSign UserSign `json:"user_sign"` 42 | NotOnionTip int `json:"not_onion_tip"` 43 | NoDrawCouponMoney string `json:"no_draw_coupon_money"` 44 | PointNum int `json:"point_num"` 45 | Balance UserBalance `json:"balance"` 46 | UserInfo UserInfo `json:"user_info"` 47 | CouponNum int `json:"coupon_num"` 48 | NoCommentOrderNum int `json:"no_comment_order_num"` 49 | } 50 | 51 | type UserVIP struct { 52 | IsRenew int `json:"is_renew"` 53 | VipSaveMoneyDescription string `json:"vip_save_money_description"` 54 | VipDescription string `json:"vip_description"` 55 | VipStatus int `json:"vip_status"` 56 | VipNotice string `json:"vip_notice"` 57 | VipExpireTimeDescription string `json:"vip_expire_time_description"` 58 | VipUrl string `json:"vip_url"` 59 | } 60 | 61 | type UserSign struct { 62 | IsTodaySign bool `json:"is_today_sign"` 63 | SignSeries int `json:"sign_series"` 64 | SignText string `json:"sign_text"` 65 | } 66 | 67 | type UserBalance struct { 68 | SetFingerPayPassword int `json:"set_finger_pay_password"` 69 | Balance string `json:"balance"` 70 | SetPayPassword int `json:"set_pay_password"` 71 | } 72 | 73 | type UserInfo struct { 74 | Birthday string `json:"birthday"` 75 | ShowInviteCode bool `json:"show_invite_code"` 76 | NameInCheck string `json:"name_in_check"` 77 | InviteCodeUrl string `json:"invite_code_url"` 78 | Sex int `json:"sex"` 79 | Mobile string `json:"mobile"` 80 | Avatar string `json:"avatar"` 81 | ImUid int `json:"im_uid"` 82 | BindStatus int `json:"bind_status"` 83 | NameStatus int `json:"name_status"` 84 | NewRegister bool `json:"new_register"` 85 | ImSecret string `json:"im_secret"` 86 | Name string `json:"name"` 87 | Id string `json:"id"` 88 | Introduction string `json:"introduction"` 89 | } 90 | 91 | func (s *Session) GetUser() error { 92 | u, err := url.Parse("https://sunquan.api.ddxq.mobi/api/v1/user/detail/") 93 | if err != nil { 94 | return fmt.Errorf("user url parse failed: %v", err) 95 | } 96 | 97 | params := s.buildURLParams(false) 98 | u.RawQuery = params.Encode() 99 | urlPath := u.String() 100 | 101 | req := s.client.R() 102 | req.SetHeader("Host", "sunquan.api.ddxq.mobi") 103 | resp, err := s.execute(context.Background(), req, http.MethodGet, urlPath, s.cfg.RetryCount) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | var userResult UserResult 109 | if err := json.Unmarshal(resp.Body(), &userResult); err != nil { 110 | return fmt.Errorf("parse response failed: %v, body: %v", err, resp.String()) 111 | } 112 | 113 | s.UserID = userResult.Data.UserInfo.Id 114 | logrus.Infof("获取用户信息成功, id: %s, name: %s", s.UserID, userResult.Data.UserInfo.Name) 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /core/ddmc/cart.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "net/http" 22 | "net/url" 23 | ) 24 | 25 | func (s *Session) CartAllCheck(ctx context.Context) error { 26 | u, err := url.Parse("https://maicai.api.ddxq.mobi/cart/allCheck") 27 | if err != nil { 28 | return fmt.Errorf("cart url parse failed: %v", err) 29 | } 30 | 31 | params := s.buildURLParams(true) 32 | params.Set("is_check", "1") 33 | u.RawQuery = params.Encode() 34 | urlPath := u.String() 35 | 36 | req := s.client.R() 37 | req.Header = s.buildHeader() 38 | _, err = s.execute(ctx, req, http.MethodGet, urlPath, s.cfg.RetryCount) 39 | return err 40 | } 41 | 42 | func (s *Session) GetCart(ctx context.Context) (map[string]interface{}, error) { 43 | u, err := url.Parse("https://maicai.api.ddxq.mobi/cart/index") 44 | if err != nil { 45 | return nil, fmt.Errorf("获取购物车商品,请求URL解析失败: %v", err) 46 | } 47 | 48 | params := s.buildURLParams(true) 49 | params.Set("is_load", "1") 50 | params.Set("ab_config", `{"key_onion":"D","key_cart_discount_price":"C"}`) 51 | u.RawQuery = params.Encode() 52 | urlPath := u.String() 53 | 54 | req := s.client.R() 55 | req.Header = s.buildHeader() 56 | resp, err := s.execute(ctx, req, http.MethodGet, urlPath, s.cfg.RetryCount) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | var result map[string]interface{} 62 | if err := json.Unmarshal(resp.Body(), &result); err != nil { 63 | return nil, fmt.Errorf("获取购物车商品,JSON解析失败: %s", resp.String()) 64 | } 65 | 66 | data := result["data"].(map[string]interface{}) 67 | list := data["new_order_product_list"].([]interface{}) 68 | if len(list) == 0 { 69 | return nil, ErrorNoValidProduct 70 | } 71 | 72 | item := list[0].(map[string]interface{}) 73 | 74 | productList := item["products"].([]interface{}) 75 | products := make([]map[string]interface{}, 0, len(productList)) 76 | for _, v := range productList { 77 | product := v.(map[string]interface{}) 78 | product["total_money"] = product["total_price"] 79 | product["total_origin_money"] = product["total_origin_price"] 80 | products = append(products, product) 81 | } 82 | 83 | out := map[string]interface{}{ 84 | "products": products, 85 | "package_type": item["package_type"], 86 | "package_id": item["package_id"], 87 | "total_money": item["total_money"], 88 | "total_origin_money": item["total_origin_money"], 89 | "goods_real_money": item["goods_real_money"], 90 | "total_count": item["total_count"], 91 | "cart_count": item["cart_count"], 92 | "is_presale": item["is_presale"], 93 | "instant_rebate_money": item["instant_rebate_money"], 94 | "coupon_rebate_money": item["coupon_rebate_money"], 95 | "total_rebate_money": item["total_rebate_money"], 96 | "used_balance_money": item["used_balance_money"], 97 | "can_used_balance_money": item["can_used_balance_money"], 98 | "used_point_num": item["used_point_num"], 99 | "used_point_money": item["used_point_money"], 100 | "can_used_point_num": item["can_used_point_num"], 101 | "can_used_point_money": item["can_used_point_money"], 102 | "is_share_station": item["is_share_station"], 103 | "only_today_products": item["only_today_products"], 104 | "only_tomorrow_products": item["only_tomorrow_products"], 105 | "front_package_text": item["front_package_text"], 106 | "front_package_type": item["front_package_type"], 107 | "front_package_stock_color": item["front_package_stock_color"], 108 | "front_package_bg_color": item["front_package_bg_color"], 109 | } 110 | parentOrderInfo, ok := data["parent_order_info"].(map[string]interface{}) 111 | if ok { 112 | out["parent_order_sign"] = parentOrderInfo["parent_order_sign"] 113 | } 114 | return out, nil 115 | } 116 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: DDSHOP-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | go_version: 15 | - 1.17 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: ${{ matrix.go_version }} 23 | 24 | # Cache go build cache, used to speedup go test 25 | - name: Setup Golang caches 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | /go/pkg/.cache/go-build 30 | /go/pkg/mod 31 | key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-golang- 34 | 35 | - name: Setup Golang with cache 36 | uses: magnetikonline/action-golang-cache@v2 37 | with: 38 | go-version: ${{ matrix.go_version }} 39 | 40 | - name: Mod download 41 | run: go mod download 42 | 43 | - run: | 44 | mkdir -p build 45 | 46 | - name: Build with xgo 47 | uses: TianTianBigWang/ghaction-xgo@master 48 | with: 49 | xgo_version: v0.1 50 | go_version: ${{ matrix.go_version }} 51 | pkg: cmd/ddshop 52 | dest: build 53 | prefix: ddshop 54 | targets: windows/amd64,linux/arm64,linux/amd64,darwin/arm64,darwin/amd64 55 | v: true 56 | x: false 57 | race: false 58 | ldflags: -s -w 59 | buildmode: default 60 | 61 | - name: Move 62 | run: | 63 | ls ./build 64 | mkdir -p release 65 | mv $GITHUB_WORKSPACE/build/ddshop-darwin-10.16-amd64 release/ddshop_darwin_amd64 && chmod +x release/ddshop_darwin_amd64 66 | mv $GITHUB_WORKSPACE/build/ddshop-darwin-10.16-arm64 release/ddshop_darwin_arm64 && chmod +x release/ddshop_darwin_arm64 67 | mv $GITHUB_WORKSPACE/build/ddshop-linux-amd64 release/ddshop_linux_amd64 && chmod +x release/ddshop_linux_amd64 68 | mv $GITHUB_WORKSPACE/build/ddshop-windows-4.0-amd64.exe release/ddshop_windows_amd64.exe 69 | cd release 70 | tar -zcvf ddshop_darwin_amd64.tar.gz ddshop_darwin_amd64 71 | tar -zcvf ddshop_darwin_arm64.tar.gz ddshop_darwin_arm64 72 | tar -zcvf ddshop_linux_amd64.tar.gz ddshop_linux_amd64 73 | zip -q -r ddshop_windows_amd64.zip ddshop_windows_amd64.exe 74 | 75 | - name: Release 76 | uses: softprops/action-gh-release@v1 77 | with: 78 | files: | 79 | release/ddshop_linux_amd64.tar.gz 80 | release/ddshop_windows_amd64.zip 81 | release/ddshop_darwin_amd64.tar.gz 82 | release/ddshop_darwin_arm64.tar.gz 83 | 84 | docker-release: 85 | runs-on: ubuntu-latest 86 | strategy: 87 | matrix: 88 | target: 89 | - Dockerfile: build/Dockerfile 90 | steps: 91 | - uses: actions/checkout@v3 92 | 93 | - name: Prepare 94 | id: prepare 95 | run: | 96 | TAG=${GITHUB_REF#refs/tags/} 97 | DATE=$(date +'%Y-%m-%d_%H-%M-%S') 98 | echo ::set-output name=full_tag_name::${TAG} 99 | echo ::set-output name=full_date_tag::${DATE} 100 | echo ::set-output name=latest_tag::latest 101 | 102 | - name: Set up QEMU 103 | uses: docker/setup-qemu-action@v1 104 | 105 | - name: Set up Docker Buildx 106 | id: buildx 107 | uses: docker/setup-buildx-action@master 108 | 109 | - name: Login to Docker Hub 110 | uses: docker/login-action@v1 111 | with: 112 | username: ${{ secrets.DOCKER_USER }} 113 | password: ${{ secrets.DOCKER_SECRET }} 114 | 115 | - name: Build and publish ${{ matrix.target.Dockerfile }} 116 | uses: docker/build-push-action@v2 117 | with: 118 | context: . 119 | push: true 120 | builder: ${{ steps.buildx.outputs.name }} 121 | file: ${{ matrix.target.Dockerfile }} 122 | platforms: linux/amd64,linux/arm64,linux/arm 123 | cache-from: type=gha,scope=${{ github.workflow }} 124 | cache-to: type=gha,mode=max,scope=${{ github.workflow }} 125 | tags: | 126 | zc2638/ddshop:${{ steps.prepare.outputs.full_tag_name }} 127 | zc2638/ddshop:${{ steps.prepare.outputs.full_date_tag }} 128 | zc2638/ddshop:${{ steps.prepare.outputs.latest_tag }} 129 | -------------------------------------------------------------------------------- /core/ddmc/order.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "net/http" 22 | "strings" 23 | 24 | "github.com/google/uuid" 25 | "github.com/tidwall/gjson" 26 | ) 27 | 28 | func (s *Session) CheckOrder(ctx context.Context, cartData map[string]interface{}, reserveTime *ReserveTime) (map[string]interface{}, error) { 29 | urlPath := "https://maicai.api.ddxq.mobi/order/checkOrder" 30 | 31 | packagesInfo := make(map[string]interface{}) 32 | for k, v := range cartData { 33 | packagesInfo[k] = v 34 | } 35 | packagesInfo["reserved_time"] = map[string]interface{}{ 36 | "reserved_time_start": reserveTime.StartTimestamp, 37 | "reserved_time_end": reserveTime.EndTimestamp, 38 | } 39 | packagesJson, err := json.Marshal([]interface{}{packagesInfo}) 40 | if err != nil { 41 | return nil, fmt.Errorf("marshal products info failed: %v", err) 42 | } 43 | 44 | params := s.buildURLParams(true) 45 | params.Add("packages", string(packagesJson)) 46 | params.Add("user_ticket_id", "default") 47 | params.Add("freight_ticket_id", "default") 48 | params.Add("is_use_point", "0") 49 | params.Add("is_use_balance", "0") 50 | params.Add("is_buy_vip", "0") 51 | params.Add("coupons_id", "") 52 | params.Add("is_buy_coupons", "0") 53 | params.Add("check_order_type", "0") 54 | params.Add("is_support_merge_payment", "0") 55 | params.Add("showData", "true") 56 | params.Add("showMsg", "false") 57 | 58 | req := s.client.R() 59 | req.Header = s.buildHeader() 60 | req.SetBody(strings.NewReader(params.Encode())) 61 | resp, err := s.execute(ctx, req, http.MethodPost, urlPath, s.cfg.RetryCount) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | jsonResult := gjson.ParseBytes(resp.Body()) 67 | out := map[string]interface{}{ 68 | "price": jsonResult.Get("data.order.total_money").Str, 69 | "freight_discount_money": jsonResult.Get("data.order.freight_discount_money").Str, // 运费折扣费用 70 | "freight_money": jsonResult.Get("data.order.freight_money").Str, // 运费 71 | "order_freight": jsonResult.Get("data.order.freights.0.freight.freight_real_money").Str, // 订单运费 72 | "user_ticket_id": jsonResult.Get("data.order.default_coupon._id").Str, 73 | } 74 | return out, nil 75 | } 76 | 77 | func (s *Session) CreateOrder( 78 | ctx context.Context, 79 | cartData map[string]interface{}, 80 | checkOrderData map[string]interface{}, 81 | reserveTime *ReserveTime, 82 | ) error { 83 | urlPath := "https://maicai.api.ddxq.mobi/order/addNewOrder" 84 | 85 | paymentOrder := map[string]interface{}{ 86 | "reserved_time_start": reserveTime.StartTimestamp, 87 | "reserved_time_end": reserveTime.EndTimestamp, 88 | "parent_order_sign": cartData["parent_order_sign"], 89 | "address_id": s.Address.Id, 90 | "pay_type": s.PayType, 91 | "product_type": 1, 92 | "form_id": strings.ReplaceAll(uuid.New().String(), "-", ""), 93 | "receipt_without_sku": nil, 94 | "vip_money": "", 95 | "vip_buy_user_ticket_id": "", 96 | "coupons_money": "", 97 | "coupons_id": "", 98 | } 99 | for k, v := range checkOrderData { 100 | paymentOrder[k] = v 101 | } 102 | 103 | packages := map[string]interface{}{ 104 | "reserved_time_start": reserveTime.StartTimestamp, 105 | "reserved_time_end": reserveTime.EndTimestamp, 106 | "products": cartData["products"], 107 | "package_type": cartData["package_type"], 108 | "package_id": cartData["package_id"], 109 | "total_money": cartData["total_money"], 110 | "total_origin_money": cartData["total_origin_money"], 111 | "goods_real_money": cartData["goods_real_money"], 112 | "total_count": cartData["total_count"], 113 | "cart_count": cartData["cart_count"], 114 | "is_presale": cartData["is_presale"], 115 | "instant_rebate_money": cartData["instant_rebate_money"], 116 | "coupon_rebate_money": cartData["coupon_rebate_money"], 117 | "total_rebate_money": cartData["total_rebate_money"], 118 | "used_balance_money": cartData["used_balance_money"], 119 | "can_used_balance_money": cartData["can_used_balance_money"], 120 | "used_point_num": cartData["used_point_num"], 121 | "used_point_money": cartData["used_point_money"], 122 | "can_used_point_num": cartData["can_used_point_num"], 123 | "can_used_point_money": cartData["can_used_point_money"], 124 | "is_share_station": cartData["is_share_station"], 125 | "only_today_products": cartData["only_today_products"], 126 | "only_tomorrow_products": cartData["only_tomorrow_products"], 127 | "front_package_text": cartData["front_package_text"], 128 | "front_package_type": cartData["front_package_type"], 129 | "front_package_stock_color": cartData["front_package_stock_color"], 130 | "front_package_bg_color": cartData["front_package_bg_color"], 131 | "eta_trace_id": "", 132 | "soon_arrival": "", 133 | "first_selected_big_time": 0, 134 | "receipt_without_sku": 0, 135 | } 136 | 137 | packageOrder := map[string]interface{}{ 138 | "payment_order": paymentOrder, 139 | "packages": []interface{}{packages}, 140 | } 141 | packageOrderJson, err := json.Marshal(packageOrder) 142 | if err != nil { 143 | return fmt.Errorf("marshal products info failed: %v", err) 144 | } 145 | 146 | params := s.buildURLParams(true) 147 | params.Add("package_order", string(packageOrderJson)) 148 | params.Add("showData", "true") 149 | params.Add("showMsg", "false") 150 | params.Add("ab_config", `{"key_onion":"C"}`) 151 | 152 | req := s.client.R() 153 | req.Header = s.buildHeader() 154 | req.SetBody(strings.NewReader(params.Encode())) 155 | _, err = s.execute(ctx, req, http.MethodPost, urlPath, s.cfg.RetryCount) 156 | return err 157 | } 158 | -------------------------------------------------------------------------------- /core/ddmc/session.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 zc2638 . 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ddmc 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "math/rand" 22 | "net/http" 23 | "net/url" 24 | "strconv" 25 | "strings" 26 | "time" 27 | 28 | "github.com/AlecAivazis/survey/v2" 29 | "github.com/go-resty/resty/v2" 30 | "github.com/sirupsen/logrus" 31 | "github.com/tidwall/gjson" 32 | 33 | "github.com/zc2638/ddshop/pkg/notice" 34 | ) 35 | 36 | var ( 37 | ErrorNoValidProduct = errors.New("无有效商品") 38 | ErrorNoReserveTime = errors.New("无可预约时间段") 39 | ErrorOutStock = errors.New("部分商品已缺货") 40 | ErrorProductChange = errors.New("商品信息发生变化") 41 | ErrorReserveTimeExpired = errors.New("送达时间已失效") 42 | ) 43 | 44 | func NewSession(cfg *Config, noticeIns notice.Interface) (*Session, error) { 45 | cookie := cfg.Cookie 46 | if !strings.HasPrefix(cookie, "DDXQSESSID=") { 47 | cookie = "DDXQSESSID=" + cookie 48 | } 49 | 50 | header := make(http.Header) 51 | header.Set("Host", "maicai.api.ddxq.mobi") 52 | header.Set("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E217 MicroMessenger/6.8.0(0x16080000) NetType/WIFI Language/en Branch/Br_trunk MiniProgramEnv/Mac") 53 | header.Set("accept", "application/json, text/plain, */*") 54 | header.Set("content-type", "application/x-www-form-urlencoded") 55 | header.Set("origin", "https://wx.m.ddxq.mobi") 56 | header.Set("sec-fetch-site", "same-site") 57 | header.Set("sec-fetch-mode", "cors") 58 | header.Set("sec-fetch-dest", "empty") 59 | header.Set("referer", "https://wx.m.ddxq.mobi/") 60 | header.Set("accept-language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7") 61 | header.Set("cookie", cookie) 62 | 63 | client := resty.New() 64 | client.Header = header 65 | 66 | if cfg.Channel != 3 && cfg.Channel != 4 { 67 | cfg.Channel = 4 68 | } 69 | sess := &Session{ 70 | cfg: cfg, 71 | noticeIns: noticeIns, 72 | client: client, 73 | 74 | apiVersion: "9.49.2", 75 | appVersion: "2.82.0", 76 | channel: "applet", 77 | appClientID: strconv.Itoa(cfg.Channel), 78 | } 79 | 80 | if err := sess.GetUser(); err != nil { 81 | return nil, fmt.Errorf("获取用户信息失败: %v", err) 82 | } 83 | if err := sess.Choose(); err != nil { 84 | return nil, err 85 | } 86 | return sess, nil 87 | } 88 | 89 | type Session struct { 90 | cfg *Config 91 | noticeIns notice.Interface 92 | client *resty.Client 93 | 94 | channel string 95 | apiVersion string 96 | appVersion string 97 | appClientID string 98 | 99 | UserID string 100 | PayType int64 101 | Address *AddressItem 102 | 103 | cartData map[string]interface{} 104 | multiReserveTime []ReserveTime 105 | checkOrderData map[string]interface{} 106 | } 107 | 108 | func (s *Session) Run(ctx context.Context) error { 109 | err := s.run(ctx) 110 | if err != nil { 111 | switch err { 112 | case ErrorNoValidProduct: 113 | sleepInterval := 30 114 | logrus.Errorf("购物车中无有效商品,请先前往app添加或勾选,%d 秒后重试!", sleepInterval) 115 | time.Sleep(time.Duration(sleepInterval) * time.Second) 116 | case ErrorNoReserveTime: 117 | sleepInterval := 3 + rand.Intn(6) 118 | logrus.Warningf("暂无可预约的时间,%d 秒后重试!", sleepInterval) 119 | time.Sleep(time.Duration(sleepInterval) * time.Second) 120 | default: 121 | logrus.Error(err) 122 | } 123 | } 124 | return err 125 | } 126 | 127 | func (s *Session) run(ctx context.Context) error { 128 | if s.cartData == nil { 129 | logrus.Info("=====> 获取购物车中有效商品") 130 | if err := s.CartAllCheck(ctx); err != nil { 131 | return fmt.Errorf("全选购物车商品失败: %v", err) 132 | } 133 | cartData, err := s.GetCart(ctx) 134 | if err != nil { 135 | return err 136 | } 137 | s.cartData = cartData 138 | } 139 | 140 | products := s.cartData["products"].([]map[string]interface{}) 141 | for k, v := range products { 142 | logrus.Infof("[%v] %s 数量:%v 总价:%s", k, v["product_name"], v["count"], v["total_price"]) 143 | } 144 | 145 | if len(s.multiReserveTime) == 0 { 146 | logrus.Info("=====> 获取可预约时间") 147 | multiReserveTime, err := s.GetMultiReserveTime(ctx, products) 148 | if err != nil { 149 | return fmt.Errorf("获取可预约时间失败: %v", err) 150 | } 151 | if len(multiReserveTime) == 0 { 152 | return ErrorNoReserveTime 153 | } 154 | logrus.Infof("发现可用的配送时段!") 155 | s.multiReserveTime = multiReserveTime 156 | } 157 | reserveTime := s.multiReserveTime[0] 158 | 159 | if s.checkOrderData == nil { 160 | logrus.Info("=====> 生成订单信息") 161 | checkOrderData, err := s.CheckOrder(ctx, s.cartData, &reserveTime) 162 | if err != nil { 163 | return fmt.Errorf("检查订单失败: %v", err) 164 | } 165 | s.checkOrderData = checkOrderData 166 | logrus.Infof("订单总金额:%v\n", s.checkOrderData["price"]) 167 | } 168 | 169 | startTime := time.Unix(int64(reserveTime.StartTimestamp), 0).Format("2006/01/02 15:04:05") 170 | endTime := time.Unix(int64(reserveTime.EndTimestamp), 0).Format("2006/01/02 15:04:05") 171 | timeRange := startTime + "——" + endTime 172 | 173 | logrus.Infof("=====> 提交订单中, 预约时间段(%s)", timeRange) 174 | if err := s.CreateOrder(context.Background(), s.cartData, s.checkOrderData, &reserveTime); err != nil { 175 | logrus.Errorf("提交订单(%s)失败: %v", timeRange, err) 176 | return err 177 | } 178 | 179 | if err := s.noticeIns.Notice("抢菜成功", "叮咚买菜 抢菜成功,请尽快支付!"); err != nil { 180 | logrus.Warningf("通知失败: %v", err) 181 | } 182 | return nil 183 | } 184 | 185 | func (s *Session) execute(ctx context.Context, request *resty.Request, method, url string, count int) (*resty.Response, error) { 186 | if ctx != nil { 187 | request.SetContext(ctx) 188 | } 189 | resp, err := request.Execute(method, url) 190 | if err != nil { 191 | return nil, fmt.Errorf("request failed: %v", err) 192 | } 193 | if resp.StatusCode() != http.StatusOK { 194 | return nil, fmt.Errorf("statusCode: %d, body: %s", resp.StatusCode(), resp.String()) 195 | } 196 | 197 | result := gjson.ParseBytes(resp.Body()) 198 | code := result.Get("code").Num 199 | switch code { 200 | case 0: 201 | return resp, nil 202 | case -3000: 203 | msg := result.Get("msg").Str 204 | if count == 0 { 205 | return nil, fmt.Errorf("当前人多拥挤(%v): %s", code, msg) 206 | } 207 | logrus.Warningf("将在 %dms 后重试, 当前人多拥挤(%v): %s", s.cfg.Interval, code, msg) 208 | case -3001, -3100: // -3001创建订单, -3100检查订单 209 | msg := result.Get("tips.limitMsg").Str 210 | if count == 0 { 211 | return nil, fmt.Errorf("当前拥挤(%v): %s", code, msg) 212 | } 213 | 214 | interval := int64(result.Get("tips.duration").Num) 215 | if interval == 0 { 216 | interval = s.cfg.Interval 217 | } 218 | logrus.Warningf("将在 %dms 后重试, 当前人多拥挤(%v): %s", interval, code, msg) 219 | time.Sleep(time.Duration(interval) * time.Millisecond) 220 | case 5001: 221 | s.cartData = nil 222 | s.checkOrderData = nil 223 | return nil, ErrorOutStock 224 | case 5003: 225 | s.cartData = nil 226 | s.checkOrderData = nil 227 | return nil, ErrorProductChange 228 | case 5004: 229 | s.multiReserveTime = nil 230 | return nil, ErrorReserveTimeExpired 231 | default: 232 | s.checkOrderData = nil 233 | return nil, fmt.Errorf("无法识别的状态码: %v", resp.String()) 234 | } 235 | count-- 236 | return s.execute(nil, request, method, url, count) 237 | } 238 | 239 | func (s *Session) buildHeader() http.Header { 240 | header := make(http.Header) 241 | header.Set("ddmc-city-number", s.Address.CityNumber) 242 | header.Set("ddmc-os-version", "undefined") 243 | header.Set("ddmc-channel", s.channel) 244 | header.Set("ddmc-api-version", s.apiVersion) 245 | header.Set("ddmc-build-version", s.appVersion) 246 | header.Set("ddmc-app-client-id", s.appClientID) 247 | header.Set("ddmc-ip", "") 248 | header.Set("ddmc-station-id", s.Address.StationId) 249 | header.Set("ddmc-uid", s.UserID) 250 | if len(s.Address.Location.Location) == 2 { 251 | header.Set("ddmc-longitude", strconv.FormatFloat(s.Address.Location.Location[0], 'f', -1, 64)) 252 | header.Set("ddmc-latitude", strconv.FormatFloat(s.Address.Location.Location[1], 'f', -1, 64)) 253 | } 254 | return header 255 | } 256 | 257 | func (s *Session) buildURLParams(needAddress bool) url.Values { 258 | params := url.Values{} 259 | params.Add("channel", s.channel) 260 | params.Add("api_version", s.apiVersion) 261 | params.Add("app_version", s.appVersion) 262 | params.Add("app_client_id", s.appClientID) 263 | params.Add("applet_source", "") 264 | params.Add("h5_source", "") 265 | params.Add("sharer_uid", "") 266 | params.Add("s_id", "") 267 | params.Add("openid", "") 268 | 269 | params.Add("uid", s.UserID) 270 | if needAddress { 271 | params.Add("address_id", s.Address.Id) 272 | params.Add("station_id", s.Address.StationId) 273 | params.Add("city_number", s.Address.CityNumber) 274 | if len(s.Address.Location.Location) == 2 { 275 | params.Add("longitude", strconv.FormatFloat(s.Address.Location.Location[0], 'f', -1, 64)) 276 | params.Add("latitude", strconv.FormatFloat(s.Address.Location.Location[1], 'f', -1, 64)) 277 | } 278 | } 279 | 280 | params.Add("device_token", "") 281 | params.Add("nars", "") 282 | params.Add("sesi", "") 283 | return params 284 | } 285 | 286 | func (s *Session) Choose() error { 287 | if err := s.chooseAddr(); err != nil { 288 | return err 289 | } 290 | if err := s.choosePay(); err != nil { 291 | return err 292 | } 293 | return nil 294 | } 295 | 296 | func (s *Session) chooseAddr() error { 297 | addrMap, err := s.GetAddress() 298 | if err != nil { 299 | return fmt.Errorf("获取收货地址失败: %v", err) 300 | } 301 | addrs := make([]string, 0, len(addrMap)) 302 | for k := range addrMap { 303 | addrs = append(addrs, k) 304 | } 305 | 306 | if len(addrs) == 1 { 307 | address := addrMap[addrs[0]] 308 | s.Address = &address 309 | logrus.Infof("默认收货地址: %s %s", s.Address.Location.Address, s.Address.AddrDetail) 310 | return nil 311 | } 312 | 313 | var addr string 314 | sv := &survey.Select{ 315 | Message: "请选择收货地址", 316 | Options: addrs, 317 | } 318 | if err := survey.AskOne(sv, &addr); err != nil { 319 | return fmt.Errorf("选择收货地址错误: %v", err) 320 | } 321 | 322 | address, ok := addrMap[addr] 323 | if !ok { 324 | return errors.New("请选择正确的收货地址") 325 | } 326 | s.Address = &address 327 | logrus.Infof("已选择收货地址: %s %s", s.Address.Location.Address, s.Address.AddrDetail) 328 | return nil 329 | } 330 | 331 | const ( 332 | PaymentAlipay = "alipay" 333 | PaymentAlipayStr = "支付宝" 334 | PaymentWechat = "wechat" 335 | PaymentWechatStr = "微信" 336 | ) 337 | 338 | func (s *Session) choosePay() error { 339 | payType := s.cfg.PayType 340 | if payType == "" { 341 | sv := &survey.Select{ 342 | Message: "请选择支付方式", 343 | Options: []string{PaymentWechatStr, PaymentAlipayStr}, 344 | Default: PaymentWechatStr, 345 | } 346 | if err := survey.AskOne(sv, &payType); err != nil { 347 | return fmt.Errorf("选择支付方式错误: %v", err) 348 | } 349 | } 350 | 351 | // 2支付宝,4微信,6小程序支付 352 | switch payType { 353 | case PaymentAlipay, PaymentAlipayStr: 354 | s.PayType = 2 355 | logrus.Info("已选择支付方式:支付宝") 356 | case PaymentWechat, PaymentWechatStr: 357 | s.PayType = 4 358 | logrus.Info("已选择支付方式:微信") 359 | default: 360 | return fmt.Errorf("无法识别的支付方式: %s", payType) 361 | } 362 | return nil 363 | } 364 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/AlecAivazis/survey/v2 v2.3.4 h1:pchTU9rsLUSvWEl2Aq9Pv3k0IE2fkqtGxazskAMd9Ng= 15 | github.com/AlecAivazis/survey/v2 v2.3.4/go.mod h1:hrV6Y/kQCLhIZXGcriDCUBtB3wnN7156gMXJ3+b23xM= 16 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 17 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 18 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 19 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 20 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 21 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 24 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 25 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 26 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 27 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 28 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 29 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 30 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 31 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 32 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 33 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 34 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 35 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 36 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 37 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 38 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 39 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 40 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 41 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 42 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 43 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 44 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 45 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 46 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 47 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 48 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 49 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 50 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 51 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 52 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 53 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 54 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 55 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 56 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 57 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 58 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 59 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 60 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 61 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 63 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 64 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 65 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 71 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 72 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 73 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 74 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 75 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 76 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 77 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 78 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 79 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 80 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 81 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 82 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 83 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 85 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 86 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 87 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 88 | github.com/hajimehoshi/go-mp3 v0.3.3 h1:cWnfRdpye2m9ElSoVqneYRcpt/l3ijttgjMeQh+r+FE= 89 | github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= 90 | github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM= 91 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= 92 | github.com/hajimehoshi/oto/v2 v2.0.3 h1:AlqDUp2bssDzSnvU7knrDmGf6+Eob8BBCEJL/6SZxPQ= 93 | github.com/hajimehoshi/oto/v2 v2.0.3/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= 94 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 95 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 96 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 97 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 98 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 99 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 100 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 101 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 102 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 103 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 104 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 105 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 106 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 107 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 108 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 109 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 110 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 111 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 112 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 113 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 114 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 115 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 116 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 117 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 118 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 119 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 120 | github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 121 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 122 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 123 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 124 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 125 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 126 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 127 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 128 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 129 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 130 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 131 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 132 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 133 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 134 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 135 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 136 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 137 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 138 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 139 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 140 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 141 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 142 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 143 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 144 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 145 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 146 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 147 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 148 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 149 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 150 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= 151 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 152 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 153 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 154 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 155 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 156 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 157 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 158 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 159 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 160 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 161 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 162 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 163 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 164 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 165 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 166 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 167 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 168 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 169 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 170 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 171 | github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a h1:ecwbopPjyxXrRDizIqaaH5S9lxu5YunO56Z+794qa/w= 172 | github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a/go.mod h1:LbHvgTtlRyyaOV6ax3tx/YB0DZdW8o+EVUeLYZhJlPY= 173 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 174 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 175 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 176 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 177 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 178 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 179 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 180 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 181 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 182 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 183 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 184 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 185 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 186 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 187 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 188 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 189 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 190 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 191 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 192 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 193 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 194 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 195 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 196 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 197 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 198 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 199 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 200 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 201 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 202 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 203 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 204 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 205 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 206 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 207 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 208 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 209 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 210 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 211 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 212 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 213 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 214 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 215 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 216 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 217 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 218 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 219 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 220 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 221 | github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= 222 | github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 223 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 224 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 225 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 226 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 227 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 228 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 229 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 230 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 231 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 232 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 233 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 234 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 235 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 236 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 237 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 238 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 239 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 240 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 241 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 242 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 243 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 244 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 245 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 246 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 247 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 248 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 249 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 250 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 251 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 252 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 253 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 254 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 255 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 256 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 257 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 258 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 259 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 260 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 261 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 262 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 263 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 264 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 265 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 266 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 270 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 271 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 272 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 273 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 274 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 275 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 276 | golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= 277 | golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 278 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 279 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 280 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 281 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 283 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 285 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 286 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 287 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 288 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 289 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 290 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 291 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 293 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 294 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20201027140754-0fcbb8f4928c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 306 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 307 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 308 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 309 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= 310 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 311 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 312 | golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= 313 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 314 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 315 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 316 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 317 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 318 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 319 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 320 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 321 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 322 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 323 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 324 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 325 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 326 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 327 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 328 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 329 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 330 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 331 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 332 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 333 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 334 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 335 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 336 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 337 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 338 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 339 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 340 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 341 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 342 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 343 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 344 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 345 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 346 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 347 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 348 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 349 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 350 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 351 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 352 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 353 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 354 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 355 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 356 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 357 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 358 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 359 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 360 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 361 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 362 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 363 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 364 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 365 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 366 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 367 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 368 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 369 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 370 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 371 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 372 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 373 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 374 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 375 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 376 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 377 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 378 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 379 | gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= 380 | gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= 381 | gorm.io/gorm v1.20.5/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= 382 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 383 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 384 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 385 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 386 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 387 | --------------------------------------------------------------------------------