├── config.dev.json ├── go.mod ├── main.go ├── go.sum ├── .gitignore ├── README.md ├── bootstrap └── bootstrap.go ├── config └── config.go ├── handlers ├── handler.go ├── user_msg_handler.go └── group_msg_handler.go └── gtp └── gtp.go /config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "your api key", 3 | "auto_pass": true 4 | } 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/869413421/wechatbot 2 | 3 | go 1.16 4 | 5 | require github.com/eatmoreapple/openwechat v1.2.1 6 | -------------------------------------------------------------------------------- /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.sum: -------------------------------------------------------------------------------- 1 | github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU= 2 | github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8= 3 | -------------------------------------------------------------------------------- /.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 | # wechatbot 2 | 最近chatGPT异常火爆,想到将其接入到个人微信是件比较有趣的事,所以有了这个项目。项目基于[openwechat](https://github.com/eatmoreapple/openwechat) 3 | 开发 4 | ###目前实现了以下功能 5 | + 群聊@回复 6 | + 私聊回复 7 | + 自动通过回复 8 | 9 | # 注册openai 10 | chatGPT注册可以参考[这里](https://juejin.cn/post/7173447848292253704) 11 | 12 | # 安装使用 13 | ```` 14 | # 获取项目 15 | git clone https://github.com/869413421/wechatbot.git 16 | 17 | # 进入项目目录 18 | cd wechatbot 19 | 20 | # 复制配置文件 21 | copy config.dev.json config.json 22 | 23 | # 启动项目 24 | go run main.go 25 | 26 | 启动前需替换config中的api_key 27 | -------------------------------------------------------------------------------- /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 | // 自动通过好友 15 | AutoPass bool `json:"auto_pass"` 16 | } 17 | 18 | var config *Configuration 19 | var once sync.Once 20 | 21 | // LoadConfig 加载配置 22 | func LoadConfig() *Configuration { 23 | once.Do(func() { 24 | // 从文件中读取 25 | config = &Configuration{} 26 | f, err := os.Open("config.json") 27 | if err != nil { 28 | log.Fatalf("open config err: %v", err) 29 | return 30 | } 31 | defer f.Close() 32 | encoder := json.NewDecoder(f) 33 | err = encoder.Decode(config) 34 | if err != nil { 35 | log.Fatalf("decode config err: %v", err) 36 | return 37 | } 38 | 39 | // 如果环境变量有配置,读取环境变量 40 | ApiKey := os.Getenv("ApiKey") 41 | AutoPass := os.Getenv("AutoPass") 42 | if ApiKey != "" { 43 | config.ApiKey = ApiKey 44 | } 45 | if AutoPass == "true" { 46 | config.AutoPass = true 47 | } 48 | }) 49 | return config 50 | } 51 | -------------------------------------------------------------------------------- /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/gtp" 5 | "github.com/eatmoreapple/openwechat" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | var _ MessageHandlerInterface = (*UserMessageHandler)(nil) 11 | 12 | // UserMessageHandler 私聊消息处理 13 | type UserMessageHandler struct { 14 | } 15 | 16 | // handle 处理消息 17 | func (g *UserMessageHandler) handle(msg *openwechat.Message) error { 18 | if msg.IsText() { 19 | return g.ReplyText(msg) 20 | } 21 | return nil 22 | } 23 | 24 | // NewUserMessageHandler 创建私聊处理器 25 | func NewUserMessageHandler() MessageHandlerInterface { 26 | return &UserMessageHandler{} 27 | } 28 | 29 | // ReplyText 发送文本消息到群 30 | func (g *UserMessageHandler) ReplyText(msg *openwechat.Message) error { 31 | // 接收私聊消息 32 | sender, err := msg.Sender() 33 | log.Printf("Received User %v Text Msg : %v", sender.NickName, msg.Content) 34 | 35 | // 向GPT发起请求 36 | requestText := strings.TrimSpace(msg.Content) 37 | requestText = strings.Trim(msg.Content, "\n") 38 | reply, err := gtp.Completions(requestText) 39 | if err != nil { 40 | log.Printf("gtp request error: %v \n", err) 41 | msg.ReplyText("机器人神了,我一会发现了就去修。") 42 | return err 43 | } 44 | if reply == "" { 45 | return nil 46 | } 47 | 48 | // 回复用户 49 | reply = strings.TrimSpace(reply) 50 | reply = strings.Trim(reply, "\n") 51 | _, err = msg.ReplyText(reply) 52 | if err != nil { 53 | log.Printf("response user error: %v \n", err) 54 | } 55 | return err 56 | } 57 | -------------------------------------------------------------------------------- /handlers/group_msg_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/869413421/wechatbot/gtp" 5 | "github.com/eatmoreapple/openwechat" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | var _ MessageHandlerInterface = (*GroupMessageHandler)(nil) 11 | 12 | // GroupMessageHandler 群消息处理 13 | type GroupMessageHandler struct { 14 | } 15 | 16 | // handle 处理消息 17 | func (g *GroupMessageHandler) handle(msg *openwechat.Message) error { 18 | if msg.IsText() { 19 | return g.ReplyText(msg) 20 | } 21 | return nil 22 | } 23 | 24 | // NewGroupMessageHandler 创建群消息处理器 25 | func NewGroupMessageHandler() MessageHandlerInterface { 26 | return &GroupMessageHandler{} 27 | } 28 | 29 | // ReplyText 发送文本消息到群 30 | func (g *GroupMessageHandler) ReplyText(msg *openwechat.Message) error { 31 | // 接收群消息 32 | sender, err := msg.Sender() 33 | group := openwechat.Group{sender} 34 | log.Printf("Received Group %v Text Msg : %v", group.NickName, msg.Content) 35 | 36 | // 不是@的不处理 37 | if !msg.IsAt() { 38 | return nil 39 | } 40 | 41 | // 替换掉@文本,然后向GPT发起请求 42 | replaceText := "@" + sender.Self.NickName 43 | requestText := strings.TrimSpace(strings.ReplaceAll(msg.Content, replaceText, "")) 44 | reply, err := gtp.Completions(requestText) 45 | if err != nil { 46 | log.Printf("gtp request error: %v \n", err) 47 | msg.ReplyText("机器人神了,我一会发现了就去修。") 48 | return err 49 | } 50 | if reply == "" { 51 | return nil 52 | } 53 | 54 | // 获取@我的用户 55 | groupSender, err := msg.SenderInGroup() 56 | if err != nil { 57 | log.Printf("get sender in group error :%v \n", err) 58 | return err 59 | } 60 | 61 | // 回复@我的用户 62 | reply = strings.TrimSpace(reply) 63 | reply = strings.Trim(reply, "\n") 64 | atText := "@" + groupSender.NickName 65 | replyText := atText + reply 66 | _, err = msg.ReplyText(replyText) 67 | if err != nil { 68 | log.Printf("response group error: %v \n", err) 69 | } 70 | return err 71 | } 72 | -------------------------------------------------------------------------------- /gtp/gtp.go: -------------------------------------------------------------------------------- 1 | package gtp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/869413421/wechatbot/config" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | const BASEURL = "https://api.openai.com/v1/" 13 | 14 | // ChatGPTResponseBody 请求体 15 | type ChatGPTResponseBody struct { 16 | ID string `json:"id"` 17 | Object string `json:"object"` 18 | Created int `json:"created"` 19 | Model string `json:"model"` 20 | Choices []map[string]interface{} `json:"choices"` 21 | Usage map[string]interface{} `json:"usage"` 22 | } 23 | 24 | type ChoiceItem struct { 25 | } 26 | 27 | // ChatGPTRequestBody 响应体 28 | type ChatGPTRequestBody struct { 29 | Model string `json:"model"` 30 | Prompt string `json:"prompt"` 31 | MaxTokens int `json:"max_tokens"` 32 | Temperature float32 `json:"temperature"` 33 | TopP int `json:"top_p"` 34 | FrequencyPenalty int `json:"frequency_penalty"` 35 | PresencePenalty int `json:"presence_penalty"` 36 | } 37 | 38 | // Completions gtp文本模型回复 39 | //curl https://api.openai.com/v1/completions 40 | //-H "Content-Type: application/json" 41 | //-H "Authorization: Bearer your chatGPT key" 42 | //-d '{"model": "text-davinci-003", "prompt": "give me good song", "temperature": 0, "max_tokens": 7}' 43 | func Completions(msg string) (string, error) { 44 | requestBody := ChatGPTRequestBody{ 45 | Model: "text-davinci-003", 46 | Prompt: msg, 47 | MaxTokens: 2048, 48 | Temperature: 0.7, 49 | TopP: 1, 50 | FrequencyPenalty: 0, 51 | PresencePenalty: 0, 52 | } 53 | requestData, err := json.Marshal(requestBody) 54 | 55 | if err != nil { 56 | return "", err 57 | } 58 | log.Printf("request gtp json string : %v", string(requestData)) 59 | req, err := http.NewRequest("POST", BASEURL+"completions", bytes.NewBuffer(requestData)) 60 | if err != nil { 61 | return "", err 62 | } 63 | 64 | apiKey := config.LoadConfig().ApiKey 65 | req.Header.Set("Content-Type", "application/json") 66 | req.Header.Set("Authorization", "Bearer "+apiKey) 67 | client := &http.Client{} 68 | response, err := client.Do(req) 69 | if err != nil { 70 | return "", err 71 | } 72 | defer response.Body.Close() 73 | 74 | body, err := ioutil.ReadAll(response.Body) 75 | if err != nil { 76 | return "", err 77 | } 78 | 79 | gptResponseBody := &ChatGPTResponseBody{} 80 | log.Println(string(body)) 81 | err = json.Unmarshal(body, gptResponseBody) 82 | if err != nil { 83 | return "", err 84 | } 85 | var reply string 86 | if len(gptResponseBody.Choices) > 0 { 87 | for _, v := range gptResponseBody.Choices { 88 | reply = v["text"].(string) 89 | break 90 | } 91 | } 92 | log.Printf("gpt response text: %s \n", reply) 93 | return reply, nil 94 | } 95 | --------------------------------------------------------------------------------