├── config.json ├── main.go ├── go.mod ├── .gitignore ├── README.md ├── bootstrap └── bootstrap.go ├── config └── config.go ├── handlers ├── handler.go ├── user_msg_handler.go └── group_msg_handler.go ├── go.sum └── gtp └── gtp.go /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "Bearer 你的ChatGPT Token", 3 | "auto_pass": true 4 | } 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/bootstrap" 5 | ) 6 | 7 | func main() { 8 | bootstrap.Run() 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/869413421/wechatbot 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/abhayptp/go-chatgpt v0.0.0-20221207143216-b080e90544b9 7 | github.com/eatmoreapple/openwechat v1.2.1 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea/ 8 | storage.json 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 目前实现了以下功能 2 | 3 | + 机器人群聊@回复 4 | + 机器人私聊回复 5 | + 好友添加自动通过 6 | ![image](https://user-images.githubusercontent.com/68322685/206850729-901ae567-08e3-4221-8acb-bf32bfaa53c2.png) 7 | 8 | ## 注册openai 9 | 10 | chatGPT注册可以参考[这里](https://juejin.cn/post/7173447848292253704) 11 | 注册完之后登录`https://chat.openai.com`获取token 12 | 13 | ![image](https://user-images.githubusercontent.com/68322685/206850650-dc93c18b-b11a-4c55-8f77-a71da9559fbf.png) 14 | 15 | ## 使用 16 | 17 | 修改`config.json`把里面的`你的token`替换成前面获取的`Bearer `后面的`token`字符串复制进来 18 | 19 | ````bash 20 | # 启动项目 21 | $ ./chatgptWechatbot-macos-arm64 22 | 23 | # 出现下面信息如果没弹浏览器二维码就复制链接到浏览器打开,使用你要自动回复的微信xxxx 24 | 访问下面网址扫描二维码登录 25 | https://login.weixin.qq.com/qrcode/QdXGWxxxx== 26 | 27 | # 登录成功之后,用户私聊你那个机器人微信或者群里艾特你机器人微信都能请求chatgpt获取回复。 28 | ```` 29 | -------------------------------------------------------------------------------- /bootstrap/bootstrap.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/handlers" 5 | "github.com/eatmoreapple/openwechat" 6 | "log" 7 | ) 8 | 9 | 10 | 11 | func Run() { 12 | //bot := openwechat.DefaultBot() 13 | bot := openwechat.DefaultBot(openwechat.Desktop) // 桌面模式,上面登录不上的可以尝试切换这种模式 14 | 15 | // 注册消息处理函数 16 | bot.MessageHandler = handlers.Handler 17 | // 注册登陆二维码回调 18 | bot.UUIDCallback = openwechat.PrintlnQrcodeUrl 19 | 20 | // 创建热存储容器对象 21 | reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json") 22 | // 执行热登录 23 | err := bot.HotLogin(reloadStorage) 24 | if err != nil { 25 | if err = bot.Login(); err != nil { 26 | log.Printf("login error: %v \n", err) 27 | return 28 | } 29 | } 30 | // 阻塞主goroutine, 直到发生异常或者用户主动退出 31 | bot.Block() 32 | } 33 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | // Configuration 项目配置 11 | type Configuration struct { 12 | // gtp apikey 13 | ApiKey string `json:"api_key"` 14 | Token string `json:"token"` 15 | // 自动通过好友 16 | AutoPass bool `json:"auto_pass"` 17 | } 18 | 19 | var config *Configuration 20 | var once sync.Once 21 | 22 | // LoadConfig 加载配置 23 | func LoadConfig() *Configuration { 24 | once.Do(func() { 25 | // 从文件中读取 26 | config = &Configuration{} 27 | f, err := os.Open("config.json") 28 | if err != nil { 29 | log.Fatalf("open config err: %v", err) 30 | return 31 | } 32 | defer f.Close() 33 | encoder := json.NewDecoder(f) 34 | err = encoder.Decode(config) 35 | if err != nil { 36 | log.Fatalf("decode config err: %v", err) 37 | return 38 | } 39 | 40 | // 如果环境变量有配置,读取环境变量 41 | ApiKey := os.Getenv("ApiKey") 42 | AutoPass := os.Getenv("AutoPass") 43 | if ApiKey != "" { 44 | config.ApiKey = ApiKey 45 | } 46 | if AutoPass == "true" { 47 | config.AutoPass = true 48 | } 49 | }) 50 | return config 51 | } 52 | -------------------------------------------------------------------------------- /handlers/handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/config" 5 | "github.com/eatmoreapple/openwechat" 6 | "log" 7 | ) 8 | 9 | // MessageHandlerInterface 消息处理接口 10 | type MessageHandlerInterface interface { 11 | handle(*openwechat.Message) error 12 | ReplyText(*openwechat.Message) error 13 | } 14 | 15 | type HandlerType string 16 | 17 | const ( 18 | GroupHandler = "group" 19 | UserHandler = "user" 20 | ) 21 | 22 | // handlers 所有消息类型类型的处理器 23 | var handlers map[HandlerType]MessageHandlerInterface 24 | 25 | func init() { 26 | handlers = make(map[HandlerType]MessageHandlerInterface) 27 | handlers[GroupHandler] = NewGroupMessageHandler() 28 | handlers[UserHandler] = NewUserMessageHandler() 29 | } 30 | 31 | // Handler 全局处理入口 32 | func Handler(msg *openwechat.Message) { 33 | log.Printf("hadler Received msg : %v", msg.Content) 34 | // 处理群消息 35 | if msg.IsSendByGroup() { 36 | handlers[GroupHandler].handle(msg) 37 | return 38 | } 39 | 40 | // 好友申请 41 | if msg.IsFriendAdd() { 42 | if config.LoadConfig().AutoPass { 43 | _, err := msg.Agree("你好我是基于chatGPT引擎开发的微信机器人,你可以向我提问任何问题。") 44 | if err != nil { 45 | log.Fatalf("add friend agree error : %v", err) 46 | return 47 | } 48 | } 49 | } 50 | 51 | // 私聊 52 | handlers[UserHandler].handle(msg) 53 | } 54 | -------------------------------------------------------------------------------- /handlers/user_msg_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/config" 5 | "github.com/abhayptp/go-chatgpt" 6 | "github.com/eatmoreapple/openwechat" 7 | "log" 8 | "strings" 9 | ) 10 | 11 | var _ MessageHandlerInterface = (*UserMessageHandler)(nil) 12 | 13 | // UserMessageHandler 私聊消息处理 14 | type UserMessageHandler struct { 15 | } 16 | 17 | // handle 处理消息 18 | func (g *UserMessageHandler) handle(msg *openwechat.Message) error { 19 | if msg.IsText() { 20 | return g.ReplyText(msg) 21 | } 22 | return nil 23 | } 24 | 25 | // NewUserMessageHandler 创建私聊处理器 26 | func NewUserMessageHandler() MessageHandlerInterface { 27 | return &UserMessageHandler{} 28 | } 29 | 30 | // ReplyText 发送文本消息到群 31 | func (g *UserMessageHandler) ReplyText(msg *openwechat.Message) error { 32 | // 接收私聊消息 33 | sender, err := msg.Sender() 34 | log.Printf("Received User %v Text Msg : %v", sender.NickName, msg.Content) 35 | 36 | // 向GPT发起请求 37 | token := config.LoadConfig().Token 38 | c := chatgpt.NewChatGpt(chatgpt.NewClient(chatgpt.NewCredentials(token))) 39 | requestText := strings.TrimSpace(msg.Content) 40 | requestText = strings.Trim(msg.Content, "\n") 41 | //reply, err := gtp.Completions(requestText) 42 | reply, err := c.SendMessage(requestText) 43 | if err != nil { 44 | log.Printf("gtp request error: %v \n", err) 45 | msg.ReplyText("机器人神了,我一会发现了就去修。") 46 | return err 47 | } 48 | if reply == "" { 49 | return nil 50 | } 51 | 52 | // 回复用户 53 | reply = strings.TrimSpace(reply) 54 | reply = strings.Trim(reply, "\n") 55 | reply = "[此消息由chatGPT回复]\n" + reply 56 | _, err = msg.ReplyText(reply) 57 | if err != nil { 58 | log.Printf("response user error: %v \n", err) 59 | } 60 | return err 61 | } 62 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/abhayptp/go-chatgpt v0.0.0-20221207143216-b080e90544b9 h1:PzkduaulykGD5Qw3r56mfiXOGW8XMte3s2BnYa9bdPc= 2 | github.com/abhayptp/go-chatgpt v0.0.0-20221207143216-b080e90544b9/go.mod h1:PkuAZ39TA3pBv7/VWj5S3sxpd8ov8Eggc1VsiB8KgXI= 3 | github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 4 | github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= 5 | github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU= 9 | github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8= 10 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 11 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/tmaxmax/go-sse v0.4.2 h1:2GEnHsvzyFjWE0aOTw/TCiaA3Zqu/oMCeto92Cxu/qs= 18 | github.com/tmaxmax/go-sse v0.4.2/go.mod h1:K+M8G9G2kxssBYbdw9QlPSZDmAbNdt1az5Xdjq9AM68= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /handlers/group_msg_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/config" 5 | "github.com/abhayptp/go-chatgpt" 6 | "github.com/eatmoreapple/openwechat" 7 | "log" 8 | "strings" 9 | ) 10 | 11 | var _ MessageHandlerInterface = (*GroupMessageHandler)(nil) 12 | 13 | // GroupMessageHandler 群消息处理 14 | type GroupMessageHandler struct { 15 | } 16 | 17 | // handle 处理消息 18 | func (g *GroupMessageHandler) handle(msg *openwechat.Message) error { 19 | if msg.IsText() { 20 | return g.ReplyText(msg) 21 | } 22 | return nil 23 | } 24 | 25 | // NewGroupMessageHandler 创建群消息处理器 26 | func NewGroupMessageHandler() MessageHandlerInterface { 27 | return &GroupMessageHandler{} 28 | } 29 | 30 | // ReplyText 发送文本消息到群 31 | func (g *GroupMessageHandler) ReplyText(msg *openwechat.Message) error { 32 | // 接收群消息 33 | sender, err := msg.Sender() 34 | group := openwechat.Group{sender} 35 | log.Printf("Received Group %v Text Msg : %v", group.NickName, msg.Content) 36 | 37 | // 不是@的不处理 38 | if !msg.IsAt() { 39 | return nil 40 | } 41 | 42 | // 替换掉@文本,然后向GPT发起请求 43 | token := config.LoadConfig().Token 44 | c := chatgpt.NewChatGpt(chatgpt.NewClient(chatgpt.NewCredentials(token))) 45 | 46 | replaceText := "@" + sender.Self.NickName 47 | requestText := strings.TrimSpace(strings.ReplaceAll(msg.Content, replaceText, "")) 48 | if requestText == "" { 49 | return nil 50 | } 51 | //reply, err := gtp.Completions(requestText) 52 | reply, err := c.SendMessage(requestText) 53 | if err != nil { 54 | log.Printf("gtp request error: %v \n", err) 55 | _, err = msg.ReplyText("机器人神了,我一会发现了就去修。") 56 | if err != nil { 57 | log.Printf("response group error: %v \n", err) 58 | } 59 | return err 60 | } 61 | if reply == "" { 62 | return nil 63 | } 64 | 65 | // 获取@我的用户 66 | groupSender, err := msg.SenderInGroup() 67 | if err != nil { 68 | log.Printf("get sender in group error :%v \n", err) 69 | return err 70 | } 71 | 72 | // 回复@我的用户 73 | reply = strings.TrimSpace(reply) 74 | reply = strings.Trim(reply, "\n") 75 | atText := "@" + groupSender.NickName + " " + requestText + "\n" + "------------------------------" + "\n" 76 | replyText := atText + reply 77 | _, err = msg.ReplyText(replyText) 78 | if err != nil { 79 | log.Printf("response group error: %v \n", err) 80 | } 81 | return err 82 | } 83 | -------------------------------------------------------------------------------- /gtp/gtp.go: -------------------------------------------------------------------------------- 1 | package gtp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/869413421/wechatbot/config" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | ) 13 | 14 | const BASEURL = "https://api.openai.com/v1/" 15 | 16 | // ChatGPTResponseBody 请求体 17 | type ChatGPTResponseBody struct { 18 | ID string `json:"id"` 19 | Object string `json:"object"` 20 | Created int `json:"created"` 21 | Model string `json:"model"` 22 | Choices []ChoiceItem `json:"choices"` 23 | Usage map[string]interface{} `json:"usage"` 24 | } 25 | 26 | type ChoiceItem struct { 27 | Text string `json:"text"` 28 | Index int `json:"index"` 29 | Logprobs int `json:"logprobs"` 30 | FinishReason string `json:"finish_reason"` 31 | } 32 | 33 | // ChatGPTRequestBody 响应体 34 | type ChatGPTRequestBody struct { 35 | Model string `json:"model"` 36 | Prompt string `json:"prompt"` 37 | MaxTokens int `json:"max_tokens"` 38 | Temperature float32 `json:"temperature"` 39 | TopP int `json:"top_p"` 40 | FrequencyPenalty int `json:"frequency_penalty"` 41 | PresencePenalty int `json:"presence_penalty"` 42 | } 43 | 44 | // Completions gtp文本模型回复 45 | //curl https://api.openai.com/v1/completions 46 | //-H "Content-Type: application/json" 47 | //-H "Authorization: Bearer your chatGPT key" 48 | //-d '{"model": "text-davinci-003", "prompt": "give me good song", "temperature": 0, "max_tokens": 7}' 49 | func Completions(msg string) (string, error) { 50 | requestBody := ChatGPTRequestBody{ 51 | Model: "text-moderation-playground", 52 | Prompt: msg, 53 | MaxTokens: 2048, 54 | Temperature: 0.7, 55 | TopP: 1, 56 | FrequencyPenalty: 0, 57 | PresencePenalty: 0, 58 | } 59 | requestData, err := json.Marshal(requestBody) 60 | 61 | if err != nil { 62 | return "", err 63 | } 64 | log.Printf("request gtp json string : %v", string(requestData)) 65 | req, err := http.NewRequest("POST", BASEURL+"completions", bytes.NewBuffer(requestData)) 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | apiKey := config.LoadConfig().ApiKey 71 | req.Header.Set("Content-Type", "application/json") 72 | req.Header.Set("Authorization", "Bearer "+apiKey) 73 | client := &http.Client{} 74 | response, err := client.Do(req) 75 | if err != nil { 76 | return "", err 77 | } 78 | defer response.Body.Close() 79 | if response.StatusCode != 200 { 80 | return "", errors.New(fmt.Sprintf("gtp api status code not equals 200,code is %d", response.StatusCode)) 81 | } 82 | body, err := ioutil.ReadAll(response.Body) 83 | if err != nil { 84 | return "", err 85 | } 86 | 87 | gptResponseBody := &ChatGPTResponseBody{} 88 | log.Println(string(body)) 89 | err = json.Unmarshal(body, gptResponseBody) 90 | if err != nil { 91 | return "", err 92 | } 93 | 94 | var reply string 95 | if len(gptResponseBody.Choices) > 0 { 96 | reply = gptResponseBody.Choices[0].Text 97 | } 98 | log.Printf("gpt response text: %s \n", reply) 99 | return reply, nil 100 | } 101 | --------------------------------------------------------------------------------