Repository: CatchZeng/dingtalk
Branch: master
Commit: f4f1df9b5f30
Files: 50
Total size: 118.2 KB
Directory structure:
gitextract_cg7u8c_q/
├── .editorconfig
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .vscode/
│ └── settings.json
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── READMEEN.md
├── build/
│ └── package/
│ ├── Dockerfile
│ └── build.sh
├── cmd/
│ └── dingtalk/
│ ├── actionCard.go
│ ├── actionCard_test.go
│ ├── feedCard.go
│ ├── feedCard_test.go
│ ├── link.go
│ ├── link_test.go
│ ├── markdown.go
│ ├── markdown_test.go
│ ├── root.go
│ ├── root_test.go
│ ├── text.go
│ ├── text_test.go
│ ├── version.go
│ └── version_test.go
├── configs/
│ ├── app.go
│ ├── configs.go
│ └── configs_test.go
├── go.mod
├── go.sum
├── internal/
│ └── security/
│ ├── security.go
│ └── security_test.go
├── main.go
├── pkg/
│ └── dingtalk/
│ ├── actionCard.go
│ ├── actionCard_test.go
│ ├── client.go
│ ├── client_test.go
│ ├── feedCard.go
│ ├── feedCard_test.go
│ ├── link.go
│ ├── link_test.go
│ ├── markdown.go
│ ├── markdown_test.go
│ ├── message.go
│ ├── text.go
│ └── text_test.go
├── scripts/
│ ├── mock.sh
│ └── test.sh
├── sonar-project.properties
└── test/
└── mocks/
└── message/
└── message.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.go]
indent_style = tab
indent_size = 2
[{Makefile, Dockerfile}]
indent_style = tab
indent_size = 4
[*.{yml, yaml, json}]
indent_style = space
indent_size = 2
================================================
FILE: .github/workflows/go.yml
================================================
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.16
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
make mod
- name: Test
run: make test
- name: codecoe
run: bash <(curl -s https://codecov.io/bash)
================================================
FILE: .gitignore
================================================
dingtalk-darwin-amd64.zip
dingtalk-darwin-arm64.zip
dingtalk-linux-amd64.zip
dingtalk-windows-386.zip
dingtalk-windows-amd64.zip
dingtalk.exe
.idea
coverage.txt
coverage.data
.scannerwork
================================================
FILE: .vscode/settings.json
================================================
{
"cSpell.words": ["btns", "dingtalk", "gomock", "mitchellh", "OAPI", "Unpatch"]
}
================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG
## v1.5.0
### Added
- Support the environment variable prefix
## v1.4.0
### Added
- Support environment variables
### Refactor
- update to golang 1.18.1
## v1.2.0
### Refactor
- project layout
### Added
- support config.xml
- validate function
- .editconfig file
## v1.1.1
### Fixed
- Fix release response body resource, see more at <https://github.com/CatchZeng/dingtalk/pull/1>.
- Fix bug about timestamp, see more at <https://github.com/CatchZeng/dingtalk/pull/2>.
## v1.1.0
### Added
- actionCard
- feedCard
## v1.0.0
### Added
- text
- link
- markdown
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2020 Catch Zeng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
SHELL := /bin/bash
BASEDIR = $(shell pwd)
APP_NAME=dingtalk
APP_VERSION=1.5.0
IMAGE_NAME="catchzeng/${APP_NAME}:${APP_VERSION}"
IMAGE_LATEST="catchzeng/${APP_NAME}:latest"
all: mod fmt imports lint test
first:
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
fmt:
gofmt -w .
mod:
go mod tidy
imports:
goimports -w .
lint:
golangci-lint run
.PHONY: test
test:
sh scripts/test.sh
test-sonar:
go test -gcflags=-l -coverpkg=./... -coverprofile=coverage.data ./...
mock:
sh scripts/mock.sh
.PHONY: build
build:
rm -f dingtalk
go build -o dingtalk main.go
build-darwin-amd64:
rm -f dingtalk dingtalk-darwin-amd64.zip
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dingtalk main.go
zip dingtalk-darwin-amd64.zip dingtalk
build-darwin-arm64:
rm -f dingtalk dingtalk-darwin-arm64.zip
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o dingtalk main.go
zip dingtalk-darwin-arm64.zip dingtalk
build-linux:
rm -f dingtalk dingtalk-linux-amd64.zip
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dingtalk main.go
zip dingtalk-linux-amd64.zip dingtalk
build-win:
rm -f dingtalk.exe dingtalk-windows-amd64.zip
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dingtalk.exe main.go
zip dingtalk-windows-amd64.zip dingtalk.exe
build-win32:
rm -f dingtalk.exe dingtalk-windows-386.zip
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o dingtalk.exe main.go
zip dingtalk-windows-386.zip dingtalk.exe
build-release:
make build-darwin-amd64
make build-darwin-arm64
make build-linux
make build-win
make build-win32
rm -f dingtalk dingtalk.exe
build-docker:
sh build/package/build.sh ${IMAGE_NAME}
push-docker:
docker tag ${IMAGE_NAME} ${IMAGE_LATEST};
docker push ${IMAGE_NAME};
docker push ${IMAGE_LATEST};
help:
@echo "first - first time"
@echo "fmt - go format"
@echo "mod - go mod tidy"
@echo "imports - go imports"
@echo "lint - run golangci-lint"
@echo "test - unit test"
@echo "mock - mockgen"
@echo "build - build binary"
@echo "build-mac - build mac binary"
@echo "build-linux - build linux amd64 binary"
@echo "build-win - build win amd64 binary"
@echo "build-win32 - build win 386 binary"
@echo "build-docker - build docker image"
@echo "push-docker - push docker image to docker hub"
================================================
FILE: README.md
================================================
# dingtalk

[](https://codecov.io/gh/CatchZeng/dingtalk)
[](https://goreportcard.com/report/github.com/CatchZeng/dingtalk)
[](https://github.com/CatchZeng/dingtalk/releases)
[](https://pkg.go.dev/github.com/CatchZeng/dingtalk?tab=doc)
[English](https://github.com/CatchZeng/dingtalk/blob/master/READMEEN.md)
> DingTalk(dingding) 是钉钉机器人的 go 实现。支持 **Docker、Jenkinsfile、命令行模式,module 模式**;支持**加签**安全设置,支持**链式语法**创建消息;支持**文本、链接、Markdown、ActionCard、FeedCard** 消息类型。
> 注:使用飞书的小伙伴,可以使用[飞书(feishu)版](https://github.com/CatchZeng/feishu)。
## 文档
[钉钉文档](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq)
## 特性
- [x] 支持[Docker](https://github.com/CatchZeng/dingtalk#Docker)
- [x] 支持[Jenkinsfile](https://github.com/CatchZeng/dingtalk#Jenkinsfile)
- [x] 支持[module](https://github.com/CatchZeng/dingtalk#%E4%BD%9C%E4%B8%BA-module)
- [x] 支持[命令行模式](https://github.com/CatchZeng/dingtalk#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7)
- [x] 支持[配置文件](https://github.com/CatchZeng/dingtalk#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)
- [x] 支持[环境变量](https://github.com/CatchZeng/dingtalk#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)
- [x] 支持加签
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1572261283991-f8e35f4d-6997-4a02-9704-843ee8f97464.png" width = 50% />
- [x] Text 消息
<img src="https://img.alicdn.com/tfs/TB1jFpqaRxRMKJjy0FdXXaifFXa-497-133.png" width = 50% />
- [x] Link 消息
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1570679827267-6243216b-d1c3-48b7-9b1e-0f0b4211b50b.png" width = 50% />
- [x] Markdown 消息
<img src="https://img.alicdn.com/tfs/TB1yL3taUgQMeJjy0FeXXXOEVXa-492-380.png" width = 50% />
- [x] ActionCard 消息
<img src="https://img.alicdn.com/tfs/TB1nhWCiBfH8KJjy1XbXXbLdXXa-547-379.png" width = 50% />
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1570679939723-c1fb7861-5bcb-4c30-9e1b-033932f6b72f.png" width = 50% />
- [x] FeedCard 消息
<img src="http://img01.taobaocdn.com/top/i1/LB1R2evQVXXXXXDapXXXXXXXXXX" width = 50% />
## 安装
## Docker 安装
```shell
docker pull catchzeng/dingtalk
```
### 二进制安装
到 [releases](https://github.com/CatchZeng/dingtalk/releases/) 下载相应平台的二进制可执行文件,然后加入到 PATH 环境变量即可。
### go install 安装
```sh
# Go 1.16+
go install github.com/CatchZeng/dingtalk@v1.5.0
# Go version < 1.16
go get -u github.com/CatchZeng/dingtalk@v1.5.0
```
## 使用方法
### 配置文件
可以在 `$/HOME/.dingtalk` 下创建 `config.yaml` 填入 `access_token` 和 `secret` 默认值。
```yaml
access_token: "1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
secret: "SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
```
### 环境变量
```sh
$ export ACCESS_TOKEN="1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
$ export SECRET="SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
$ dingtalk link -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a
```
你也可以为环境变量设置一个**前缀**
```sh
$ export DINGTALK_ENV_PREFIX="DINGTALK_"
$ export DINGTALK_ACCESS_TOKEN="1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
$ export DINGTALK_SECRET="SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
$ dingtalk link -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a
```
### Docker
```shell
docker run catchzeng/dingtalk dingtalk text -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68 -c "docker test"
```
### Jenkinsfile
```shell
pipeline {
agent {
docker {
image 'catchzeng/dingtalk:latest'
}
}
environment {
DING_TOKEN = '1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f'
DING_SECRET = 'SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68'
}
stages {
stage('notify') {
steps {
sh 'dingtalk link -t ${DING_TOKEN} -s ${DING_SECRET} -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a'
}
}
}
}
```
### 作为 module
```sh
go get github.com/CatchZeng/dingtalk
```
```go
package main
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
)
func main() {
accessToken := "1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
secret := "SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
client := dingtalk.NewClient(accessToken, secret)
msg := dingtalk.NewTextMessage().SetContent("测试文本&at 某个人").SetAt([]string{"177010xxx60"}, false)
client.Send(msg)
}
```
### 命令行工具
#### Demo
```shell
$ dingtalk text -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68 -c "测试命令行 & at 某个人" -m "177010xxx60","177010xxx61"
```
```shell
$ dingtalk markdown -D -i "杭州天气" -e '## 杭州天气 @150XXXXXXXX
> 9度,西北风1级,空气良89,相对温度73%
> 
> ###### 10点20分发布 [天气](https://www.dingtalk.com)' -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68
{"msgtype":"markdown","markdown":{"title":"杭州天气","text":"## 杭州天气 @150XXXXXXXX\n \u003e 9度,西北风1级,空气良89,相对温度73%\n \u003e \n \u003e ###### 10点20分发布 [天气](https://www.dingtalk.com)"},"at":{"atMobiles":[],"isAtAll":false}}
```
> -D 参数:打印发送的消息内容
#### Help
```shell
$ dingtalk -h
dingtalk is a command line tool for DingTalk
Usage:
dingtalk [command]
Available Commands:
actionCard send actionCard message with DingTalk robot
feedCard send feedCard message with DingTalk robot
help Help about any command
link send link message with DingTalk robot
markdown send markdown message with DingTalk robot
text send text message with DingTalk robot
version dingtalk version
Flags:
-t, --access_token string access_token
-m, --atMobiles strings atMobiles
-D, --debug debug
-h, --help help for dingtalk
-a, --isAtAll isAtAll
-s, --secret string secret
Use "dingtalk [command] --help" for more information about a command.
```
## Stargazers
[](https://starchart.cc/CatchZeng/dingtalk)
================================================
FILE: READMEEN.md
================================================
# dingtalk

[](https://codecov.io/gh/CatchZeng/dingtalk)
[](https://goreportcard.com/report/github.com/CatchZeng/dingtalk)
[](https://github.com/CatchZeng/dingtalk/releases)
[](https://pkg.go.dev/github.com/CatchZeng/dingtalk?tab=doc)
[中文](https://github.com/CatchZeng/dingtalk/blob/master/README.md)
> DingTalk (dingding) is the go implementation of the DingTalk robot. Support **Docker, Jenkinsfile,command line mode, module mode**, **signature security settings, chain syntax** to create messages, support **text, link, markdown、ActionCard、FeedCard** message types.
## Doc
[ding-doc](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq)
## Feature
- [x] Support [Docker](https://github.com/CatchZeng/dingtalk#Docker)
- [x] Support [Jenkinsfile](https://github.com/CatchZeng/dingtalk#Jenkinsfile)
- [x] Support [module](https://github.com/CatchZeng/dingtalk/blob/master/READMEEN.md#use-as-module)
- [x] Support [Command Line Mode](https://github.com/CatchZeng/dingtalk/blob/master/READMEEN.md#use-as-command-line-tool)
- [x] Support [config.yaml](https://github.com/CatchZeng/dingtalk/blob/master/READMEEN.md#config.yaml)
- [x] Support [environment variables](https://github.com/CatchZeng/dingtalk#environment%20variables)
- [x] Support sign
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1572261283991-f8e35f4d-6997-4a02-9704-843ee8f97464.png" width = 50% />
- [x] Text message
<img src="https://img.alicdn.com/tfs/TB1jFpqaRxRMKJjy0FdXXaifFXa-497-133.png" width = 50% />
- [x] Link message
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1570679827267-6243216b-d1c3-48b7-9b1e-0f0b4211b50b.png" width = 50% />
- [x] Markdown message
<img src="https://img.alicdn.com/tfs/TB1yL3taUgQMeJjy0FeXXXOEVXa-492-380.png" width = 50% />
- [x] ActionCard message
<img src="https://img.alicdn.com/tfs/TB1nhWCiBfH8KJjy1XbXXbLdXXa-547-379.png" width = 50% />
<img src="https://dingtalkdoc.oss-cn-beijing.aliyuncs.com/images/0.0.210/1570679939723-c1fb7861-5bcb-4c30-9e1b-033932f6b72f.png" width = 50% />
- [x] FeedCard message
<img src="http://img01.taobaocdn.com/top/i1/LB1R2evQVXXXXXDapXXXXXXXXXX" width = 50% />
## Install
## with Docker
```shell
docker pull catchzeng/dingtalk
```
### binary
Go to [releases](https://github.com/CatchZeng/dingtalk/releases/) to download the binary executable file of the corresponding platform, and then add it to the PATH environment variable.
### with go install
```sh
# Go 1.16+
go install github.com/CatchZeng/dingtalk@v1.5.0
# Go version < 1.16
go get -u github.com/CatchZeng/dingtalk@v1.5.0
```
## Usage
### config.yaml
You can create `config.yaml` under `$/HOME/.dingtalk` and fill in the default values of `access_token` and `secret`.
```yaml
access_token: "1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
secret: "SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
```
### environment variables
```sh
$ export ACCESS_TOKEN=1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f
$ export SECRET=SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68
$ dingtalk link -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a
```
You can also set a **prefix** for environment variables.
```sh
$ export DINGTALK_ENV_PREFIX="DINGTALK_"
$ export DINGTALK_ACCESS_TOKEN="1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
$ export DINGTALK_SECRET="SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
$ dingtalk link -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a
```
### Docker
```shell
docker run catchzeng/dingtalk dingtalk text -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68 -c "docker test"
```
### Jenkinsfile
```shell
pipeline {
agent {
docker {
image 'catchzeng/dingtalk:latest'
}
}
environment {
DING_TOKEN = '1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f'
DING_SECRET = 'SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68'
}
stages {
stage('notify') {
steps {
sh 'dingtalk link -t ${DING_TOKEN} -s ${DING_SECRET} -i "标题" -e "信息" -u "https://makeoptim.com/" -p "https://makeoptim.com/assets/img/logo.png" -a'
}
}
}
}
```
### Use as module
```sh
go get github.com/CatchZeng/dingtalk
```
```go
package main
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
)
func main() {
accessToken := "1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f"
secret := "SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68"
client := dingtalk.NewClient(accessToken, secret)
msg := dingtalk.NewTextMessage().SetContent("测试文本&at 某个人").SetAt([]string{"177010xxx60"}, false)
client.Send(msg)
}
```
### Use as command line tool
#### Demo
```shell
dingtalk text -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68 -c "测试命令行 & at 某个人" -m "177010xxx60","177010xxx61"
```
```shell
$ dingtalk markdown -D -i "杭州天气" -e '## 杭州天气 @150XXXXXXXX
> 9度,西北风1级,空气良89,相对温度73%
> 
> ###### 10点20分发布 [天气](https://www.dingtalk.com)' -t 1c53e149ba5de6597cxxxxxx0e901fdxxxxxx80b8ac141e4a75afdc44c85ca4f -s SECb90923e19e58b466481e9e7b7a5bxxxxxx4531axxxxxxad3967fb29f0eae5c68
{"msgtype":"markdown","markdown":{"title":"杭州天气","text":"## 杭州天气 @150XXXXXXXX\n \u003e 9度,西北风1级,空气良89,相对温度73%\n \u003e \n \u003e ###### 10点20分发布 [天气](https://www.dingtalk.com)"},"at":{"atMobiles":[],"isAtAll":false}}
```
> -D: print the message content
#### Help
```shell
dingtalk is a command line tool for DingTalk
Usage:
dingtalk [command]
Available Commands:
actionCard send actionCard message with DingTalk robot
feedCard send feedCard message with DingTalk robot
help Help about any command
link send link message with DingTalk robot
markdown send markdown message with DingTalk robot
text send text message with DingTalk robot
version dingtalk version
Flags:
-t, --access_token string access_token
-m, --atMobiles strings atMobiles
-D, --debug debug
-h, --help help for dingtalk
-a, --isAtAll isAtAll
-s, --secret string secret
Use "dingtalk [command] --help" for more information about a command.
```
## Stargazers
[](https://starchart.cc/CatchZeng/dingtalk)
================================================
FILE: build/package/Dockerfile
================================================
FROM golang:1.18.1-alpine as builder
RUN mkdir /build
ADD . /build/
WORKDIR /build
RUN go mod tidy && go build -o dingtalk main.go
FROM alpine:3.7
COPY --from=builder /build/dingtalk /usr/local/bin/dingtalk
RUN chmod +x /usr/local/bin/dingtalk
================================================
FILE: build/package/build.sh
================================================
#!/bin/bash
shell_dir=$(dirname $0)
cd ${shell_dir}
# check params
if [[ ! $1 ]]; then
echo "image tag is null"; exit 1;
else
echo "image tag: $1"
fi
cd ../../
docker build -t $1 -f build/package/Dockerfile .
================================================
FILE: cmd/dingtalk/actionCard.go
================================================
package dingtalk
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
var actionCardCmd = &cobra.Command{
Use: "actionCard",
Short: "send actionCard message with DingTalk robot",
Long: `send actionCard message with DingTalk robot`,
Args: cobra.MinimumNArgs(0),
Run: runActionCardCmd,
}
func runActionCardCmd(_ *cobra.Command, args []string) {
if len(actionCardVars.Title) < 1 {
log.Fatal("title can not be empty")
return
}
if len(actionCardVars.Text) < 1 {
log.Fatal("text can not be empty")
return
}
var isOverallJump = false
if len(actionCardVars.SingleTitle) < 1 {
if len(btnTitles) < 1 {
log.Fatal("btns can not be empty when singleTitle is empty")
return
}
} else {
isOverallJump = true
if len(actionCardVars.SingleURL) < 1 {
log.Fatal("singleURL can not be empty")
return
}
}
client, err := newClient()
if err != nil {
log.Fatal(err.Error())
return
}
msg := dingtalk.NewActionCardMessage()
if isOverallJump {
msg.SetOverallJump(
actionCardVars.Title,
actionCardVars.Text,
actionCardVars.SingleTitle,
actionCardVars.SingleURL,
actionCardVars.BtnOrientation,
actionCardVars.HideAvatar)
} else {
if len(btnTitles) != len(btnActionURLs) {
log.Fatal("btnTitles & btnActionURLs count must be equal")
return
}
for i := 0; i < len(btnTitles); i++ {
actionCardVars.Btns = append(actionCardVars.Btns, dingtalk.Btn{
Title: btnTitles[i],
ActionURL: btnActionURLs[i],
})
}
msg.SetIndependentJump(
actionCardVars.Title,
actionCardVars.Text,
actionCardVars.Btns,
actionCardVars.BtnOrientation,
actionCardVars.HideAvatar)
}
req, _, err := client.Send(msg)
if debug {
log.Print(req)
}
if err != nil {
log.Fatal(err.Error())
}
}
var actionCardVars dingtalk.ActionCard
var btnTitles, btnActionURLs []string
func init() {
rootCmd.AddCommand(actionCardCmd)
actionCardCmd.Flags().StringVarP(&actionCardVars.Title, "title", "i", "", "title")
actionCardCmd.Flags().StringVarP(&actionCardVars.Text, "text", "e", "", "text")
actionCardCmd.Flags().StringVarP(&actionCardVars.SingleTitle, "singleTitle", "n", "", "singleTitle")
actionCardCmd.Flags().StringVarP(&actionCardVars.SingleURL, "singleURL", "u", "", "singleURL")
actionCardCmd.Flags().StringSliceVarP(&btnTitles, "btnTitles", "b", []string{}, "btnTitles")
actionCardCmd.Flags().StringSliceVarP(&btnActionURLs, "btnActionURLs", "c", []string{}, "btnActionURLs")
actionCardCmd.Flags().StringVarP(&actionCardVars.BtnOrientation, "btnOrientation", "o", "", "btnOrientation")
actionCardCmd.Flags().StringVarP(&actionCardVars.HideAvatar, "hideAvatar", "d", "", "hideAvatar")
}
================================================
FILE: cmd/dingtalk/actionCard_test.go
================================================
package dingtalk
import (
"bytes"
"errors"
"log"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
func Test_runActionCardCmd(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
t.Run("title is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "title can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runActionCardCmd() = %v, want %v", got, want)
}
})
t.Run("text is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
actionCardVars.Title = "123"
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "text can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runActionCardCmd() = %v, want %v", got, want)
}
})
t.Run("singleTitle is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
actionCardVars.Title = "123"
actionCardVars.Text = "123"
actionCardVars.SingleTitle = ""
btnTitles = []string{}
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "btns can not be empty when singleTitle is empty"
if !strings.Contains(got, want) {
t.Errorf("runActionCardCmd() = %v, want %v", got, want)
}
})
t.Run("singleTitle is not empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
actionCardVars.Title = "123"
actionCardVars.Text = "123"
actionCardVars.SingleTitle = "123"
actionCardVars.SingleURL = ""
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "singleURL can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runActionCardCmd() = %v, want %v", got, want)
}
})
t.Run("new client error", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
actionCardVars.Title = "123"
actionCardVars.Text = "123"
actionCardVars.SingleTitle = "123"
actionCardVars.SingleURL = "123"
msg := "new client error"
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return nil, errors.New(msg)
})
defer monkey.Unpatch(newClient)
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runActionCardCmd() = %v, want %v", got, msg)
}
})
t.Run("btnTitles & btnActionURLs different count ", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
actionCardVars.Title = "123"
actionCardVars.Text = "123"
actionCardVars.SingleTitle = ""
actionCardVars.SingleURL = ""
btnTitles = []string{"1"}
btnActionURLs = []string{"1", "2"}
msg := "btnTitles & btnActionURLs count must be equal"
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runActionCardCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runActionCardCmd() = %v, want %v", got, msg)
}
})
t.Run("client send", func(t *testing.T) {
actionCardVars.Title = "123"
actionCardVars.Text = "123"
actionCardVars.SingleTitle = "123"
actionCardVars.SingleURL = "123"
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runActionCardCmd(&cobra.Command{}, []string{})
})
}
================================================
FILE: cmd/dingtalk/feedCard.go
================================================
package dingtalk
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
var feedCardCmd = &cobra.Command{
Use: "feedCard",
Short: "send feedCard message with DingTalk robot",
Long: `send feedCard message with DingTalk robot`,
Args: cobra.MinimumNArgs(0),
Run: runFeedCardCmd,
}
func runFeedCardCmd(_ *cobra.Command, args []string) {
if len(feedCardVars.titles) < 1 || len(feedCardVars.picURLs) < 1 || len(feedCardVars.messageURLs) < 1 {
log.Fatal("titles & picURLs & messageURLs can not be empty")
return
}
if len(feedCardVars.titles) == len(feedCardVars.picURLs) && len(feedCardVars.picURLs) == len(feedCardVars.messageURLs) {
client, err := newClient()
if err != nil {
log.Fatal(err.Error())
return
}
msg := dingtalk.NewFeedCardMessage()
for i := 0; i < len(feedCardVars.titles); i++ {
msg.AppendLink(feedCardVars.titles[i], feedCardVars.messageURLs[i], feedCardVars.picURLs[i])
}
req, _, err := client.Send(msg)
if debug {
log.Print(req)
}
if err != nil {
log.Fatal(err.Error())
return
}
} else {
log.Fatal("titles & picURLs & messageURLs count must be equal")
}
}
// FeedCardVars struct
type FeedCardVars struct {
titles []string
picURLs []string
messageURLs []string
}
var feedCardVars FeedCardVars
func init() {
rootCmd.AddCommand(feedCardCmd)
feedCardCmd.Flags().StringSliceVarP(&feedCardVars.titles, "titles", "i", []string{}, "titles")
feedCardCmd.Flags().StringSliceVarP(&feedCardVars.picURLs, "picURLs", "p", []string{}, "picURLs")
feedCardCmd.Flags().StringSliceVarP(&feedCardVars.messageURLs, "messageURLs", "u", []string{}, "messageURLs")
}
================================================
FILE: cmd/dingtalk/feedCard_test.go
================================================
package dingtalk
import (
"bytes"
"errors"
"log"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
func Test_runFeedCardCmd(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
const emptyMsg = "titles & picURLs & messageURLs can not be empty"
const differentCountMsg = "titles & picURLs & messageURLs count must be equal"
t.Run("titles is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runFeedCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := emptyMsg
if !strings.Contains(got, want) {
t.Errorf("runFeedCardCmd() = %v, want %v", got, want)
}
})
t.Run("titles & picURLs different count", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
feedCardVars.titles = []string{"1", "2"}
feedCardVars.picURLs = []string{"1"}
feedCardVars.messageURLs = []string{"1"}
runFeedCardCmd(&cobra.Command{}, []string{})
got := buf.String()
want := differentCountMsg
if !strings.Contains(got, want) {
t.Errorf("runFeedCardCmd() = %v, want %v", got, want)
}
})
t.Run("new client error", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
feedCardVars.titles = []string{"1"}
feedCardVars.picURLs = []string{"1"}
feedCardVars.messageURLs = []string{"1"}
msg := "new client error"
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return nil, errors.New(msg)
})
defer monkey.Unpatch(newClient)
runFeedCardCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runFeedCardCmd() = %v, want %v", got, msg)
}
})
t.Run("client send", func(t *testing.T) {
feedCardVars.titles = []string{"1"}
feedCardVars.picURLs = []string{"1"}
feedCardVars.messageURLs = []string{"1"}
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runFeedCardCmd(&cobra.Command{}, []string{})
})
}
================================================
FILE: cmd/dingtalk/link.go
================================================
package dingtalk
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
var linkCmd = &cobra.Command{
Use: "link",
Short: "send link message with DingTalk robot",
Long: `send link message with DingTalk robot`,
Args: cobra.MinimumNArgs(0),
Run: runLinkCmd,
}
func runLinkCmd(_ *cobra.Command, args []string) {
if len(linkVars.title) < 1 {
log.Fatal("title can not be empty")
return
}
if len(linkVars.text) < 1 {
log.Fatal("text can not be empty")
return
}
if len(linkVars.messageURL) < 1 {
log.Fatal("messageURL can not be empty")
return
}
client, err := newClient()
if err != nil {
log.Fatal(err.Error())
return
}
msg := dingtalk.NewLinkMessage().
SetLink(linkVars.title, linkVars.text, linkVars.picURL, linkVars.messageURL)
req, _, err := client.Send(msg)
if debug {
log.Print(req)
}
if err != nil {
log.Fatal(err.Error())
}
}
// LinkVars struct
type LinkVars struct {
title string
text string
picURL string
messageURL string
}
var linkVars LinkVars
func init() {
rootCmd.AddCommand(linkCmd)
linkCmd.Flags().StringVarP(&linkVars.title, "title", "i", "", "title")
linkCmd.Flags().StringVarP(&linkVars.text, "text", "e", "", "text")
linkCmd.Flags().StringVarP(&linkVars.picURL, "picURL", "p", "", "picURL")
linkCmd.Flags().StringVarP(&linkVars.messageURL, "messageURL", "u", "", "messageURL")
}
================================================
FILE: cmd/dingtalk/link_test.go
================================================
package dingtalk
import (
"bytes"
"errors"
"log"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
func Test_runLinkCmd(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
t.Run("title is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runLinkCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "title can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runLinkCmd() = %v, want %v", got, want)
}
})
t.Run("text is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
linkVars.title = "123"
runLinkCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "text can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runLinkCmd() = %v, want %v", got, want)
}
})
t.Run("messageURL is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
linkVars.title = "123"
linkVars.text = "123"
runLinkCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "messageURL can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runLinkCmd() = %v, want %v", got, want)
}
})
t.Run("new client error", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
linkVars.title = "123"
linkVars.text = "123"
linkVars.messageURL = "123"
msg := "new client error"
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return nil, errors.New(msg)
})
defer monkey.Unpatch(newClient)
runLinkCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runLinkCmd() = %v, want %v", got, msg)
}
})
t.Run("client send", func(t *testing.T) {
linkVars.title = "123"
linkVars.text = "123"
linkVars.messageURL = "123"
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runLinkCmd(&cobra.Command{}, []string{})
})
}
================================================
FILE: cmd/dingtalk/markdown.go
================================================
package dingtalk
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
var markdownCmd = &cobra.Command{
Use: "markdown",
Short: "send markdown message with DingTalk robot",
Long: `send markdown message with DingTalk robot`,
Args: cobra.MinimumNArgs(0),
Run: runMarkdownCmd,
}
func runMarkdownCmd(_ *cobra.Command, args []string) {
if len(markdownVars.title) < 1 {
log.Fatal("title can not be empty")
return
}
if len(markdownVars.text) < 1 {
log.Fatal("text can not be empty")
return
}
client, err := newClient()
if err != nil {
log.Fatal(err.Error())
return
}
msg := dingtalk.NewMarkdownMessage().
SetMarkdown(markdownVars.title, markdownVars.text).
SetAt(atMobiles, isAtAll)
req, _, err := client.Send(msg)
if debug {
log.Print(req)
}
if err != nil {
log.Fatal(err.Error())
}
}
// MarkdownVars struct
type MarkdownVars struct {
title string
text string
}
var markdownVars MarkdownVars
func init() {
rootCmd.AddCommand(markdownCmd)
markdownCmd.Flags().StringVarP(&markdownVars.title, "title", "i", "", "title")
markdownCmd.Flags().StringVarP(&markdownVars.text, "text", "e", "", "text")
}
================================================
FILE: cmd/dingtalk/markdown_test.go
================================================
package dingtalk
import (
"bytes"
"errors"
"log"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
func Test_runMarkdownCmd(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
t.Run("title is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runMarkdownCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "title can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runMarkdownCmd() = %v, want %v", got, want)
}
})
t.Run("text is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
markdownVars.title = "123"
runMarkdownCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "text can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runMarkdownCmd() = %v, want %v", got, want)
}
})
t.Run("new client error", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
markdownVars.title = "123"
markdownVars.text = "123"
msg := "new client error"
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return nil, errors.New(msg)
})
defer monkey.Unpatch(newClient)
runMarkdownCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runMarkdownCmd() = %v, want %v", got, msg)
}
})
t.Run("client send", func(t *testing.T) {
markdownVars.title = "123"
markdownVars.text = "123"
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runMarkdownCmd(&cobra.Command{}, []string{})
})
}
================================================
FILE: cmd/dingtalk/root.go
================================================
package dingtalk
import (
"errors"
"fmt"
"log"
"os"
"github.com/CatchZeng/dingtalk/configs"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/viper"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "dingtalk",
Short: "dingtalk is a command line tool for DingTalk",
Long: "dingtalk is a command line tool for DingTalk",
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func newClient() (*dingtalk.Client, error) {
token := getAccessToken()
secret := getSecret()
if len(token) < 1 {
return nil, errors.New("access_token can not be empty")
}
client := dingtalk.NewClient(token, secret)
return client, nil
}
func getAccessToken() string {
if len(accessToken) > 0 {
return accessToken
}
value, err := configs.GetConfig(configs.AccessToken)
if err == nil {
return value
}
return ""
}
func getSecret() string {
if len(secret) > 0 {
return secret
}
value, err := configs.GetConfig(configs.Secret)
if err == nil {
return value
}
return ""
}
var accessToken, secret string
var isAtAll bool
var atMobiles []string
var debug bool
func init() {
cobra.OnInitialize(configs.InitConfig)
rootCmd.PersistentFlags().StringVarP(&accessToken, configs.AccessToken, "t", "", configs.AccessToken)
rootCmd.PersistentFlags().StringVarP(&secret, configs.Secret, "s", "", configs.Secret)
rootCmd.PersistentFlags().BoolVarP(&isAtAll, "isAtAll", "a", false, "isAtAll")
rootCmd.PersistentFlags().StringSliceVarP(&atMobiles, "atMobiles", "m", []string{}, "atMobiles")
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "D", false, "debug")
if err := viper.BindPFlag(configs.AccessToken, rootCmd.PersistentFlags().Lookup(configs.AccessToken)); err != nil {
log.Print(err)
}
if err := viper.BindPFlag(configs.Secret, rootCmd.PersistentFlags().Lookup(configs.Secret)); err != nil {
log.Print(err)
}
}
================================================
FILE: cmd/dingtalk/root_test.go
================================================
package dingtalk
import (
"errors"
"log"
"os"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/configs"
)
func Test_newClient(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
t.Run("getAccessToken return empty", func(t *testing.T) {
accessToken = ""
_, err := newClient()
if err == nil {
t.Error("newClient() error")
}
})
t.Run("getAccessToken return token", func(t *testing.T) {
accessToken = "123"
client, err := newClient()
if err != nil || client == nil {
t.Error("newClient() error")
}
})
}
func Test_getAccessToken(t *testing.T) {
t.Run("get from accessToken", func(t *testing.T) {
accessToken = "123"
got := getAccessToken()
if got != accessToken {
t.Errorf("getAccessToken() = %v, want %v", got, accessToken)
}
})
t.Run("GetConfig error", func(t *testing.T) {
accessToken = ""
monkey.Patch(configs.GetConfig, func(key string) (string, error) {
return "", errors.New("GetConfig error")
})
defer monkey.Unpatch(configs.GetConfig)
got := getAccessToken()
if got != "" {
t.Errorf("getAccessToken() = %v, want %v", got, "")
}
})
t.Run("get from config", func(t *testing.T) {
accessToken = ""
want := "123"
monkey.Patch(configs.GetConfig, func(key string) (string, error) {
return want, nil
})
defer monkey.Unpatch(configs.GetConfig)
got := getAccessToken()
if got != want {
t.Errorf("getAccessToken() = %v, want %v", got, want)
}
})
}
func Test_getSecret(t *testing.T) {
t.Run("get from secret", func(t *testing.T) {
secret = "123"
got := getSecret()
if got != secret {
t.Errorf("getSecret() = %v, want %v", got, secret)
}
})
t.Run("GetConfig error", func(t *testing.T) {
secret = ""
monkey.Patch(configs.GetConfig, func(key string) (string, error) {
return "", errors.New("GetConfig error")
})
defer monkey.Unpatch(configs.GetConfig)
got := getSecret()
if got != "" {
t.Errorf("getSecret() = %v, want %v", got, "")
}
})
t.Run("get from config", func(t *testing.T) {
secret = ""
want := "123"
monkey.Patch(configs.GetConfig, func(key string) (string, error) {
return want, nil
})
defer monkey.Unpatch(configs.GetConfig)
got := getSecret()
if got != want {
t.Errorf("getSecret() = %v, want %v", got, want)
}
})
}
================================================
FILE: cmd/dingtalk/text.go
================================================
package dingtalk
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
var textCmd = &cobra.Command{
Use: "text",
Short: "send text message with DingTalk robot",
Long: `send text message with DingTalk robot`,
Args: cobra.MinimumNArgs(0),
Run: runTextCmd,
}
func runTextCmd(_ *cobra.Command, _ []string) {
if len(textVars.content) < 1 {
log.Fatal("content can not be empty")
return
}
client, err := newClient()
if err != nil {
log.Fatal(err.Error())
return
}
msg := dingtalk.NewTextMessage().
SetContent(textVars.content).
SetAt(atMobiles, isAtAll)
req, _, err := client.Send(msg)
if debug {
log.Print(req)
}
if err != nil {
log.Fatal(err.Error())
}
}
// TextVars struct
type TextVars struct {
content string
}
var textVars TextVars
func init() {
rootCmd.AddCommand(textCmd)
textCmd.Flags().StringVarP(&textVars.content, "content", "c", "", "content")
}
================================================
FILE: cmd/dingtalk/text_test.go
================================================
package dingtalk
import (
"bytes"
"errors"
"log"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
"github.com/spf13/cobra"
)
func Test_runTextCmd(t *testing.T) {
fakeExit := func(int) {
log.Print("fake exit")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
t.Run("content is empty", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runTextCmd(&cobra.Command{}, []string{})
got := buf.String()
want := "content can not be empty"
if !strings.Contains(got, want) {
t.Errorf("runTextCmd() = %v, want %v", got, want)
}
})
t.Run("new client error", func(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
textVars.content = "123"
msg := "new client error"
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return nil, errors.New(msg)
})
defer monkey.Unpatch(newClient)
runTextCmd(&cobra.Command{}, []string{})
got := buf.String()
if !strings.Contains(got, msg) {
t.Errorf("runTextCmd() = %v, want %v", got, msg)
}
})
t.Run("client send", func(t *testing.T) {
textVars.content = "123"
client := &dingtalk.Client{}
monkey.Patch(newClient, func() (*dingtalk.Client, error) {
return client, nil
})
defer monkey.Unpatch(newClient)
runTextCmd(&cobra.Command{}, []string{})
})
}
================================================
FILE: cmd/dingtalk/version.go
================================================
package dingtalk
import (
"log"
v "github.com/CatchZeng/gutils/version"
"github.com/spf13/cobra"
)
const (
version = "1.5.0"
buildTime = "2022/04/20"
)
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "dingtalk version",
Long: `dingtalk version`,
Run: runVersionCmd,
}
func runVersionCmd(_ *cobra.Command, _ []string) {
v := v.Stringify(version, buildTime)
log.Println(v)
}
func init() {
rootCmd.AddCommand(versionCmd)
}
================================================
FILE: cmd/dingtalk/version_test.go
================================================
package dingtalk
import (
"bytes"
"log"
"os"
"strings"
"testing"
v "github.com/CatchZeng/gutils/version"
"github.com/spf13/cobra"
)
func Test_runVersionCmd(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
runVersionCmd(&cobra.Command{}, []string{})
got := buf.String()
want := v.Stringify(version, buildTime)
if !strings.Contains(got, want) {
t.Errorf("runVersionCmd() = %v, want %v", got, want)
}
}
================================================
FILE: configs/app.go
================================================
package configs
const (
// AccessToken access token key
AccessToken string = "access_token"
// Secret secret key
Secret string = "secret"
)
================================================
FILE: configs/configs.go
================================================
package configs
import (
"log"
"os"
"path"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
// InitConfig reads in configs file and ENV variables if set.
func InitConfig() {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
log.Panic(err)
}
// Search configs in home directory with name ".dingtalk" (without extension).
configPath := path.Join(home, ".dingtalk")
viper.AddConfigPath(configPath)
viper.SetConfigName("config")
envPrefix := os.Getenv("DINGTALK_ENV_PREFIX")
viper.SetEnvPrefix(envPrefix)
viper.AutomaticEnv() // read in environment variables that match
// If a configs file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
return
}
log.Println("using configs file:", viper.ConfigFileUsed())
}
// GetConfig get configs with key
func GetConfig(key string) (string, error) {
// Check the environment variable
envPrefix := os.Getenv("DINGTALK_ENV_PREFIX")
envKey := envPrefix + strings.ToUpper(key)
result := os.Getenv(envKey)
if result != "" {
return result, nil
}
// If a configs file is found, read it in.
err := viper.ReadInConfig()
if err == nil {
return viper.GetString(key), nil
}
return "", err
}
================================================
FILE: configs/configs_test.go
================================================
package configs
import (
"errors"
"testing"
"bou.ke/monkey"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
func TestInitConfig(t *testing.T) {
t.Run("homedir.Dir() return error", func(t *testing.T) {
monkey.Patch(homedir.Dir, func() (string, error) {
return "", errors.New("homedir error")
})
shouldPanic(t, InitConfig)
})
t.Run("viper.ReadInConfig() return error", func(t *testing.T) {
monkey.Patch(homedir.Dir, func() (string, error) {
return "/catchzeng", nil
})
monkey.Patch(viper.ReadInConfig, func() error {
return errors.New("ReadInConfig error")
})
InitConfig()
})
t.Run("viper.ReadInConfig() return nil", func(t *testing.T) {
monkey.Patch(homedir.Dir, func() (string, error) {
return "/catchzeng", nil
})
monkey.Patch(viper.ReadInConfig, func() error {
return nil
})
InitConfig()
})
}
func shouldPanic(t *testing.T, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("should have panicked")
}
}()
f()
}
func TestGetConfig(t *testing.T) {
t.Run("viper.ReadInConfig() return error", func(t *testing.T) {
monkey.Patch(viper.ReadInConfig, func() error {
return errors.New("ReadInConfig error")
})
key := "123"
if _, err := GetConfig(key); err == nil {
t.Error("GetConfig error")
}
})
t.Run("viper.ReadInConfig() return nil", func(t *testing.T) {
monkey.Patch(viper.ReadInConfig, func() error {
return nil
})
monkey.Patch(viper.GetString, func(key string) string {
return key
})
key := "456"
if value, err := GetConfig(key); err != nil || value != key {
t.Error("GetConfig error")
}
})
}
================================================
FILE: go.mod
================================================
module github.com/CatchZeng/dingtalk
go 1.18
require (
bou.ke/monkey v1.0.2
github.com/CatchZeng/gutils v0.1.4
github.com/golang/mock v1.4.4
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.11.0
)
require (
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
================================================
FILE: go.sum
================================================
bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=
bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CatchZeng/gutils v0.1.4 h1:Xj1oqB0Rg3F6rymxW1d+ajPl0wHtw2KvDlcquhnCNAc=
github.com/CatchZeng/gutils v0.1.4/go.mod h1:Uz8tJTZDM9XWGTFQt3oIYs+zY3/2fOy0TVQaQTgPtWg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0=
github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
================================================
FILE: internal/security/security.go
================================================
package security
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"math"
"net/url"
"strconv"
"time"
)
// https://oapi.dingtalk.com/robot/send?access_token=xxx
const dingTalkOAPI = "oapi.dingtalk.com"
var dingTalkURL url.URL = url.URL{
Scheme: "https",
Host: dingTalkOAPI,
Path: "robot/send",
}
// URL get DingTalk URL with accessToken & secret
// If no signature is set, the secret is set to ""
// 如果没有加签,secret 设置为 "" 即可
func URL(accessToken string, secret string) (string, error) {
timestamp := strconv.FormatInt(time.Now().Unix()*1000, 10)
return URLWithTimestamp(timestamp, accessToken, secret)
}
// URLWithTimestamp get DingTalk URL with timestamp & accessToken & secret
func URLWithTimestamp(timestamp string, accessToken string, secret string) (string, error) {
dtu := dingTalkURL
value := url.Values{}
value.Set("access_token", accessToken)
if secret == "" {
dtu.RawQuery = value.Encode()
return dtu.String(), nil
}
sign, err := sign(timestamp, secret)
if err != nil {
dtu.RawQuery = value.Encode()
return dtu.String(), err
}
value.Set("timestamp", timestamp)
value.Set("sign", sign)
dtu.RawQuery = value.Encode()
return dtu.String(), nil
}
// Validate validate
// https://ding-doc.dingtalk.com/doc#/serverapi2/elzz1p
func Validate(signStr, timestamp, secret string) (bool, error) {
t, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return false, err
}
timeGap := time.Since(time.Unix(t, 0))
if math.Abs(timeGap.Hours()) > 1 {
return false, fmt.Errorf("specified timestamp is expired")
}
ourSign, err := sign(timestamp, secret)
if err != nil {
return false, err
}
return ourSign == signStr, nil
}
func sign(timestamp string, secret string) (string, error) {
stringToSign := fmt.Sprintf("%s\n%s", timestamp, secret)
h := hmac.New(sha256.New, []byte(secret))
if _, err := io.WriteString(h, stringToSign); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
================================================
FILE: internal/security/security_test.go
================================================
package security
import (
"fmt"
"strconv"
"testing"
"time"
"bou.ke/monkey"
)
const (
timestamp = "1582163555000"
accessToken = "1c53e149ba5de6597ca2442f0e901fd86156780b8ac141e4a75afdc44c85ca4f"
secret = "SECb90923e19e58b466481e9e7b7a5b4f108a4531abde590ad3967fb29f0eae5c68"
signed = "BQKsG%2BQOCl%2BbYJOLc6pxDHxjVquzlZPWgvRzeN2J5zY%3D"
)
func TestURL(t *testing.T) {
monkey.Patch(strconv.FormatInt, func(i int64, base int) string {
return timestamp
})
defer monkey.Unpatch(strconv.FormatInt)
got, err := URL(accessToken, secret)
if err != nil {
t.Errorf("URL() error = %v", err)
}
want := fmt.Sprintf("https://oapi.dingtalk.com/robot/send?access_token=%v&sign=%v×tamp=%v", accessToken, signed, timestamp)
if got != want {
t.Errorf("URL() = %v, want %v", got, want)
}
}
func TestURLWithTimestamp(t *testing.T) {
type args struct {
accessToken string
secret string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "without sign",
args: args{
accessToken: accessToken,
},
want: fmt.Sprintf("https://oapi.dingtalk.com/robot/send?access_token=%v", accessToken),
wantErr: false,
},
{
name: "with sign",
args: args{
accessToken: accessToken,
secret: secret,
},
want: fmt.Sprintf("https://oapi.dingtalk.com/robot/send?access_token=%v&sign=%v×tamp=%v", accessToken, signed, timestamp),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := URLWithTimestamp(timestamp, tt.args.accessToken, tt.args.secret)
if (err != nil) != tt.wantErr {
t.Errorf("URLWithTimestamp() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("URLWithTimestamp() = %v, want %v", got, tt.want)
}
})
}
}
func TestValidate(t *testing.T) {
validateTimestamp := strconv.FormatInt(time.Now().Add(60*time.Second).Unix(), 10)
result, err := sign(validateTimestamp, secret)
if err != nil {
t.Error(err)
}
_, err = Validate(result, strconv.FormatInt(time.Now().Add(-3601*time.Second).Unix(), 10), secret)
if err == nil {
t.Error("this should be err, but not")
}
_, err = Validate(result, strconv.FormatInt(time.Now().Add(3601*time.Second).Unix(), 10), secret)
if err == nil {
t.Error("this should be err, but not")
}
b, err := Validate(result, validateTimestamp, secret)
if err != nil {
t.Error(err)
} else {
if !b {
t.Error("token is not the same")
}
}
}
================================================
FILE: main.go
================================================
package main
import (
"log"
"os"
"github.com/CatchZeng/dingtalk/cmd/dingtalk"
)
func main() {
log.SetOutput(os.Stdout)
log.SetFlags(0)
dingtalk.Execute()
}
================================================
FILE: pkg/dingtalk/actionCard.go
================================================
package dingtalk
import "encoding/json"
// ActionCardMessage struct
type ActionCardMessage struct {
MsgType MsgType `json:"msgtype"`
ActionCard ActionCard `json:"actionCard"`
}
// ActionCard actionCard struct
type ActionCard struct {
Title string `json:"title"`
Text string `json:"text"`
SingleTitle string `json:"singleTitle"`
SingleURL string `json:"singleURL"`
Btns []Btn `json:"btns"`
BtnOrientation string `json:"btnOrientation"`
HideAvatar string `json:"hideAvatar"`
}
// Btn struct
type Btn struct {
Title string `json:"title"`
ActionURL string `json:"actionURL"`
}
// ToByte to byte
func (m *ActionCardMessage) ToByte() ([]byte, error) {
m.MsgType = MsgTypeActionCard
jsonByte, err := json.Marshal(m)
return jsonByte, err
}
// NewActionCardMessage new message
func NewActionCardMessage() *ActionCardMessage {
msg := ActionCardMessage{}
return &msg
}
// SetOverallJump set overall jump actionCard
func (m *ActionCardMessage) SetOverallJump(
title string,
text string,
singleTitle string,
singleURL string,
btnOrientation string,
hideAvatar string) *ActionCardMessage {
m.ActionCard = ActionCard{
Title: title,
Text: text,
SingleTitle: singleTitle,
SingleURL: singleURL,
BtnOrientation: btnOrientation,
HideAvatar: hideAvatar,
}
return m
}
// SetIndependentJump set independent jump actionCard
func (m *ActionCardMessage) SetIndependentJump(
title string,
text string,
btns []Btn,
btnOrientation string,
hideAvatar string) *ActionCardMessage {
m.ActionCard = ActionCard{
Title: title,
Text: text,
Btns: btns,
BtnOrientation: btnOrientation,
HideAvatar: hideAvatar,
}
return m
}
================================================
FILE: pkg/dingtalk/actionCard_test.go
================================================
package dingtalk
import (
"reflect"
"testing"
)
func TestActionCardMessage_ToByte(t *testing.T) {
msg := NewActionCardMessage()
_, _ = msg.ToByte()
if msg.MsgType != MsgTypeActionCard {
t.Errorf("ActionCardMessage.ToByte() type error")
}
}
func TestNewActionCardMessage(t *testing.T) {
tests := []struct {
name string
want *ActionCardMessage
}{
{
name: "Should return a ActionCardMessage instance",
want: &ActionCardMessage{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewActionCardMessage(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewActionCardMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestActionCardMessage_SetOverallJump(t *testing.T) {
got := NewActionCardMessage()
got.SetOverallJump("title", "text", "singleTitle", "singleURL", "btnOrientation", "hideAvatar")
card := ActionCard{
Title: "title",
Text: "text",
SingleTitle: "singleTitle",
SingleURL: "singleURL",
BtnOrientation: "btnOrientation",
HideAvatar: "hideAvatar",
}
want := NewActionCardMessage()
want.ActionCard = card
if !reflect.DeepEqual(got, want) {
t.Errorf("SetOverallJump() = %v, want %v", got, want)
}
}
func TestActionCardMessage_SetIndependentJump(t *testing.T) {
got := NewActionCardMessage()
got.SetIndependentJump("title", "text", []Btn{{
Title: "title",
ActionURL: "actionURL",
}}, "btnOrientation", "hideAvatar")
card := ActionCard{
Title: "title",
Text: "text",
Btns: []Btn{{
Title: "title",
ActionURL: "actionURL",
}},
BtnOrientation: "btnOrientation",
HideAvatar: "hideAvatar",
}
want := NewActionCardMessage()
want.ActionCard = card
if !reflect.DeepEqual(got, want) {
t.Errorf("SetIndependentJump() = %v, want %v", got, want)
}
}
================================================
FILE: pkg/dingtalk/client.go
================================================
package dingtalk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/CatchZeng/dingtalk/internal/security"
)
// Client dingtalk client
type Client struct {
AccessToken string
Secret string
}
// NewClient new dingtalk client
func NewClient(accessToken, secret string) *Client {
return &Client{
AccessToken: accessToken,
Secret: secret,
}
}
// Response response struct
type Response struct {
ErrMsg string `json:"errmsg"`
ErrCode int64 `json:"errcode"`
}
const httpTimoutSecond = time.Duration(30) * time.Second
// Send message
func (d *Client) Send(message Message) (string, *Response, error) {
res := &Response{}
reqBytes, err := message.ToByte()
if err != nil {
return "", res, err
}
reqString := string(reqBytes)
pushURL, err := security.URL(d.AccessToken, d.Secret)
if err != nil {
return reqString, res, err
}
req, err := http.NewRequest(http.MethodPost, pushURL, bytes.NewReader(reqBytes))
if err != nil {
return reqString, res, err
}
req.Header.Add("Accept-Charset", "utf8")
req.Header.Add("Content-Type", "application/json")
client := new(http.Client)
client.Timeout = httpTimoutSecond
resp, err := client.Do(req)
if err != nil {
return reqString, res, err
}
defer resp.Body.Close()
resultByte, err := ioutil.ReadAll(resp.Body)
if err != nil {
return reqString, res, err
}
err = json.Unmarshal(resultByte, &res)
if err != nil {
return reqString, res, fmt.Errorf("unmarshal http response body from json error = %v", err)
}
if res.ErrCode != 0 {
return reqString, res, fmt.Errorf("send message to dingtalk error = %s", res.ErrMsg)
}
return reqString, res, nil
}
================================================
FILE: pkg/dingtalk/client_test.go
================================================
package dingtalk
import (
"errors"
"io"
"net/http"
"reflect"
"testing"
"bou.ke/monkey"
"github.com/CatchZeng/dingtalk/internal/security"
mock_message "github.com/CatchZeng/dingtalk/test/mocks/message"
"github.com/golang/mock/gomock"
)
func TestNewClient(t *testing.T) {
type args struct {
accessToken string
secret string
}
tests := []struct {
name string
args args
want *Client
}{
{
name: "",
args: args{
accessToken: "123456",
secret: "111111",
},
want: &Client{
AccessToken: "123456",
Secret: "111111",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewClient(tt.args.accessToken, tt.args.secret); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewClient() = %v, want %v", got, tt.want)
}
})
}
}
func TestClient_Send(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
message := mock_message.NewMockMessage(ctrl)
t.Run("message return error", func(t *testing.T) {
c := &Client{}
message.EXPECT().ToByte().Return([]byte{}, errors.New("test"))
if _, _, err := c.Send(message); err == nil {
t.Error("send error")
}
})
t.Run("security.URL return error", func(t *testing.T) {
c := &Client{
AccessToken: "test-access-token",
Secret: "test-secret",
}
message.EXPECT().ToByte().Return([]byte{}, nil)
monkey.Patch(security.URL, func(accessToken string, secret string) (string, error) {
return "", errors.New("URL error")
})
if _, _, err := c.Send(message); err == nil {
t.Error("send error")
}
})
t.Run("http.NewRequest return error", func(t *testing.T) {
c := &Client{
AccessToken: "test-access-token",
Secret: "test-secret",
}
message.EXPECT().ToByte().Return([]byte{}, nil)
monkey.Patch(security.URL, func(accessToken string, secret string) (string, error) {
return "https://oapi.dingtalk.com/robot/send?access_token=ewfewfwfwefwafew", nil
})
monkey.Patch(http.NewRequest, func(method, url string, body io.Reader) (*http.Request, error) {
return nil, errors.New("NewRequest error")
})
if _, _, err := c.Send(message); err == nil {
t.Error("send error")
}
})
}
================================================
FILE: pkg/dingtalk/feedCard.go
================================================
package dingtalk
import "encoding/json"
// FeedCardMessage feed message struct
type FeedCardMessage struct {
MsgType MsgType `json:"msgtype"`
FeedCard FeedCard `json:"feedCard"`
}
// FeedCard feedCard struct
type FeedCard struct {
Links []FeedCardLink `json:"links"`
}
// FeedCardLink struct
type FeedCardLink struct {
Title string `json:"title"`
PicURL string `json:"picURL"`
MessageURL string `json:"messageURL"`
}
// ToByte to byte
func (m *FeedCardMessage) ToByte() ([]byte, error) {
m.MsgType = MsgTypeFeedCard
jsonByte, err := json.Marshal(m)
return jsonByte, err
}
// NewFeedCardMessage new message
func NewFeedCardMessage() *FeedCardMessage {
msg := FeedCardMessage{}
return &msg
}
// AppendLink append link
func (m *FeedCardMessage) AppendLink(
title string,
messageURL string,
picURL string) *FeedCardMessage {
var link = FeedCardLink{
Title: title,
MessageURL: messageURL,
PicURL: picURL,
}
m.FeedCard.Links = append(m.FeedCard.Links, link)
return m
}
================================================
FILE: pkg/dingtalk/feedCard_test.go
================================================
package dingtalk
import (
"reflect"
"testing"
)
func TestFeedCardMessage_ToByte(t *testing.T) {
msg := NewFeedCardMessage()
_, _ = msg.ToByte()
if msg.MsgType != MsgTypeFeedCard {
t.Errorf("FeedCardMessage.ToByte() type error")
}
}
func TestNewFeedCardMessage(t *testing.T) {
tests := []struct {
name string
want *FeedCardMessage
}{
{
name: "Should return a FeedCardMessage instance",
want: &FeedCardMessage{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewFeedCardMessage(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewFeedCardMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestFeedCardMessage_AppendLink(t *testing.T) {
msg := NewFeedCardMessage()
msg.AppendLink("title", "messageURL", "picURL")
if len(msg.FeedCard.Links) != 1 {
t.Errorf("The number of links after AppendLink should be 1")
}
msg.AppendLink("title2", "messageURL2", "picURL2")
if len(msg.FeedCard.Links) != 2 {
t.Errorf("The number of links after AppendLink should be 2")
}
}
================================================
FILE: pkg/dingtalk/link.go
================================================
package dingtalk
import "encoding/json"
// LinkMessage link message struct
type LinkMessage struct {
MsgType MsgType `json:"msgtype"`
Link Link `json:"link"`
}
// Link link struct
type Link struct {
Title string `json:"title"`
Text string `json:"text"`
PicURL string `json:"picUrl"`
MessageURL string `json:"messageUrl"`
}
// ToByte to byte
func (m *LinkMessage) ToByte() ([]byte, error) {
m.MsgType = MsgTypeLink
jsonByte, err := json.Marshal(m)
return jsonByte, err
}
// NewLinkMessage new message
func NewLinkMessage() *LinkMessage {
msg := LinkMessage{}
return &msg
}
// SetLink set link
func (m *LinkMessage) SetLink(
title string,
text string,
picURL string,
messageURL string) *LinkMessage {
m.Link = Link{
Title: title,
Text: text,
PicURL: picURL,
MessageURL: messageURL,
}
return m
}
================================================
FILE: pkg/dingtalk/link_test.go
================================================
package dingtalk
import (
"reflect"
"testing"
)
func TestLinkMessage_ToByte(t *testing.T) {
msg := NewLinkMessage()
_, _ = msg.ToByte()
if msg.MsgType != MsgTypeLink {
t.Errorf("LinkMessage.ToByte() type error")
}
}
func TestNewLinkMessage(t *testing.T) {
tests := []struct {
name string
want *LinkMessage
}{
{
name: "Should return a LinkMessage instance",
want: &LinkMessage{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewLinkMessage(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewLinkMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestLinkMessage_SetLink(t *testing.T) {
got := NewLinkMessage()
got.SetLink("title", "text", "picURL", "messageURL")
want := NewLinkMessage()
want.Link = Link{
Title: "title",
Text: "text",
PicURL: "picURL",
MessageURL: "messageURL",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetLink() = %v, want %v", got, want)
}
}
================================================
FILE: pkg/dingtalk/markdown.go
================================================
package dingtalk
import "encoding/json"
// MarkdownMessage markdown message struct
type MarkdownMessage struct {
MsgType MsgType `json:"msgtype"`
Markdown Markdown `json:"markdown"`
At At `json:"at"`
}
// Markdown markdown struct
type Markdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
// ToByte to byte
func (m *MarkdownMessage) ToByte() ([]byte, error) {
m.MsgType = MsgTypeMarkdown
jsonByte, err := json.Marshal(m)
return jsonByte, err
}
// NewMarkdownMessage new message
func NewMarkdownMessage() *MarkdownMessage {
msg := MarkdownMessage{}
return &msg
}
// SetMarkdown set markdown
func (m *MarkdownMessage) SetMarkdown(title string, text string) *MarkdownMessage {
m.Markdown = Markdown{
Title: title,
Text: text,
}
return m
}
// SetAt set at
func (m *MarkdownMessage) SetAt(atMobiles []string, isAtAll bool) *MarkdownMessage {
m.At = At{
AtMobiles: atMobiles,
IsAtAll: isAtAll,
}
return m
}
================================================
FILE: pkg/dingtalk/markdown_test.go
================================================
package dingtalk
import (
"reflect"
"testing"
)
func TestMarkdownMessage_ToByte(t *testing.T) {
msg := NewMarkdownMessage()
_, _ = msg.ToByte()
if msg.MsgType != MsgTypeMarkdown {
t.Errorf("MarkdownMessage.ToByte() type error")
}
}
func TestNewMarkdownMessage(t *testing.T) {
tests := []struct {
name string
want *MarkdownMessage
}{
{
name: "Should return a MarkdownMessage instance",
want: &MarkdownMessage{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewMarkdownMessage(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewMarkdownMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestMarkdownMessage_SetMarkdown(t *testing.T) {
got := NewMarkdownMessage()
got.SetMarkdown("title", "text")
want := NewMarkdownMessage()
want.Markdown = Markdown{
Title: "title",
Text: "text",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetMarkdown() = %v, want %v", got, want)
}
}
func TestMarkdownMessage_SetAt(t *testing.T) {
got := NewMarkdownMessage()
got.SetAt([]string{"atMobiles"}, false)
want := NewMarkdownMessage()
want.At = At{
AtMobiles: []string{"atMobiles"},
IsAtAll: false,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetMarkdown() = %v, want %v", got, want)
}
}
================================================
FILE: pkg/dingtalk/message.go
================================================
package dingtalk
// MsgType message type enum
type MsgType string
const (
// MsgTypeText text
MsgTypeText MsgType = "text"
// MsgTypeMarkdown markdown
MsgTypeMarkdown MsgType = "markdown"
// MsgTypeLink link
MsgTypeLink MsgType = "link"
// MsgTypeActionCard actionCard
MsgTypeActionCard MsgType = "actionCard"
// MsgTypeFeedCard feedCard
MsgTypeFeedCard MsgType = "feedCard"
)
// Message interface
type Message interface {
ToByte() ([]byte, error)
}
// At at struct
type At struct {
AtMobiles []string `json:"atMobiles"`
IsAtAll bool `json:"isAtAll"`
}
================================================
FILE: pkg/dingtalk/text.go
================================================
package dingtalk
import "encoding/json"
// TextMessage text message struct
type TextMessage struct {
MsgType MsgType `json:"msgtype"`
Text Text `json:"text"`
At At `json:"at"`
}
// Text text struct
type Text struct {
Content string `json:"content"`
}
// ToByte to byte
func (m *TextMessage) ToByte() ([]byte, error) {
m.MsgType = MsgTypeText
jsonByte, err := json.Marshal(m)
return jsonByte, err
}
// NewTextMessage new message
func NewTextMessage() *TextMessage {
msg := TextMessage{}
return &msg
}
// SetContent set content
func (m *TextMessage) SetContent(content string) *TextMessage {
m.Text = Text{
Content: content,
}
return m
}
// SetAt set at
func (m *TextMessage) SetAt(atMobiles []string, isAtAll bool) *TextMessage {
m.At = At{
AtMobiles: atMobiles,
IsAtAll: isAtAll,
}
return m
}
================================================
FILE: pkg/dingtalk/text_test.go
================================================
package dingtalk
import (
"reflect"
"testing"
)
func TestTextMessage_ToByte(t *testing.T) {
msg := NewTextMessage()
_, _ = msg.ToByte()
if msg.MsgType != MsgTypeText {
t.Errorf("TextMessage.ToByte() type error")
}
}
func TestNewTextMessage(t *testing.T) {
tests := []struct {
name string
want *TextMessage
}{
{
name: "Should return a TextMessage instance",
want: &TextMessage{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewTextMessage(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewTextMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestTextMessage_SetContent(t *testing.T) {
got := NewTextMessage()
got.SetContent("content")
want := NewTextMessage()
want.Text = Text{
Content: "content",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetContent() = %v, want %v", got, want)
}
}
func TestTextMessage_SetAt(t *testing.T) {
got := NewTextMessage()
got.SetAt([]string{"atMobiles"}, false)
want := NewTextMessage()
want.At = At{
AtMobiles: []string{"atMobiles"},
IsAtAll: false,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("SetMarkdown() = %v, want %v", got, want)
}
}
================================================
FILE: scripts/mock.sh
================================================
#!/bin/bash
shell_dir=$(dirname $0)
cd ${shell_dir}
cd ..
rm -rf test/mocks
mkdir -p test/mocks/message
# open with vscode
if which mockgen >/dev/null; then
echo "mockgen has installed in PATH"
else
echo "warning: 'mockgen' command has not installed in PATH"
GO111MODULE=on go get github.com/golang/mock/mockgen@v1.4.3
fi
mockgen -package=mock_message -source=message.go > test/mocks/message/message.go
================================================
FILE: scripts/test.sh
================================================
#!/bin/bash
shell_dir=$(dirname $0)
cd ${shell_dir}
cd ..
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -gcflags=-l -race -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done
================================================
FILE: sonar-project.properties
================================================
# must be unique in a given SonarQube instance
sonar.projectKey=dingtalk
# --- optional properties ---
# defaults to project key
#sonar.projectName=My project
# defaults to 'not provided'
sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Defaults to .
sonar.sources=.
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
sonar.exclusions=**/proto/**
sonar.language=go
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.test.exclusions=**/vendor/**,**/proto/**
sonar.go.coverage.reportPaths=coverage.data
Dsonar.coverage.dtdVerification=false
================================================
FILE: test/mocks/message/message.go
================================================
// Code generated by MockGen. DO NOT EDIT.
// Source: message.go
// Package mock_message is a generated GoMock package.
package mock_message
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockMessage is a mock of Message interface
type MockMessage struct {
ctrl *gomock.Controller
recorder *MockMessageMockRecorder
}
// MockMessageMockRecorder is the mock recorder for MockMessage
type MockMessageMockRecorder struct {
mock *MockMessage
}
// NewMockMessage creates a new mock instance
func NewMockMessage(ctrl *gomock.Controller) *MockMessage {
mock := &MockMessage{ctrl: ctrl}
mock.recorder = &MockMessageMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockMessage) EXPECT() *MockMessageMockRecorder {
return m.recorder
}
// ToByte mocks base method
func (m *MockMessage) ToByte() ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToByte")
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ToByte indicates an expected call of ToByte
func (mr *MockMessageMockRecorder) ToByte() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToByte", reflect.TypeOf((*MockMessage)(nil).ToByte))
}
gitextract_cg7u8c_q/
├── .editorconfig
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .vscode/
│ └── settings.json
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── READMEEN.md
├── build/
│ └── package/
│ ├── Dockerfile
│ └── build.sh
├── cmd/
│ └── dingtalk/
│ ├── actionCard.go
│ ├── actionCard_test.go
│ ├── feedCard.go
│ ├── feedCard_test.go
│ ├── link.go
│ ├── link_test.go
│ ├── markdown.go
│ ├── markdown_test.go
│ ├── root.go
│ ├── root_test.go
│ ├── text.go
│ ├── text_test.go
│ ├── version.go
│ └── version_test.go
├── configs/
│ ├── app.go
│ ├── configs.go
│ └── configs_test.go
├── go.mod
├── go.sum
├── internal/
│ └── security/
│ ├── security.go
│ └── security_test.go
├── main.go
├── pkg/
│ └── dingtalk/
│ ├── actionCard.go
│ ├── actionCard_test.go
│ ├── client.go
│ ├── client_test.go
│ ├── feedCard.go
│ ├── feedCard_test.go
│ ├── link.go
│ ├── link_test.go
│ ├── markdown.go
│ ├── markdown_test.go
│ ├── message.go
│ ├── text.go
│ └── text_test.go
├── scripts/
│ ├── mock.sh
│ └── test.sh
├── sonar-project.properties
└── test/
└── mocks/
└── message/
└── message.go
SYMBOL INDEX (121 symbols across 34 files)
FILE: cmd/dingtalk/actionCard.go
function runActionCardCmd (line 18) | func runActionCardCmd(_ *cobra.Command, args []string) {
function init (line 89) | func init() {
FILE: cmd/dingtalk/actionCard_test.go
function Test_runActionCardCmd (line 16) | func Test_runActionCardCmd(t *testing.T) {
FILE: cmd/dingtalk/feedCard.go
function runFeedCardCmd (line 18) | func runFeedCardCmd(_ *cobra.Command, args []string) {
type FeedCardVars (line 49) | type FeedCardVars struct
function init (line 57) | func init() {
FILE: cmd/dingtalk/feedCard_test.go
function Test_runFeedCardCmd (line 16) | func Test_runFeedCardCmd(t *testing.T) {
FILE: cmd/dingtalk/link.go
function runLinkCmd (line 18) | func runLinkCmd(_ *cobra.Command, args []string) {
type LinkVars (line 52) | type LinkVars struct
function init (line 61) | func init() {
FILE: cmd/dingtalk/link_test.go
function Test_runLinkCmd (line 16) | func Test_runLinkCmd(t *testing.T) {
FILE: cmd/dingtalk/markdown.go
function runMarkdownCmd (line 18) | func runMarkdownCmd(_ *cobra.Command, args []string) {
type MarkdownVars (line 48) | type MarkdownVars struct
function init (line 55) | func init() {
FILE: cmd/dingtalk/markdown_test.go
function Test_runMarkdownCmd (line 16) | func Test_runMarkdownCmd(t *testing.T) {
FILE: cmd/dingtalk/root.go
function Execute (line 24) | func Execute() {
function newClient (line 31) | func newClient() (*dingtalk.Client, error) {
function getAccessToken (line 42) | func getAccessToken() string {
function getSecret (line 54) | func getSecret() string {
function init (line 71) | func init() {
FILE: cmd/dingtalk/root_test.go
function Test_newClient (line 13) | func Test_newClient(t *testing.T) {
function Test_getAccessToken (line 37) | func Test_getAccessToken(t *testing.T) {
function Test_getSecret (line 76) | func Test_getSecret(t *testing.T) {
FILE: cmd/dingtalk/text.go
function runTextCmd (line 18) | func runTextCmd(_ *cobra.Command, _ []string) {
type TextVars (line 43) | type TextVars struct
function init (line 49) | func init() {
FILE: cmd/dingtalk/text_test.go
function Test_runTextCmd (line 16) | func Test_runTextCmd(t *testing.T) {
FILE: cmd/dingtalk/version.go
constant version (line 11) | version = "1.5.0"
constant buildTime (line 12) | buildTime = "2022/04/20"
function runVersionCmd (line 23) | func runVersionCmd(_ *cobra.Command, _ []string) {
function init (line 28) | func init() {
FILE: cmd/dingtalk/version_test.go
function Test_runVersionCmd (line 15) | func Test_runVersionCmd(t *testing.T) {
FILE: configs/app.go
constant AccessToken (line 5) | AccessToken string = "access_token"
constant Secret (line 7) | Secret string = "secret"
FILE: configs/configs.go
function InitConfig (line 14) | func InitConfig() {
function GetConfig (line 38) | func GetConfig(key string) (string, error) {
FILE: configs/configs_test.go
function TestInitConfig (line 12) | func TestInitConfig(t *testing.T) {
function shouldPanic (line 45) | func shouldPanic(t *testing.T, f func()) {
function TestGetConfig (line 54) | func TestGetConfig(t *testing.T) {
FILE: internal/security/security.go
constant dingTalkOAPI (line 16) | dingTalkOAPI = "oapi.dingtalk.com"
function URL (line 27) | func URL(accessToken string, secret string) (string, error) {
function URLWithTimestamp (line 33) | func URLWithTimestamp(timestamp string, accessToken string, secret strin...
function Validate (line 57) | func Validate(signStr, timestamp, secret string) (bool, error) {
function sign (line 75) | func sign(timestamp string, secret string) (string, error) {
FILE: internal/security/security_test.go
constant timestamp (line 13) | timestamp = "1582163555000"
constant accessToken (line 14) | accessToken = "1c53e149ba5de6597ca2442f0e901fd86156780b8ac141e4a75afdc44...
constant secret (line 15) | secret = "SECb90923e19e58b466481e9e7b7a5b4f108a4531abde590ad3967fb2...
constant signed (line 16) | signed = "BQKsG%2BQOCl%2BbYJOLc6pxDHxjVquzlZPWgvRzeN2J5zY%3D"
function TestURL (line 19) | func TestURL(t *testing.T) {
function TestURLWithTimestamp (line 37) | func TestURLWithTimestamp(t *testing.T) {
function TestValidate (line 80) | func TestValidate(t *testing.T) {
FILE: main.go
function main (line 10) | func main() {
FILE: pkg/dingtalk/actionCard.go
type ActionCardMessage (line 6) | type ActionCardMessage struct
method ToByte (line 29) | func (m *ActionCardMessage) ToByte() ([]byte, error) {
method SetOverallJump (line 42) | func (m *ActionCardMessage) SetOverallJump(
method SetIndependentJump (line 61) | func (m *ActionCardMessage) SetIndependentJump(
type ActionCard (line 12) | type ActionCard struct
type Btn (line 23) | type Btn struct
function NewActionCardMessage (line 36) | func NewActionCardMessage() *ActionCardMessage {
FILE: pkg/dingtalk/actionCard_test.go
function TestActionCardMessage_ToByte (line 8) | func TestActionCardMessage_ToByte(t *testing.T) {
function TestNewActionCardMessage (line 16) | func TestNewActionCardMessage(t *testing.T) {
function TestActionCardMessage_SetOverallJump (line 35) | func TestActionCardMessage_SetOverallJump(t *testing.T) {
function TestActionCardMessage_SetIndependentJump (line 55) | func TestActionCardMessage_SetIndependentJump(t *testing.T) {
FILE: pkg/dingtalk/client.go
type Client (line 15) | type Client struct
method Send (line 37) | func (d *Client) Send(message Message) (string, *Response, error) {
function NewClient (line 21) | func NewClient(accessToken, secret string) *Client {
type Response (line 29) | type Response struct
constant httpTimoutSecond (line 34) | httpTimoutSecond = time.Duration(30) * time.Second
FILE: pkg/dingtalk/client_test.go
function TestNewClient (line 17) | func TestNewClient(t *testing.T) {
function TestClient_Send (line 48) | func TestClient_Send(t *testing.T) {
FILE: pkg/dingtalk/feedCard.go
type FeedCardMessage (line 6) | type FeedCardMessage struct
method ToByte (line 24) | func (m *FeedCardMessage) ToByte() ([]byte, error) {
method AppendLink (line 37) | func (m *FeedCardMessage) AppendLink(
type FeedCard (line 12) | type FeedCard struct
type FeedCardLink (line 17) | type FeedCardLink struct
function NewFeedCardMessage (line 31) | func NewFeedCardMessage() *FeedCardMessage {
FILE: pkg/dingtalk/feedCard_test.go
function TestFeedCardMessage_ToByte (line 8) | func TestFeedCardMessage_ToByte(t *testing.T) {
function TestNewFeedCardMessage (line 16) | func TestNewFeedCardMessage(t *testing.T) {
function TestFeedCardMessage_AppendLink (line 35) | func TestFeedCardMessage_AppendLink(t *testing.T) {
FILE: pkg/dingtalk/link.go
type LinkMessage (line 6) | type LinkMessage struct
method ToByte (line 20) | func (m *LinkMessage) ToByte() ([]byte, error) {
method SetLink (line 33) | func (m *LinkMessage) SetLink(
type Link (line 12) | type Link struct
function NewLinkMessage (line 27) | func NewLinkMessage() *LinkMessage {
FILE: pkg/dingtalk/link_test.go
function TestLinkMessage_ToByte (line 8) | func TestLinkMessage_ToByte(t *testing.T) {
function TestNewLinkMessage (line 16) | func TestNewLinkMessage(t *testing.T) {
function TestLinkMessage_SetLink (line 35) | func TestLinkMessage_SetLink(t *testing.T) {
FILE: pkg/dingtalk/markdown.go
type MarkdownMessage (line 6) | type MarkdownMessage struct
method ToByte (line 19) | func (m *MarkdownMessage) ToByte() ([]byte, error) {
method SetMarkdown (line 32) | func (m *MarkdownMessage) SetMarkdown(title string, text string) *Mark...
method SetAt (line 41) | func (m *MarkdownMessage) SetAt(atMobiles []string, isAtAll bool) *Mar...
type Markdown (line 13) | type Markdown struct
function NewMarkdownMessage (line 26) | func NewMarkdownMessage() *MarkdownMessage {
FILE: pkg/dingtalk/markdown_test.go
function TestMarkdownMessage_ToByte (line 8) | func TestMarkdownMessage_ToByte(t *testing.T) {
function TestNewMarkdownMessage (line 16) | func TestNewMarkdownMessage(t *testing.T) {
function TestMarkdownMessage_SetMarkdown (line 35) | func TestMarkdownMessage_SetMarkdown(t *testing.T) {
function TestMarkdownMessage_SetAt (line 50) | func TestMarkdownMessage_SetAt(t *testing.T) {
FILE: pkg/dingtalk/message.go
type MsgType (line 4) | type MsgType
constant MsgTypeText (line 8) | MsgTypeText MsgType = "text"
constant MsgTypeMarkdown (line 10) | MsgTypeMarkdown MsgType = "markdown"
constant MsgTypeLink (line 12) | MsgTypeLink MsgType = "link"
constant MsgTypeActionCard (line 14) | MsgTypeActionCard MsgType = "actionCard"
constant MsgTypeFeedCard (line 16) | MsgTypeFeedCard MsgType = "feedCard"
type Message (line 20) | type Message interface
type At (line 25) | type At struct
FILE: pkg/dingtalk/text.go
type TextMessage (line 6) | type TextMessage struct
method ToByte (line 18) | func (m *TextMessage) ToByte() ([]byte, error) {
method SetContent (line 31) | func (m *TextMessage) SetContent(content string) *TextMessage {
method SetAt (line 39) | func (m *TextMessage) SetAt(atMobiles []string, isAtAll bool) *TextMes...
type Text (line 13) | type Text struct
function NewTextMessage (line 25) | func NewTextMessage() *TextMessage {
FILE: pkg/dingtalk/text_test.go
function TestTextMessage_ToByte (line 8) | func TestTextMessage_ToByte(t *testing.T) {
function TestNewTextMessage (line 16) | func TestNewTextMessage(t *testing.T) {
function TestTextMessage_SetContent (line 35) | func TestTextMessage_SetContent(t *testing.T) {
function TestTextMessage_SetAt (line 49) | func TestTextMessage_SetAt(t *testing.T) {
FILE: test/mocks/message/message.go
type MockMessage (line 14) | type MockMessage struct
method EXPECT (line 32) | func (m *MockMessage) EXPECT() *MockMessageMockRecorder {
method ToByte (line 37) | func (m *MockMessage) ToByte() ([]byte, error) {
type MockMessageMockRecorder (line 20) | type MockMessageMockRecorder struct
method ToByte (line 46) | func (mr *MockMessageMockRecorder) ToByte() *gomock.Call {
function NewMockMessage (line 25) | func NewMockMessage(ctrl *gomock.Controller) *MockMessage {
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (132K chars).
[
{
"path": ".editorconfig",
"chars": 301,
"preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_w"
},
{
"path": ".github/workflows/go.yml",
"chars": 529,
"preview": "name: Go\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n\n build:\n name: Build"
},
{
"path": ".gitignore",
"chars": 188,
"preview": "dingtalk-darwin-amd64.zip\ndingtalk-darwin-arm64.zip\ndingtalk-linux-amd64.zip\ndingtalk-windows-386.zip\ndingtalk-windows-a"
},
{
"path": ".vscode/settings.json",
"chars": 85,
"preview": "{\n \"cSpell.words\": [\"btns\", \"dingtalk\", \"gomock\", \"mitchellh\", \"OAPI\", \"Unpatch\"]\n}\n"
},
{
"path": "CHANGELOG.md",
"chars": 591,
"preview": "# CHANGELOG\n\n## v1.5.0\n\n### Added\n\n- Support the environment variable prefix\n\n## v1.4.0\n\n### Added\n\n- Support environmen"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2020 Catch Zeng\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "Makefile",
"chars": 2305,
"preview": "SHELL := /bin/bash\nBASEDIR = $(shell pwd)\n\nAPP_NAME=dingtalk\nAPP_VERSION=1.5.0\nIMAGE_NAME=\"catchzeng/${APP_NAME}:${APP_V"
},
{
"path": "README.md",
"chars": 6880,
"preview": "# dingtalk\n\n\n[\n[\ncd ${shell_dir}\n\n# check params\nif [[ ! $1 ]]; then\n echo \"image tag is null\"; ex"
},
{
"path": "cmd/dingtalk/actionCard.go",
"chars": 2704,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\t\"github.com/CatchZeng/dingtalk/pkg/dingtalk\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar action"
},
{
"path": "cmd/dingtalk/actionCard_test.go",
"chars": 3833,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/feedCard.go",
"chars": 1678,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\t\"github.com/CatchZeng/dingtalk/pkg/dingtalk\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar feedCa"
},
{
"path": "cmd/dingtalk/feedCard_test.go",
"chars": 2275,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/link.go",
"chars": 1412,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\t\"github.com/CatchZeng/dingtalk/pkg/dingtalk\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar linkCm"
},
{
"path": "cmd/dingtalk/link_test.go",
"chars": 2317,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/markdown.go",
"chars": 1186,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\t\"github.com/CatchZeng/dingtalk/pkg/dingtalk\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar markdo"
},
{
"path": "cmd/dingtalk/markdown_test.go",
"chars": 1906,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/root.go",
"chars": 2078,
"preview": "package dingtalk\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/CatchZeng/dingtalk/configs\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/root_test.go",
"chars": 2377,
"preview": "package dingtalk\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZeng/dingtalk/configs\"\n"
},
{
"path": "cmd/dingtalk/text.go",
"chars": 939,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\t\"github.com/CatchZeng/dingtalk/pkg/dingtalk\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar textCm"
},
{
"path": "cmd/dingtalk/text_test.go",
"chars": 1447,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZen"
},
{
"path": "cmd/dingtalk/version.go",
"chars": 498,
"preview": "package dingtalk\n\nimport (\n\t\"log\"\n\n\tv \"github.com/CatchZeng/gutils/version\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst (\n\tversio"
},
{
"path": "cmd/dingtalk/version_test.go",
"chars": 478,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\tv \"github.com/CatchZeng/gutils/version\"\n\n\t\"gith"
},
{
"path": "configs/app.go",
"chars": 145,
"preview": "package configs\n\nconst (\n\t// AccessToken access token key\n\tAccessToken string = \"access_token\"\n\t// Secret secret key\n\tSe"
},
{
"path": "configs/configs.go",
"chars": 1220,
"preview": "package configs\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/spf13/viper\"\n"
},
{
"path": "configs/configs_test.go",
"chars": 1641,
"preview": "package configs\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/spf13/v"
},
{
"path": "go.mod",
"chars": 1102,
"preview": "module github.com/CatchZeng/dingtalk\n\ngo 1.18\n\nrequire (\n\tbou.ke/monkey v1.0.2\n\tgithub.com/CatchZeng/gutils v0.1.4\n\tgith"
},
{
"path": "go.sum",
"chars": 48513,
"preview": "bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=\nbou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvt"
},
{
"path": "internal/security/security.go",
"chars": 2005,
"preview": "package security\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net/url\"\n\t\"strconv\""
},
{
"path": "internal/security/security_test.go",
"chars": 2525,
"preview": "package security\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"bou.ke/monkey\"\n)\n\nconst (\n\ttimestamp = \"15821635550"
},
{
"path": "main.go",
"chars": 165,
"preview": "package main\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/CatchZeng/dingtalk/cmd/dingtalk\"\n)\n\nfunc main() {\n\tlog.SetOutput(os.St"
},
{
"path": "pkg/dingtalk/actionCard.go",
"chars": 1763,
"preview": "package dingtalk\n\nimport \"encoding/json\"\n\n// ActionCardMessage struct\ntype ActionCardMessage struct {\n\tMsgType MsgTyp"
},
{
"path": "pkg/dingtalk/actionCard_test.go",
"chars": 1817,
"preview": "package dingtalk\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestActionCardMessage_ToByte(t *testing.T) {\n\tmsg := NewActionC"
},
{
"path": "pkg/dingtalk/client.go",
"chars": 1682,
"preview": "package dingtalk\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/CatchZeng/din"
},
{
"path": "pkg/dingtalk/client_test.go",
"chars": 2201,
"preview": "package dingtalk\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"bou.ke/monkey\"\n\t\"github.com/CatchZeng/di"
},
{
"path": "pkg/dingtalk/feedCard.go",
"chars": 1014,
"preview": "package dingtalk\n\nimport \"encoding/json\"\n\n// FeedCardMessage feed message struct\ntype FeedCardMessage struct {\n\tMsgType "
},
{
"path": "pkg/dingtalk/feedCard_test.go",
"chars": 1048,
"preview": "package dingtalk\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFeedCardMessage_ToByte(t *testing.T) {\n\tmsg := NewFeedCardM"
},
{
"path": "pkg/dingtalk/link.go",
"chars": 861,
"preview": "package dingtalk\n\nimport \"encoding/json\"\n\n// LinkMessage link message struct\ntype LinkMessage struct {\n\tMsgType MsgType "
},
{
"path": "pkg/dingtalk/link_test.go",
"chars": 982,
"preview": "package dingtalk\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestLinkMessage_ToByte(t *testing.T) {\n\tmsg := NewLinkMessage()"
},
{
"path": "pkg/dingtalk/markdown.go",
"chars": 970,
"preview": "package dingtalk\n\nimport \"encoding/json\"\n\n// MarkdownMessage markdown message struct\ntype MarkdownMessage struct {\n\tMsgT"
},
{
"path": "pkg/dingtalk/markdown_test.go",
"chars": 1286,
"preview": "package dingtalk\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestMarkdownMessage_ToByte(t *testing.T) {\n\tmsg := NewMarkdownM"
},
{
"path": "pkg/dingtalk/message.go",
"chars": 576,
"preview": "package dingtalk\n\n// MsgType message type enum\ntype MsgType string\n\nconst (\n\t// MsgTypeText text\n\tMsgTypeText MsgType = "
},
{
"path": "pkg/dingtalk/text.go",
"chars": 839,
"preview": "package dingtalk\n\nimport \"encoding/json\"\n\n// TextMessage text message struct\ntype TextMessage struct {\n\tMsgType MsgType "
},
{
"path": "pkg/dingtalk/text_test.go",
"chars": 1192,
"preview": "package dingtalk\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTextMessage_ToByte(t *testing.T) {\n\tmsg := NewTextMessage()"
},
{
"path": "scripts/mock.sh",
"chars": 412,
"preview": "#!/bin/bash\nshell_dir=$(dirname $0)\ncd ${shell_dir}\n\ncd ..\n\nrm -rf test/mocks\nmkdir -p test/mocks/message\n\n# open with v"
},
{
"path": "scripts/test.sh",
"chars": 322,
"preview": "#!/bin/bash\nshell_dir=$(dirname $0)\ncd ${shell_dir}\n\ncd ..\n\nset -e\necho \"\" > coverage.txt\n\nfor d in $(go list ./... | gr"
},
{
"path": "sonar-project.properties",
"chars": 623,
"preview": "# must be unique in a given SonarQube instance\nsonar.projectKey=dingtalk\n\n# --- optional properties ---\n\n# defaults to p"
},
{
"path": "test/mocks/message/message.go",
"chars": 1286,
"preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: message.go\n\n// Package mock_message is a generated GoMock package."
}
]
About this extraction
This page contains the full source code of the CatchZeng/dingtalk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (118.2 KB), approximately 50.1k tokens, and a symbol index with 121 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.