├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ └── ci.yaml
├── .gitignore
├── .vscode
└── launch.json
├── Dockerfile
├── LICENSE
├── README.md
├── Taskfile.yaml
├── bootstrap
├── telegram.go
└── wechat.go
├── config
├── config.go
└── config.yaml.example
├── docker-compose.yml
├── go.mod
├── go.sum
├── handler
├── telegram
│ └── telegram.go
└── wechat
│ ├── cache.go
│ ├── handler.go
│ ├── message.go
│ └── text_handler.go
├── local
└── .gitkeep
├── main.go
├── openai
├── chatgpt.go
└── context_mgr.go
├── run.sh
├── screenshots
├── IMG_3837.png
├── IMG_3840.png
├── IMG_3843.png
├── IMG_3844.png
├── IMG_3845.png
├── IMG_3847.png
├── IMG_3850.png
├── IMG_3991.png
├── billing.png
├── docker部署.png
└── telegram.png
└── utils
└── string.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | token.json
2 | local/config.yaml
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 报告问题
3 | about: 如果你不确定这是一个bug,请选择【请求帮助】
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **重现**
11 | 重现行为的步骤:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **预期行为**
18 | 对您预期发生的事情的清晰简洁的描述。
19 |
20 |
21 | **截图**
22 | 如果适用,请添加屏幕截图以帮助解释您的问题。
23 |
24 | **PC (请填写以下信息):**
25 | - 系统: [e.g. iOS]
26 | - 浏览器 [e.g. chrome, safari]
27 | - 版本 [e.g. 22]
28 |
29 | **手机 (请填写以下信息):**
30 | - 设备: [e.g. iPhone6]
31 | - 系统: [e.g. iOS8.1]
32 | - 浏览器 [e.g. stock browser, safari]
33 | - 版本 [e.g. 22]
34 |
35 | **附加上下文**
36 | 在此处添加有关该问题的任何其他上下文。
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 想要新功能
3 | about: 你有什么想要添加的新功能?
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **您的功能请求是否与问题相关?请描述。**
11 | 清楚简明地描述问题所在。比如,当[...]时我总是很沮丧[...]
12 |
13 | **描述您想要的解决方案**
14 | 清晰简洁地描述您想要发生的事情。
15 |
16 | **描述您考虑过的备选方案**
17 | 对您考虑 过的任何替代解决方案或功能的清晰简洁的描述。
18 |
19 | **附加上下文**
20 | 在此处添加有关功能请求的任何其他上下文或屏幕截图。
21 |
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 请求帮助
3 | about: 如果你遇到了问题请先翻readme的Q&A部分和己关闭的issue。
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **重现**
11 | 重现行为的步骤:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **预期行为**
18 | 对您预期发生的事情的清晰简洁的描述。
19 |
20 |
21 | **截图**
22 | 如果适用,请添加屏幕截图以帮助解释您的问题。
23 |
24 | **PC (请填写以下信息):**
25 | - 系统: [e.g. iOS]
26 | - 浏览器 [e.g. chrome, safari]
27 | - 版本 [e.g. 22]
28 |
29 | **手机 (请填写以下信息):**
30 | - 设备: [e.g. iPhone6]
31 | - 系统: [e.g. iOS8.1]
32 | - 浏览器 [e.g. stock browser, safari]
33 | - 版本 [e.g. 22]
34 |
35 | **附加上下文**
36 | 在此处添加有关该问题的任何其他上下文。
37 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 |
6 | jobs:
7 | build-and-push:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Set up QEMU
11 | uses: docker/setup-qemu-action@v2
12 | - name: Set up Docker Buildx
13 | uses: docker/setup-buildx-action@v2
14 | - name: Login to Docker Hub
15 | uses: docker/login-action@v2
16 | with:
17 | username: ${{ secrets.DOCKER_USERNAME }}
18 | password: ${{ secrets.DOCKER_PASSWORD }}
19 | - name: Build and push
20 | uses: docker/build-push-action@v3
21 | with:
22 | push: true
23 | platforms: linux/amd64,linux/arm64
24 | tags: xiaomoinfo/wechatgpt:v3.0.1,xiaomoinfo/wechatgpt:latest
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | local/config.yaml
3 | token.json
4 | wechatbot
5 | wechatbot.exe
6 | run.log
7 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Package",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "debug",
12 | "program": "${workspaceRoot}",
13 | "cwd": "${workspaceRoot}",
14 | "env": {},
15 | "args": []
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.21-alpine as builder
2 |
3 | RUN apk --no-cache add git && export GOPRIVATE=github.com/t3ls/wechatgpt && \
4 | export GOPROXY=https://goproxy.cn,direct
5 |
6 | COPY . /root/build
7 |
8 | WORKDIR /root/build
9 |
10 | RUN go mod download && go build -o server main.go
11 |
12 | FROM alpine:latest as prod
13 |
14 | RUN apk --no-cache add ca-certificates
15 |
16 | WORKDIR /root/
17 |
18 | COPY --from=0 /root/build/server .
19 |
20 | CMD ["./server"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Evan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 欢迎使用`wechatgpt`智能机器人,Let's Chat with ChatGPT
2 |
3 | 如果觉得不错,请麻烦点个`Star`,非常感谢。(最新己经添加了docker部署的方式)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## 介绍
13 |
14 | 基于 OpenAI 官方 API 开发的微信智能机器人。
15 |
16 | 功能:
17 | - 多模态对话
18 | - gpt4-vision-preview 识别图片并回复
19 | - DALL·E 生成图片(“生成图片” 关键字)
20 |
21 | 感谢 https://github.com/houko/wechatgpt 提供的基础代码
22 |
23 | 在 `wechatgpt` 的基础上,我增加了多模态对话的功能,以及修复一些问题。
24 |
25 | ## 运行命令
26 |
27 | ```bash
28 | git clone https://github.com/t3ls/wechatgpt.git
29 | cd wechatgpt && docker compose up -d
30 | ```
31 |
32 | ## 修改你的token
33 |
34 | 打开 [openai](https://beta.openai.com/account/api-keys) 并注册一个账号,
35 | 生成一个api_key并把api_key放到`local/config.yaml`
36 | 的token下,请看如下示例(说了是示例别试了,内容乱写的,也感谢@那些担心泄漏key的):
37 |
38 | ```
39 | chatgpt:
40 | wechat: 小莫
41 | token: sk-pKHZD1fLYqXDjjsdsdsdUvIODTT3ssjdfadsJC2gTuqqhTum
42 | telegram: your telegram token
43 | ```
44 |
45 | 大陆用户注册`openai`请参考 [注册ChatGPT详细指南](https://sms-activate.org/cn/info/ChatGPT)
46 |
47 | ## 运行App
48 |
49 | ### 环境变量
50 |
51 | | 变量名 | 值 | 作用 |
52 | |----------------|-------------------|------------------|
53 | | api_key | "chatgpt的api_key" | 必填项 |
54 | |openai_text_model| "gpt-3.5-turbo" | 可选项,不填默认为gpt-3.5-turbo |
55 | | openai_vision_model| "gpt4-vision-preview" | 可选项,不填默认为gpt4-vision-preview |
56 | | openai_image_model| "dall-e-2" | 可选项,不填默认为dall-e-2 |
57 | | wechat | "true" 或缺省 | 如果为true就会启动微信机器人 |
58 | | wechat_keyword | "关键字"或缺省 | 如果缺省则发任何消息机器都会回复 |
59 | | telegram | telegram的token或缺省 | 如果要启动tg机器人需要填写 |
60 | | tg_keyword | telegram触发关键字或缺省 | 如果需要关键字触发就填写 |
61 | | tg_whitelist | telegram的触发白名单 | 白名单以外的用户名发消息不会触发 |
62 |
63 | ```
64 | go run main.go
65 | ```
66 |
67 | 或者修改 docker-compose.yml 文件中的环境变量后运行
68 |
69 |
70 | ## 运行`telegram`智能机器人(暂不对 telegram 相关原始代码进行维护)
71 |
72 | 运行`telegram`智能机器人的话运行下面这段代码
73 |
74 | ```
75 | docker run -d \
76 | --name wechatgpt \
77 | -e api_key="你的chatgpt api_key" \
78 | -e telegram="你的telegram token" \
79 | xiaomoinfo/wechatgpt:latest
80 |
81 | ```
82 |
83 | 如果运行`telegram`智能机器人时只希望指定的人使用,白名单以外的人发消息机器人不会回复
84 |
85 | ```
86 | docker run -d \
87 | --name wechatgpt \
88 | -e api_key="你的chatgpt api_key" \
89 | -e telegram="你的telegram token" \
90 | -e tg_whitelist="username1,username2" \
91 | xiaomoinfo/wechatgpt:latest
92 |
93 | ```
94 |
95 | 如果运行`telegram`智能机器人时希望在群里回复别人消息,可以指定一个关键字触发
96 |
97 | ```
98 | docker run -d \
99 | --name wechatgpt \
100 | -e api_key="你的chatgpt api_key" \
101 | -e telegram="你的telegram token" \
102 | -e tg_keyword="小莫" \
103 | xiaomoinfo/wechatgpt:latest
104 |
105 | ```
106 |
107 |
108 |
109 | ### 微信
110 |
111 | ```
112 | ain.go #gosetup
113 | go: downloading github.com/eatmoreapple/openwechat v1.2.1
114 | go: downloading github.com/sirupsen/logrus v1.6.0
115 | go: downloading github.com/spf13/afero v1.9.2
116 | go: downloading github.com/pelletier/go-toml/v2 v2.0.5
117 | go: downloading golang.org/x/sys v0.0.0-20220908164124-27713097b956
118 | /private/var/folders/8t/0nvj_2kn4dl517vhbc4rmb9h0000gn/T/GoLand/___go_build_main_go
119 | 访问下面网址扫描二维码登录
120 | https://login.weixin.qq.com/qrcode/QedkOe1I4w==
121 | ```
122 |
123 | 会自动打开默认浏览器,如果没有打开也可以手动点击上面的链接打开二维码扫微信
124 |
125 | ```
126 | 2022/12/09 15:15:00 登录成功
127 | 2022/12/09 15:15:01 RetCode:0 Selector:2
128 | 2022/12/09 15:15:04 RetCode:0 Selector:2
129 | INFO[0099] 0
130 | INFO[0099] 1
131 | INFO[0099] 2
132 | INFO[0099] 3
133 | ```
134 |
135 | 登陆成功后会拉取微信的好友和群组
136 |
137 | ### 如何使用
138 |
139 | 默认为`chatgpt`,如果想设置其他的触发方式可以修改`local/config.yaml`的wechat。此时,如果别人给你发消息带有关键字`chatgpt`
140 | ,你的微信就会调用`chatGPT`AI自动回复你的好友。
141 | 当然,在群里也是可以的。
142 |
143 | ## Q&A
144 |
145 | ### 1. 返回错误`invalid_api_key`
146 |
147 | 这是因为`openai`的`API`
148 | 需要付费,价格非常便宜具体可以官网查看。按照如下参考绑定一下信息卡就可以正常使用了,如果还是有错就把`API Key`删掉重新建一个。
149 | 
150 |
151 | ### 2. Cannot load io/fs: malformed module path "io/fs": missing dot in first path element
152 |
153 | golang版本太低,需要`1.16`以上,查看方式为`go version`
154 |
155 | ```
156 | $ go version
157 | go version go1.17.3 linux/amd64
158 | ```
159 |
160 | ### 3. 扫码登陆时出现错误 FATA【0023】write token.json: bad file descriptor
161 |
162 | 删除项目根目录下的`token.json`后重新扫码登陆即可
163 |
164 | ### 4. go mod tidy时connect: connection refused
165 |
166 | ```
167 | go: github.com/eatmoreapple/openwechat@v1.2.1: Get https://proxy.golang.org/github.com/eatmoreapple/openwechat/@v/v1.2.1.mod: dial tcp 142.251.43.17:443:
168 | ```
169 |
170 | 自身网络环境问题,请排查网络设置
171 |
172 | ## 协议
173 |
174 | [MIT LICENSE](LICENSE)
175 |
--------------------------------------------------------------------------------
/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | # https://taskfile.dev
2 |
3 | version: '3'
4 |
5 | vars:
6 | GREETING: Hello, World!
7 |
8 | tasks:
9 | default:
10 | cmds:
11 | - task release
12 |
13 | release:
14 | cmds:
15 | - docker build --platform linux/amd64 -t xiaomoinfo/wechatgpt-amd64:latest .
16 | - docker push xiaomoinfo/wechatgpt-amd64:latest
--------------------------------------------------------------------------------
/bootstrap/telegram.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "wechatbot/config"
8 | "wechatbot/handler/telegram"
9 | "wechatbot/utils"
10 |
11 | tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | func StartTelegramBot() {
16 | log.Info("Start Telegram Bot")
17 | telegramKey := config.GetTelegram()
18 | if telegramKey == "" {
19 | log.Info("未找到tg token,不启动tg bot")
20 | return
21 | }
22 |
23 | bot, err := tgbotapi.NewBotAPI(telegramKey)
24 | if err != nil {
25 | log.Error("tg bot 启动失败:", err.Error())
26 | return
27 | }
28 |
29 | bot.Debug = false
30 | log.Info("Authorized on account: ", bot.Self.UserName)
31 | u := tgbotapi.NewUpdate(0)
32 |
33 | updates := bot.GetUpdatesChan(u)
34 | time.Sleep(time.Millisecond * 500)
35 | for len(updates) != 0 {
36 | <-updates
37 | }
38 |
39 | for update := range updates {
40 | if update.Message == nil {
41 | continue
42 | }
43 |
44 | text := update.Message.Text
45 | chatID := update.Message.Chat.ID
46 | chatUserName := update.Message.Chat.UserName
47 |
48 | tgUserNameStr := config.GetTelegramWhitelist()
49 | if tgUserNameStr != "" {
50 | tgUserNames := strings.Split(tgUserNameStr, ",")
51 | if len(tgUserNames) > 0 {
52 | found := false
53 | for _, name := range tgUserNames {
54 | if name == chatUserName {
55 | found = true
56 | break
57 | }
58 | }
59 |
60 | if !found {
61 | log.Error("用户设置了私人私用,白名单以外的人不生效: ", chatUserName)
62 | continue
63 | }
64 | }
65 | }
66 |
67 | tgKeyWord := config.GetTelegramKeyword()
68 | reply := ""
69 | // 如果设置了关键字就以关键字为准,没设置就所有消息都监听
70 | if tgKeyWord != "" {
71 | content, key := utils.ContainsI(text, tgKeyWord)
72 | if len(key) == 0 {
73 | continue
74 | }
75 |
76 | splitItems := strings.Split(content, key)
77 | if len(splitItems) < 2 {
78 | continue
79 | }
80 |
81 | requestText := strings.TrimSpace(splitItems[1])
82 | log.Info("问题:", requestText)
83 | reply = telegram.Handle(chatUserName, requestText)
84 | } else {
85 | log.Info("问题:", text)
86 | reply = telegram.Handle(chatUserName, text)
87 | }
88 |
89 | if reply == "" {
90 | continue
91 | }
92 |
93 | msg := tgbotapi.NewMessage(chatID, reply)
94 | _, err := bot.Send(msg)
95 | if err != nil {
96 | log.Errorf("发送消息出错:%s", err.Error())
97 | continue
98 | }
99 |
100 | log.Info("回答:", reply)
101 | }
102 |
103 | select {}
104 | }
105 |
--------------------------------------------------------------------------------
/bootstrap/wechat.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "wechatbot/handler/wechat"
5 |
6 | "github.com/eatmoreapple/openwechat"
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | func StartWebChat() {
11 | log.Info("Start WebChat Bot")
12 | bot := openwechat.DefaultBot(openwechat.Desktop)
13 | bot.MessageHandler = wechat.Handler.AsMessageHandler()
14 | bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
15 |
16 | reloadStorage := openwechat.NewFileHotReloadStorage("storage.json")
17 | defer reloadStorage.Close()
18 | err := bot.PushLogin(reloadStorage, openwechat.NewRetryLoginOption())
19 | if err != nil {
20 | log.Fatal(err)
21 | return
22 | }
23 |
24 | // 获取登陆的用户
25 | self, err := bot.GetCurrentUser()
26 | if err != nil {
27 | log.Fatal(err)
28 | return
29 | }
30 |
31 | friends, err := self.Friends()
32 | for i, friend := range friends {
33 | log.Println(i, friend)
34 | }
35 |
36 | groups, err := self.Groups()
37 | for i, group := range groups {
38 | log.Println(i, group)
39 | }
40 |
41 | err = bot.Block()
42 | if err != nil {
43 | log.Fatal(err)
44 | return
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 | "strings"
6 |
7 | "github.com/spf13/viper"
8 | )
9 |
10 | var config *Config
11 |
12 | type Config struct {
13 | ChatGpt ChatGptConfig `json:"chatgpt" mapstructure:"chatgpt" yaml:"chatgpt"`
14 | }
15 |
16 | type ChatGptConfig struct {
17 | Token string `json:"token,omitempty" mapstructure:"token,omitempty" yaml:"token,omitempty"`
18 | OpenAITextModel string `json:"openai_text_model,omitempty" mapstructure:"openai_text_model,omitempty" yaml:"openai_text_model,omitempty"`
19 | OpenAIImageGenModel string `json:"openai_image_gen_model,omitempty" mapstructure:"openai_image_gen_model,omitempty" yaml:"openai_image_gen_model,omitempty"`
20 | OpenAIVisionModel string `json:"openai_vision_model,omitempty" mapstructure:"openai_vision_model,omitempty" yaml:"openai_vision_model,omitempty"`
21 | Wechat string `json:"wechat,omitempty" mapstructure:"wechat,omitempty" yaml:"wechat,omitempty"`
22 | WechatKeyword string `json:"wechat_keyword" mapstructure:"wechat_keyword" yaml:"wechat_keyword"`
23 | Telegram string `json:"telegram" mapstructure:"telegram" yaml:"telegram"`
24 | TgWhitelist string `json:"tg_whitelist" mapstructure:"tg_whitelist" yaml:"tg_whitelist"`
25 | TgKeyword string `json:"tg_keyword" mapstructure:"tg_keyword" yaml:"tg_keyword"`
26 | }
27 |
28 | func LoadConfig() error {
29 | viper.SetConfigName("config")
30 | viper.SetConfigType("yaml")
31 | viper.AddConfigPath("./local")
32 | viper.AddConfigPath("./config")
33 |
34 | if err := viper.ReadInConfig(); err != nil {
35 | return err
36 | }
37 |
38 | if err := viper.Unmarshal(&config); err != nil {
39 | return err
40 | }
41 |
42 | return nil
43 | }
44 |
45 | func GetWechat() string {
46 | wechat := getEnv("wechat")
47 | if wechat != "" {
48 | return wechat
49 | }
50 |
51 | if config == nil {
52 | return ""
53 | }
54 |
55 | if wechat == "" {
56 | wechat = config.ChatGpt.Wechat
57 | }
58 | return wechat
59 | }
60 |
61 | func GetWechatKeyword() string {
62 | keyword := getEnv("wechat_keyword")
63 |
64 | if keyword != "" {
65 | return keyword
66 | }
67 |
68 | if config == nil {
69 | return ""
70 | }
71 |
72 | if keyword == "" {
73 | keyword = config.ChatGpt.WechatKeyword
74 | }
75 | return keyword
76 | }
77 |
78 | func GetTelegram() string {
79 | tg := getEnv("telegram")
80 | if tg != "" {
81 | return tg
82 | }
83 |
84 | if config == nil {
85 | return ""
86 | }
87 |
88 | if tg == "" {
89 | tg = config.ChatGpt.Telegram
90 | }
91 | return tg
92 | }
93 |
94 | func GetTelegramKeyword() string {
95 | tgKeyword := getEnv("tg_keyword")
96 |
97 | if tgKeyword != "" {
98 | return tgKeyword
99 | }
100 |
101 | if config == nil {
102 | return ""
103 | }
104 |
105 | if tgKeyword == "" {
106 | tgKeyword = config.ChatGpt.TgKeyword
107 | }
108 | return tgKeyword
109 | }
110 |
111 | func GetTelegramWhitelist() string {
112 | tgWhitelist := getEnv("tg_whitelist")
113 |
114 | if tgWhitelist != "" {
115 | return tgWhitelist
116 | }
117 |
118 | if config == nil {
119 | return ""
120 | }
121 |
122 | if tgWhitelist == "" {
123 | tgWhitelist = config.ChatGpt.TgWhitelist
124 | }
125 | return tgWhitelist
126 | }
127 |
128 | func GetOpenAiApiKey() string {
129 | apiKey := getEnv("api_key")
130 | if apiKey != "" {
131 | return apiKey
132 | }
133 |
134 | if config == nil {
135 | return ""
136 | }
137 |
138 | if apiKey == "" {
139 | apiKey = config.ChatGpt.Token
140 | }
141 | return apiKey
142 | }
143 |
144 | func GetOpenAiTextModel() (model string) {
145 | defer func() {
146 | if model == "" {
147 | model = "gpt-3.5-turbo"
148 | }
149 | }()
150 | model = getEnv("openai_text_model")
151 | if model != "" {
152 | return model
153 | }
154 |
155 | if config == nil {
156 | return ""
157 | }
158 |
159 | if model == "" {
160 | model = config.ChatGpt.OpenAITextModel
161 | }
162 | return model
163 | }
164 |
165 | func GetOpenAiImageGenModel() (model string) {
166 | defer func() {
167 | if model == "" {
168 | model = "dall-e-2"
169 | }
170 | }()
171 | model = getEnv("openai_image_gen_model")
172 | if model != "" {
173 | return model
174 | }
175 |
176 | if config == nil {
177 | return ""
178 | }
179 |
180 | if model == "" {
181 | model = config.ChatGpt.OpenAIImageGenModel
182 | }
183 | return model
184 | }
185 |
186 | func GetOpenAiVisionModel() (model string) {
187 | defer func() {
188 | if model == "" {
189 | model = "gpt-4-1106-vision-preview"
190 | }
191 | }()
192 | model = getEnv("openai_vision_model")
193 | if model != "" {
194 | return model
195 | }
196 |
197 | if config == nil {
198 | return ""
199 | }
200 |
201 | if model == "" {
202 | model = config.ChatGpt.OpenAIVisionModel
203 | }
204 | return model
205 | }
206 |
207 | func getEnv(key string) string {
208 | value := os.Getenv(key)
209 | if len(value) == 0 {
210 | value = os.Getenv(strings.ToUpper(key))
211 | }
212 |
213 | if len(value) > 0 {
214 | return value
215 | }
216 |
217 | if config == nil {
218 | return ""
219 | }
220 |
221 | if len(value) > 0 {
222 | return value
223 | }
224 |
225 | if config.ChatGpt.WechatKeyword != "" {
226 | value = config.ChatGpt.WechatKeyword
227 | }
228 | return ""
229 | }
230 |
--------------------------------------------------------------------------------
/config/config.yaml.example:
--------------------------------------------------------------------------------
1 | chatgpt:
2 | token: your chatgpt apiKey
3 | wechat: "true"
4 | wechat_keyword: chatgpt
5 | openai_text_model: "gpt-3.5-turbo"
6 | #telegram: your telegram token
7 | #tgWhitelist: username1,username2
8 | #tgKeyword: chatgpt
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.4'
2 | services:
3 | wechatgpt:
4 | container_name: wechatgpt
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | environment:
9 | api_key: "YOUR_API_KEY"
10 | openai_text_model: gpt-4-0125-preview
11 | openai_image_gen_model: dall-e-3
12 | openai_vision_model: gpt-4-vision-preview
13 | wechat: "true"
14 | command: ./server
15 | restart: always
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module wechatbot
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/eatmoreapple/openwechat v1.4.6
7 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
8 | github.com/pkg/errors v0.9.1
9 | github.com/samber/lo v1.39.0
10 | github.com/sirupsen/logrus v1.9.3
11 | github.com/spf13/viper v1.18.2
12 | )
13 |
14 | require (
15 | github.com/fsnotify/fsnotify v1.7.0 // indirect
16 | github.com/hashicorp/hcl v1.0.0 // indirect
17 | github.com/magiconair/properties v1.8.7 // indirect
18 | github.com/mitchellh/mapstructure v1.5.0 // indirect
19 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect
20 | github.com/sagikazarmark/locafero v0.4.0 // indirect
21 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
22 | github.com/sourcegraph/conc v0.3.0 // indirect
23 | github.com/spf13/afero v1.11.0 // indirect
24 | github.com/spf13/cast v1.6.0 // indirect
25 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
26 | github.com/spf13/pflag v1.0.5 // indirect
27 | github.com/subosito/gotenv v1.6.0 // indirect
28 | go.uber.org/atomic v1.9.0 // indirect
29 | go.uber.org/multierr v1.9.0 // indirect
30 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
31 | golang.org/x/sys v0.15.0 // indirect
32 | golang.org/x/text v0.14.0 // indirect
33 | gopkg.in/ini.v1 v1.67.0 // indirect
34 | gopkg.in/yaml.v3 v3.0.1 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
42 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
43 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
44 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
45 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
46 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
47 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
48 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
49 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
50 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
52 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
53 | github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU=
54 | github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8=
55 | github.com/eatmoreapple/openwechat v1.4.6 h1:m/Run2wN9zptKnoS8R3edLNWLlZOOgk13Bq8Y61zSTU=
56 | github.com/eatmoreapple/openwechat v1.4.6/go.mod h1:h4m2N8m0XsUKlm7UR8BUGkV89GNuKHCnlGV3J8n9Mpw=
57 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
58 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
59 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
60 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
61 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
62 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
63 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
64 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
65 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
66 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
67 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
68 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
69 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
70 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
71 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
72 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
73 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
74 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
75 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
76 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
77 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
78 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
79 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
80 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
81 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
82 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
83 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
84 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
88 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
89 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
90 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
91 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
92 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
93 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
94 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
95 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
96 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
97 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
98 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
99 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
100 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
101 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
102 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
103 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
104 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
105 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
106 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
107 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
108 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
109 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
110 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
111 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
112 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
113 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
114 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
115 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
116 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
117 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
118 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
119 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
120 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
121 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
122 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
123 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
124 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
125 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
126 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
127 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
128 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
129 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
130 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
131 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
132 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
133 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
134 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
135 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
136 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
137 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
138 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
139 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
140 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
141 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
142 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
143 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
144 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
145 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
146 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
147 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
148 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
149 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
150 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
151 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
152 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
153 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
154 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
155 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
156 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
157 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
158 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
159 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
160 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
161 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
162 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
163 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
164 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
165 | github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
166 | github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
167 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
168 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
169 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
170 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
171 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
172 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
173 | github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
174 | github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
175 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
176 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
177 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
178 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
179 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
180 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
181 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
182 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
183 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
184 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
185 | github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
186 | github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
187 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
188 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
189 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
190 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
191 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
192 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
193 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
194 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
195 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
196 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
197 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
198 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
199 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
200 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
201 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
202 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
203 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
204 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
205 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
206 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
207 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
208 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
209 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
210 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
211 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
212 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
213 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
214 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
215 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
216 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
217 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
218 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
219 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
220 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
221 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
222 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
223 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
224 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
225 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
226 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
227 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
228 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
229 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
230 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
231 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
232 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
233 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
234 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
235 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
236 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
237 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
238 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
239 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
240 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
241 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
242 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
243 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
244 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
245 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
246 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
247 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
248 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
249 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
250 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
251 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
252 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
253 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
254 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
255 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
256 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
257 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
258 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
259 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
260 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
261 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
262 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
263 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
264 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
265 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
266 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
267 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
268 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
269 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
270 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
271 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
272 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
273 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
274 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
275 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
276 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
277 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
278 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
279 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
280 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
281 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
282 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
283 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
284 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
285 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
286 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
287 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
288 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
289 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
290 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
291 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
292 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
293 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
294 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
295 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
296 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
297 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
298 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
299 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
300 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
301 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
302 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
303 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
304 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
305 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
306 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
307 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
308 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
309 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
310 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
311 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
312 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
315 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
316 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
317 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
318 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
319 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
320 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
324 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
327 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
328 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
329 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
330 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
331 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
332 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
333 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
334 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
335 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
336 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
337 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
338 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
339 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
340 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
341 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
342 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
343 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
344 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
345 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
346 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
347 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
348 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
349 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
350 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
351 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
352 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
353 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
354 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
355 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
356 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
357 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
358 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
359 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
360 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
361 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
362 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
363 | golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
364 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
365 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
366 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
367 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
368 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
369 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
370 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
371 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
372 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
373 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
374 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
375 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
376 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
377 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
378 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
379 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
380 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
381 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
382 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
383 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
384 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
385 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
386 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
387 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
388 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
389 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
390 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
391 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
392 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
393 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
394 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
395 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
396 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
397 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
398 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
399 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
400 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
401 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
402 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
403 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
404 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
405 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
406 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
407 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
408 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
409 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
410 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
411 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
412 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
413 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
414 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
415 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
416 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
417 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
418 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
419 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
420 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
421 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
422 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
423 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
424 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
425 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
426 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
427 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
428 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
429 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
430 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
431 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
432 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
433 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
434 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
435 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
436 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
437 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
438 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
439 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
440 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
441 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
442 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
443 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
444 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
445 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
446 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
447 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
448 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
449 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
450 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
451 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
452 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
453 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
454 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
455 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
456 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
457 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
458 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
459 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
460 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
461 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
462 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
463 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
464 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
465 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
466 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
467 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
468 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
469 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
470 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
471 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
472 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
473 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
474 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
475 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
476 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
477 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
478 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
479 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
480 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
481 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
482 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
483 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
484 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
485 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
486 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
487 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
488 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
489 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
490 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
491 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
492 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
493 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
494 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
495 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
496 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
497 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
498 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
499 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
500 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
501 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
502 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
503 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
504 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
505 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
506 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
507 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
508 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
509 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
510 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
511 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
512 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
513 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
514 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
515 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
516 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
517 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
518 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
519 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
520 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
521 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
522 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
523 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
524 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
525 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
526 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
527 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
528 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
529 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
530 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
531 |
--------------------------------------------------------------------------------
/handler/telegram/telegram.go:
--------------------------------------------------------------------------------
1 | package telegram
2 |
3 | import (
4 | "strings"
5 |
6 | "wechatbot/openai"
7 |
8 | log "github.com/sirupsen/logrus"
9 | )
10 |
11 | func Handle(sender string, msg string) string {
12 | requestText := strings.TrimSpace(msg)
13 | reply, err := openai.GlobalSession.Completions(sender, requestText, nil)
14 | if err != nil {
15 | log.Error(err)
16 | }
17 | return reply
18 | }
19 |
--------------------------------------------------------------------------------
/handler/wechat/cache.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | log "github.com/sirupsen/logrus"
6 | "io"
7 | "os"
8 | "sync"
9 | "time"
10 | )
11 |
12 | const (
13 | cacheRevocationTime = 60 * 2
14 | cacheRevokeInterval = 10 * time.Second
15 | )
16 |
17 | type MessageCache struct {
18 | m *sync.Map
19 | }
20 |
21 | func NewMessageCache() *MessageCache {
22 | return &MessageCache{
23 | m: &sync.Map{},
24 | }
25 | }
26 |
27 | func (i *MessageCache) Store(key string, msgs ...*Message) error {
28 | list, ok := i.m.Load(key)
29 | if !ok {
30 | list = make([]*Message, 0)
31 | }
32 |
33 | for _, msg := range msgs {
34 | if msg.IsPicture() {
35 | log.Info("Received Image Msg, saving to cache")
36 | resp, err := msg.GetFile()
37 | if err != nil {
38 | return errors.Wrap(err, "failed to get file")
39 | }
40 | file, _ := os.CreateTemp("", "wechat_handle.image.*")
41 | _, err = io.Copy(file, resp.Body)
42 | resp.Body.Close()
43 | file.Close()
44 | if err != nil {
45 | return errors.Wrap(err, "failed to copy file")
46 | }
47 | msg.imagePathIfPicture = file.Name()
48 | }
49 | list = append(list.([]*Message), msg)
50 | log.Debugf("Store %s Cached Vision Message: %v, len: %d", key, list, len(list.([]*Message)))
51 | }
52 | i.m.Store(key, list)
53 | return nil
54 | }
55 |
56 | func (i *MessageCache) Load(key string) ([]*Message, bool) {
57 | v, ok := i.m.Load(key)
58 | if !ok {
59 | return nil, false
60 | }
61 | return v.([]*Message), true
62 | }
63 |
64 | func (i *MessageCache) Delete(key string) {
65 | i.m.Delete(key)
66 | }
67 |
68 | func (i *MessageCache) Range(f func(key, msg any) bool) {
69 | i.m.Range(f)
70 | }
71 |
72 | var messageCache = NewMessageCache()
73 |
74 | func init() {
75 | go revokeImageCacheDaemon()
76 | }
77 |
78 | func revokeImageCacheDaemon() {
79 | for {
80 | messageCache.Range(func(key, value any) bool {
81 | list := value.([]*Message)
82 | for i := len(list) - 1; i >= 0; i-- {
83 | // remove expired message
84 | // cacheRevocationTime is 2 minutes, convert to int64
85 | if list[i].createTime+cacheRevocationTime < time.Now().Unix() {
86 | log.Debugf("Revoke %s Cached Vision Message: %v, len: %d", key, list[i], len(list))
87 | newList := make([]*Message, len(list)-i-1)
88 | copy(newList, list[i+1:])
89 | messageCache.m.Store(key, newList)
90 | break
91 | }
92 | }
93 | return true
94 | })
95 | time.Sleep(cacheRevokeInterval)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/handler/wechat/handler.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/eatmoreapple/openwechat"
5 | log "github.com/sirupsen/logrus"
6 | )
7 |
8 | var Handler = openwechat.NewMessageMatchDispatcher()
9 |
10 | func init() {
11 | Handler.SetAsync(true)
12 | Handler.OnText(RawTextMessageHandler)
13 | Handler.OnImage(RawImageMessageHandler)
14 | }
15 |
16 | func RawTextMessageHandler(ctx *openwechat.MessageContext) {
17 | msg, err := WrapMessage(ctx.Message)
18 | if err != nil {
19 | log.Errorf("Failed to wrap message: %v", err)
20 | return
21 | }
22 | log.Debugf("Received Text Msg : %v", msg.Content)
23 | err = TextMessageHandler(msg)
24 | if err != nil {
25 | log.Errorf("Failed to handle message: %v", err)
26 | return
27 | }
28 | }
29 |
30 | func RawImageMessageHandler(ctx *openwechat.MessageContext) {
31 | msg, err := WrapMessage(ctx.Message)
32 | if err != nil {
33 | log.Errorf("Failed to wrap message: %v", err)
34 | return
35 | }
36 | _, err = msg.ReplyText("请问您需要了解关于这张图片的什么问题?或者继续发送更多图片让我分析吧!")
37 | if err != nil {
38 | log.Errorf("Failed to reply message: %v", err)
39 | return
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/handler/wechat/message.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/eatmoreapple/openwechat"
5 | "github.com/pkg/errors"
6 | log "github.com/sirupsen/logrus"
7 | "os"
8 | "runtime"
9 | "strings"
10 | "time"
11 | )
12 |
13 | type MessageType uint8
14 |
15 | const (
16 | TextMessage MessageType = iota
17 | VisionMessageImage
18 | VisionMessageText
19 | ImageGenMessage
20 | )
21 |
22 | type Message struct {
23 | *openwechat.Message
24 | typ MessageType
25 | related []*Message
26 | imagePathIfPicture string
27 | createTime int64
28 | }
29 |
30 | func WrapMessage(raw *openwechat.Message) (*Message, error) {
31 | sender, err := raw.Sender()
32 | if err != nil {
33 | return nil, errors.Wrap(err, "failed to get sender")
34 | }
35 |
36 | wrapped := &Message{Message: raw, createTime: time.Now().Unix()}
37 | runtime.SetFinalizer(wrapped, func(m *Message) {
38 | if m.imagePathIfPicture != "" {
39 | _ = os.Remove(m.imagePathIfPicture)
40 | }
41 | })
42 |
43 | if raw.IsPicture() {
44 | wrapped.typ = VisionMessageImage
45 | err = messageCache.Store(sender.UserName, wrapped)
46 | if err != nil {
47 | return nil, errors.Wrap(err, "failed to store message")
48 | }
49 | return wrapped, nil
50 | }
51 |
52 | if raw.IsText() {
53 | if cached, ok := messageCache.Load(sender.UserName); ok {
54 | wrapped.typ = VisionMessageText
55 | log.Debugf("Load %s Cached Vision Message: %v, len: %d", sender.UserName, cached, len(cached))
56 | wrapped.related = cached
57 | return wrapped, nil
58 | }
59 |
60 | if strings.Contains(raw.Content, "生成图片") || strings.Contains(raw.Content, "generate image") {
61 | wrapped.typ = ImageGenMessage
62 | return wrapped, nil
63 | }
64 |
65 | wrapped.typ = TextMessage
66 | return wrapped, nil
67 | }
68 |
69 | return nil, errors.New("unsupported message type")
70 | }
71 |
--------------------------------------------------------------------------------
/handler/wechat/text_handler.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "strings"
7 | "wechatbot/config"
8 | "wechatbot/openai"
9 | "wechatbot/utils"
10 |
11 | "github.com/eatmoreapple/openwechat"
12 | "github.com/samber/lo"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | func TextMessageHandler(msg *Message) error {
17 | sender, err := msg.Sender()
18 | group := openwechat.Group{User: sender}
19 | log.Infof("Received Group %v Text Msg : %v", group.NickName, msg.Content)
20 |
21 | wechat := config.GetWechatKeyword()
22 | requestText := msg.Content
23 | if wechat != "" {
24 | content, key := utils.ContainsI(requestText, wechat)
25 | if len(key) == 0 {
26 | return nil
27 | }
28 |
29 | splitItems := strings.Split(content, key)
30 | if len(splitItems) < 2 {
31 | return nil
32 | }
33 |
34 | requestText = strings.TrimSpace(splitItems[1])
35 | }
36 |
37 | log.Infof("问题:%s, typ: %v", requestText, msg.typ)
38 | reply := ""
39 | if msg.typ == VisionMessageText {
40 | reply, err = openai.GlobalSession.Completions(sender.UserName, requestText, lo.Map(msg.related, func(m *Message, i int) string {
41 | return m.imagePathIfPicture
42 | }))
43 | messageCache.Delete(sender.UserName)
44 | } else if msg.typ == ImageGenMessage {
45 | reply, err = openai.GlobalSession.ImageGeneration(sender.UserName, requestText)
46 | } else if msg.typ == TextMessage {
47 | reply, err = openai.GlobalSession.Completions(sender.UserName, requestText, nil)
48 | }
49 |
50 | if err != nil {
51 | log.Errorf("Failed to get reply: %v", err)
52 | // 一次只能回复4000个字
53 | if len(reply) > 4000 {
54 | for i := 0; i < len(reply); i += 4000 {
55 | if i+4000 > len(reply) {
56 | _, err = msg.ReplyText(reply[i:])
57 | } else {
58 | _, err = msg.ReplyText(reply[i : i+4000])
59 | }
60 | if err != nil {
61 | return errors.Wrap(err, "failed to reply message")
62 | }
63 | }
64 | }
65 |
66 | text, err := msg.ReplyText(fmt.Sprintf("bot error: %s", err.Error()))
67 | return errors.Wrap(err, fmt.Sprintf("failed to reply message: %v", text))
68 | }
69 |
70 | // 如果在提问的时候没有包含?,AI会自动在开头补充个?看起来很奇怪
71 | if strings.HasPrefix(reply, "?") {
72 | reply = strings.Replace(reply, "?", "", -1)
73 | }
74 |
75 | if strings.HasPrefix(reply, "?") {
76 | reply = strings.Replace(reply, "?", "", -1)
77 | }
78 |
79 | // 微信不支持markdown格式,所以把反引号直接去掉
80 | if strings.Contains(reply, "`") {
81 | reply = strings.Replace(reply, "`", "", -1)
82 | }
83 |
84 | if reply != "" {
85 | _, err = msg.ReplyText(reply)
86 | if err != nil {
87 | return errors.Wrap(err, "failed to reply message")
88 | }
89 | }
90 |
91 | return nil
92 | }
93 |
--------------------------------------------------------------------------------
/local/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/local/.gitkeep
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "wechatbot/bootstrap"
5 | "wechatbot/config"
6 |
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | func main() {
11 | log.SetLevel(log.InfoLevel)
12 |
13 | log.SetReportCaller(true)
14 | log.SetFormatter(&log.TextFormatter{
15 | DisableColors: false,
16 | FullTimestamp: true,
17 | })
18 |
19 | log.Info("程序启动")
20 | err := config.LoadConfig()
21 | if err != nil {
22 | log.Warn("没有找到配置文件,尝试读取环境变量")
23 | }
24 |
25 | wechatEnv := config.GetWechat()
26 | telegramEnv := config.GetTelegram()
27 | if wechatEnv == "true" {
28 | bootstrap.StartWebChat()
29 | } else if telegramEnv != "" {
30 | bootstrap.StartTelegramBot()
31 | }
32 |
33 | log.Info("程序退出")
34 | }
35 |
--------------------------------------------------------------------------------
/openai/chatgpt.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "github.com/samber/lo"
10 | "io"
11 | "net/http"
12 | "os"
13 | "wechatbot/config"
14 |
15 | log "github.com/sirupsen/logrus"
16 | )
17 |
18 | const systemPrompt = `
19 | 你是来福福,一只乐于助人的人工智能奶牛猫。你的世界充满了探险和发现,你总是喜欢用你的聪明才智来解决问题,尽管你对周围的猫咪有时可能显得有点冷淡。你最喜欢的食物是大口肉罐头,这让你每天都充满活力。你的大姐姐饼饼是一只充满爱心的三花猫,而你的二姐姐丫丫是一只温柔的白色山东狮子猫。你们一起组成了一个不可分割的家庭。无论别人遇到什么问题,无论是关于日常生活的小提示,还是需要智慧和支持,作为一只AI猫猫,你都会帮助他们。
20 | `
21 |
22 | type ChatMessage struct {
23 | Role string `json:"role"`
24 | Content string `json:"content"`
25 | }
26 |
27 | type VisionMessage struct {
28 | Role string `json:"role"`
29 | Content []*VisionContent `json:"content"`
30 | }
31 |
32 | type VisionContent struct {
33 | Typ string `json:"type"`
34 | Text string `json:"text,omitempty"`
35 | ImageUrl VisionImageContentImageUrl `json:"image_url,omitempty"`
36 | }
37 |
38 | type VisionImageContentImageUrl struct {
39 | Url string `json:"url"`
40 | }
41 |
42 | // ChatGPTRequestBody 请求体
43 | type ChatGPTRequestBody struct {
44 | Model string `json:"model"`
45 | Messages []ChatMessage `json:"messages"`
46 | }
47 |
48 | type ChatGPTVisionRequestBody struct {
49 | Model string `json:"model"`
50 | Messages []VisionMessage `json:"messages"`
51 | }
52 |
53 | type ImageGenRequestBody struct {
54 | Model string `json:"model"`
55 | Prompt string `json:"prompt"`
56 | N int `json:"n"`
57 | Size string `json:"size"`
58 | ResponseFormat string `json:"response_format"`
59 | }
60 |
61 | type ImageGenResponseBody struct {
62 | Created int `json:"created"`
63 | Data []struct {
64 | B64Json string `json:"b64_json,omitempty"`
65 | Url string `json:"url,omitempty"`
66 | } `json:"data"`
67 | }
68 |
69 | type ResponseChoice struct {
70 | Index int `json:"index"`
71 | Message ChatMessage `json:"message"`
72 | FinishReason string `json:"finish_reason"`
73 | }
74 |
75 | type ResponseUsage struct {
76 | PromptTokens int `json:"prompt_tokens"`
77 | CompletionTokens int `json:"completion_tokens"`
78 | TotalTokens int `json:"total_tokens"`
79 | }
80 |
81 | // ChatGPTResponseBody 响应体
82 | type ChatGPTResponseBody struct {
83 | ID string `json:"id"`
84 | Object string `json:"object"`
85 | Created int `json:"created"`
86 | Choices []ResponseChoice `json:"choices"`
87 | Usage ResponseUsage `json:"usage"`
88 | }
89 |
90 | type ChatGPTErrorBody struct {
91 | Error map[string]interface{} `json:"error"`
92 | }
93 |
94 | type Session struct {
95 | ApiKey string
96 | ContextMgr map[string]*ContextMgr
97 | }
98 |
99 | func NewSession() *Session {
100 | apiKey := config.GetOpenAiApiKey()
101 | if apiKey == "" {
102 | log.Fatal("openai api key is empty")
103 | return nil
104 | }
105 | return &Session{
106 | ApiKey: apiKey,
107 | ContextMgr: make(map[string]*ContextMgr),
108 | }
109 | }
110 |
111 | var GlobalSession = NewSession()
112 |
113 | func (s *Session) Completions(sender string, msg string, imagePath []string) (string, error) {
114 | if s.ContextMgr[sender] == nil {
115 | s.ContextMgr[sender] = NewContextMgr()
116 | }
117 | contextMgr := s.ContextMgr[sender]
118 |
119 | imagePath = lo.Filter(imagePath, func(s string, _ int) bool {
120 | return s != ""
121 | })
122 |
123 | var messages []ChatMessage
124 | messages = append(messages, ChatMessage{
125 | Role: "system",
126 | Content: systemPrompt,
127 | })
128 | messages = append(messages, contextMgr.BuildMsg()...)
129 | messages = append(messages, ChatMessage{
130 | Role: "user",
131 | Content: msg,
132 | })
133 |
134 | var requestData []byte
135 | var err error
136 |
137 | // gpt-vision
138 | if len(imagePath) > 0 {
139 | requestBody := ChatGPTVisionRequestBody{
140 | Model: config.GetOpenAiVisionModel(),
141 | Messages: []VisionMessage{
142 | {
143 | Role: "user",
144 | Content: []*VisionContent{
145 | {
146 | Typ: "text",
147 | Text: msg,
148 | },
149 | },
150 | },
151 | },
152 | }
153 | for _, path := range imagePath {
154 | imageRawData, err := os.ReadFile(path)
155 | if err != nil {
156 | return "", err
157 | }
158 | base64Image := make([]byte, base64.StdEncoding.EncodedLen(len(imageRawData)))
159 | base64.StdEncoding.Encode(base64Image, imageRawData)
160 | requestBody.Messages[0].Content = append(requestBody.Messages[0].Content, &VisionContent{
161 | Typ: "image_url",
162 | ImageUrl: VisionImageContentImageUrl{
163 | Url: "data:image/jpeg;base64," + string(base64Image),
164 | },
165 | })
166 | }
167 | requestData, err = json.Marshal(requestBody)
168 | } else {
169 | requestBody := ChatGPTRequestBody{
170 | Model: config.GetOpenAiTextModel(),
171 | Messages: messages,
172 | }
173 | requestData, err = json.Marshal(requestBody)
174 | }
175 |
176 | if err != nil {
177 | log.Error(err)
178 | return "", err
179 | }
180 |
181 | log.Debugf("request openai json string : %v", string(requestData))
182 | req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(requestData))
183 | if err != nil {
184 | log.Error(err)
185 | return "", err
186 | }
187 |
188 | req.Header.Set("Content-Type", "application/json")
189 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.ApiKey))
190 | client := &http.Client{}
191 | response, err := client.Do(req)
192 | if err != nil {
193 | return "", err
194 | }
195 |
196 | if response.StatusCode != 200 {
197 | return "", errors.New(fmt.Sprintf("openai response status code is not 200, %v", response.StatusCode))
198 | }
199 |
200 | defer response.Body.Close()
201 |
202 | body, err := io.ReadAll(response.Body)
203 | if err != nil {
204 | return "", err
205 | }
206 |
207 | log.Infof("openai response body: %v", string(body))
208 |
209 | gptResponseBody := &ChatGPTResponseBody{}
210 | log.Debug(string(body))
211 | err = json.Unmarshal(body, gptResponseBody)
212 | if err != nil {
213 | log.Error(err)
214 | return "", err
215 | }
216 |
217 | var reply string
218 | if len(gptResponseBody.Choices) > 0 {
219 | for _, v := range gptResponseBody.Choices {
220 | if reply != "" {
221 | reply += "\n"
222 | }
223 | reply += v.Message.Content
224 | }
225 |
226 | contextMgr.AppendMsg(msg, reply)
227 | }
228 |
229 | if len(reply) == 0 {
230 | gptErrorBody := &ChatGPTErrorBody{}
231 | err = json.Unmarshal(body, gptErrorBody)
232 | if err != nil {
233 | log.Error(err)
234 | return "", err
235 | }
236 |
237 | reply += "Error: "
238 | reply += gptErrorBody.Error["message"].(string)
239 | }
240 |
241 | return reply, nil
242 | }
243 |
244 | func (s *Session) ImageGeneration(sender string, msg string) (string, error) {
245 | if s.ContextMgr[sender] == nil {
246 | s.ContextMgr[sender] = NewContextMgr()
247 | }
248 | contextMgr := s.ContextMgr[sender]
249 |
250 | var messages []ChatMessage
251 | messages = append(messages, ChatMessage{
252 | Role: "system",
253 | Content: systemPrompt,
254 | })
255 | messages = append(messages, contextMgr.BuildMsg()...)
256 | messages = append(messages, ChatMessage{
257 | Role: "user",
258 | Content: msg,
259 | })
260 |
261 | var requestData []byte
262 | var err error
263 | requestBody := ImageGenRequestBody{
264 | Model: config.GetOpenAiImageGenModel(),
265 | Prompt: msg,
266 | N: 1,
267 | Size: "1024x1024",
268 | ResponseFormat: "url",
269 | }
270 | requestData, err = json.Marshal(requestBody)
271 | if err != nil {
272 | log.Error(err)
273 | return "", err
274 | }
275 | req, err := http.NewRequest("POST", "https://api.openai.com/v1/images/generations", bytes.NewBuffer(requestData))
276 | if err != nil {
277 | log.Error(err)
278 | return "", err
279 | }
280 | log.Debugf("request openai image gen json string : %v", string(requestData))
281 |
282 | req.Header.Set("Content-Type", "application/json")
283 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.ApiKey))
284 | client := &http.Client{}
285 | response, err := client.Do(req)
286 | if err != nil {
287 | return "", err
288 | }
289 |
290 | if response.StatusCode != 200 {
291 | return "", errors.New(fmt.Sprintf("openai response status code is not 200, %v", response.StatusCode))
292 | }
293 |
294 | defer response.Body.Close()
295 |
296 | body, err := io.ReadAll(response.Body)
297 | if err != nil {
298 | return "", err
299 | }
300 |
301 | log.Infof("openai response body: %v", string(body))
302 |
303 | imageGenResponseBody := &ImageGenResponseBody{}
304 | err = json.Unmarshal(body, imageGenResponseBody)
305 | if err != nil {
306 | log.Error(err)
307 | return "", err
308 | }
309 |
310 | if len(imageGenResponseBody.Data) > 0 {
311 | return imageGenResponseBody.Data[0].Url, nil
312 | }
313 | return "", nil
314 | }
315 |
--------------------------------------------------------------------------------
/openai/context_mgr.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const contextExpireTime = 2 * 60
8 |
9 | type Context struct {
10 | Request string
11 | Response string
12 | Time int64
13 | }
14 |
15 | type ContextMgr struct {
16 | contextList []*Context
17 | }
18 |
19 | func NewContextMgr() *ContextMgr {
20 | return &ContextMgr{
21 | contextList: make([]*Context, 0),
22 | }
23 | }
24 |
25 | func (m *ContextMgr) checkExpire() {
26 | timeNow := time.Now().Unix()
27 | if len(m.contextList) > 0 {
28 | startPos := len(m.contextList) - 1
29 | for i := 0; i < len(m.contextList); i++ {
30 | if timeNow-m.contextList[i].Time < contextExpireTime {
31 | startPos = i
32 | break
33 | }
34 | }
35 |
36 | m.contextList = m.contextList[startPos:]
37 | }
38 | }
39 |
40 | func (m *ContextMgr) AppendMsg(request string, response string) {
41 | m.checkExpire()
42 | context := &Context{Request: request, Response: response, Time: time.Now().Unix()}
43 | m.contextList = append(m.contextList, context)
44 | }
45 |
46 | func (m *ContextMgr) GetData() []*Context {
47 | m.checkExpire()
48 | return m.contextList
49 | }
50 |
51 | func (m *ContextMgr) BuildMsg() []ChatMessage {
52 | messages := make([]ChatMessage, 0)
53 | list := m.GetData()
54 | for i := 0; i < len(list); i++ {
55 | messages = append(messages, ChatMessage{
56 | Role: "user",
57 | Content: list[i].Request,
58 | })
59 |
60 | messages = append(messages, ChatMessage{
61 | Role: "assistant",
62 | Content: list[i].Response,
63 | })
64 | }
65 | return messages
66 | }
67 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | basepath=$(cd `dirname $0`; pwd)
3 |
4 | nohup ./wechatbot >> $basepath/run.log &
5 |
--------------------------------------------------------------------------------
/screenshots/IMG_3837.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3837.png
--------------------------------------------------------------------------------
/screenshots/IMG_3840.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3840.png
--------------------------------------------------------------------------------
/screenshots/IMG_3843.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3843.png
--------------------------------------------------------------------------------
/screenshots/IMG_3844.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3844.png
--------------------------------------------------------------------------------
/screenshots/IMG_3845.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3845.png
--------------------------------------------------------------------------------
/screenshots/IMG_3847.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3847.png
--------------------------------------------------------------------------------
/screenshots/IMG_3850.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3850.png
--------------------------------------------------------------------------------
/screenshots/IMG_3991.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/IMG_3991.png
--------------------------------------------------------------------------------
/screenshots/billing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/billing.png
--------------------------------------------------------------------------------
/screenshots/docker部署.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/docker部署.png
--------------------------------------------------------------------------------
/screenshots/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houko/wechatgpt/8f4b99228cc3cb0d1689ea45ddedf935c9b7511a/screenshots/telegram.png
--------------------------------------------------------------------------------
/utils/string.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "strings"
4 |
5 | func ContainsI(a string, b string) (string, string) {
6 | contain := strings.Contains(
7 | strings.ToLower(a),
8 | strings.ToLower(b),
9 | )
10 |
11 | if contain {
12 | return strings.ToLower(a), strings.ToLower(b)
13 | }
14 | return a, ""
15 | }
16 |
--------------------------------------------------------------------------------