├── .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 | Version 7 | 8 | License: MIT 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 | drawing 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 | ![img.png](screenshots/billing.png) 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 | --------------------------------------------------------------------------------