diff --git a/.gitignore b/.gitignore
index f7dea7e..f07d35b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,8 +6,6 @@
*.dylib
.idea/
.vscode/
-wechatbot
-storage.json
# Test binary, built with `go test -c`
*.test
@@ -16,5 +14,6 @@ storage.json
*.out
# Dependency directories (remove the comment below to include it)
-# vendor/
-/config.json
+config.json
+storage.json
+vendor
diff --git a/README.md b/README.md
index 840d6e7..b0df538 100644
--- a/README.md
+++ b/README.md
@@ -1,57 +1,48 @@
-# wechatbot
+# chatgpt_wechat_robot
+个人微信接入ChatGPT,实现和GPT机器人互动聊天。支持私聊回复和群聊艾特回复。
-> 本项目是 fork 他人的项目来进行学习和使用,请勿商用,可以下载下来做自定义的功能
-> 最近ChatGPT异常火爆,本项目可以将个人微信化身GPT机器人,
-> 项目基于[openwechat](https://github.com/eatmoreapple/openwechat) 开发。
-
-> `友链:`[chatgpt-dingtalk](https://github.com/eryajf/chatgpt-dingtalk) 本项目可以将GPT机器人集成到钉钉群聊中。
-
-
-### 目前实现了以下功能
+### 实现功能
* GPT机器人模型热度可配置
* 提问增加上下文
-* 指令清空上下文(指令:根据配置)
-* 机器人群聊@回复
+* 指令清空上下文
* 机器人私聊回复
+* 机器人群聊@回复
* 私聊回复前缀设置
* 好友添加自动通过可配置
-* ~~增加每天工作的起始时间和结束时间,只有在该时间段才会对外提供 chatgpt 服务~~
-* ~~增加 vip 用户在任意时段都可享受 chatgpt 服务,只需要在 \wechatbot\handlers\group_msg_handler.go 中 的 VipUserList 切片中,
-加入具体的 vip 昵称~~
-# 实现机制
-目前机器人有两种实现方式
-* 逆向功能,扒取官网API,通过抓取cookie获取GPT响应信息,`优点:`效果与官网一致,`缺点:`cookie会过期需要不定时更新。
-* 基于openai官网提供的API,`优点`:模型以及各种参数可以自由配置,`缺点:`效果达不到官网智能,且API收费,新账号有18美元免费额度。
+### 实现机制
+基于openai官网提供的API,`优点`:模型以及各种参数可以自由配置,`缺点:`效果达不到官网智能,且API收费,新账号有18美元免费额度。
-> 本项目基于第二种方式实现,模型之间具体差异可以参考[官方文档](https://beta.openai.com/docs/models/overview), 详细[参数示例](https://beta.openai.com/examples) 。
+> 模型之间具体差异可以参考[官方文档](https://beta.openai.com/docs/models/overview), 详细[参数示例](https://beta.openai.com/examples) 。
-# 常见问题
-* 如无法登录 login error: write storage.json: bad file descriptor 删除掉storage.json文件重新登录。
-* 如无法登录 login error: wechat network error: Get "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage": 301 response missing Location header 一般是微信登录权限问题,先确保PC端能否正常登录。
-* 其他无法登录问题,依然尝试删除掉storage.json文件,结束进程(linux一般是kill -9 进程id)之后重启程序,重新扫码登录,(如为docket部署,Supervisord进程管理工具会自动重启程序)。
-* ~~机器人无法正常回复,检查ApiKey能否正常使用,控制台日志中有详细错误信息~~ 新版本会机器人会直接输出,因为被问得好烦了。
-* linux中二维码无法扫描,缩小命令行功能,让二维码像素尽可能清晰。(无法从代码层面解决)
-* 机器人一直答非所问,可能因为上下文累积过多。切换不同问题时,发送指令:启动时配置的`session_clear_token`字段。会清空上下文
+### 常见问题
+> 如无法登录`login error: write storage.json: bad file descriptor`
+删除掉storage.json文件重新登录。
-# 使用前提
+> 如无法登录`login error: wechat network error: Get "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage": 301 response missing Location header`
+一般是微信登录权限问题,先确保PC端能否正常登录。
-> * ~~目前只支持在windows上运行因为需要弹窗扫码登录微信,后续会支持linux~~ 已支持
-> * 有openai账号,并且创建好api_key,注册事项可以参考[此文章](https://juejin.cn/post/7173447848292253704) 。
-> * 应用可以参考这篇文章 [此文章](https://juejin.cn/post/7176813187705077816) 。
-> * 微信必须实名认证。
+> 其他无法登录问题
+尝试删除掉storage.json文件,结束进程(linux一般是kill -9 进程id)之后重启程序,重新扫码登录。
+如果为docket部署,Supervisord进程管理工具会自动重启程序。
-# 注意事项
+> 机器人一直答非所问
+可能因为上下文累积过多。切换不同问题时,发送指令:启动时配置的`session_clear_token`字段。会清空上下文
-> * 项目仅供娱乐,滥用可能有微信封禁的风险,请勿用于商业用途。
-> * 请注意收发敏感信息,本项目不做信息过滤。
+### 使用前提
+* 有openai账号,并且创建好api_key,注册事项可以参考[此文章](https://juejin.cn/post/7173447848292253704) 。
+* 应用可以参考这篇文章 [此文章](https://juejin.cn/post/7176813187705077816) 。
+* 微信必须实名认证。
-# 使用docker运行
+### 注意事项
+* 项目仅供娱乐,滥用可能有微信封禁的风险,请勿用于商业用途。
+* 请注意收发敏感信息,本项目不做信息过滤。
+### docker运行
你可以使用docker快速运行本项目。
-`第一种:基于环境变量运行`
+#### 1. 基于环境变量运行
```sh
# 运行项目,环境变量参考下方配置说明
@@ -73,7 +64,7 @@ $ tail -f -n 50 /app/run.log
运行命令中映射的配置文件参考下边的配置文件说明。
-`第二种:基于配置文件挂载运行`
+#### 2. 基于配置文件挂载运行
```sh
# 复制配置文件,根据自己实际情况,调整配置里的内容
@@ -89,78 +80,39 @@ $ tail -f -n 50 /app/run.log
其中配置文件参考下边的配置文件说明。
-# 快速开始
-`第一种:直接下载二进制(适合对编程不了解的同学)`
-
-> 非技术人员请直接下载release中的[压缩包](https://github.com/869413421/wechatbot/releases) ,请根据自己系统以及架构选择合适的压缩包,下载之后直接解压运行。
-
-下载之后,在本地解压,即可看到可执行程序,与配置文件:
-
-```
-# windows
-1.下载压缩包解压
-2.复制文件中config.dev.json更改为config.json
-3.将config.json中的api_key替换为自己的
-4.双击exe,扫码登录
-
-# linux
-$ tar xf wechatbot-v0.0.2-darwin-arm64.tar.gz
-$ cd wechatbot-v0.0.2-darwin-arm64
-$ cp config.dev.json # 根据情况调整配置文件内容
-$ ./wechatbot # 直接运行
-
-# 如果要守护在后台运行
-$ nohup ./wechatbot &> run.log &
-$ tail -f run.log
-```
-
-`第二种:基于源码运行(适合了解go语言编程的同学)`
+### 源码运行
+适合了解go语言编程的同学
````
# 获取项目
-$ git clone https://github.com/869413421/wechatbot.git
+$ git clone https://github.com/ZYallers/chatgpt_wechat_robot.git
# 进入项目目录
-$ cd wechatbot
+$ cd chatgpt_wechat_robot
# 复制配置文件
-$ copy config.dev.json config.json
+$ cp config.dev.json config.json
# 启动项目
$ go run main.go
````
-# 配置文件说明
+### 配置说明
-````
+```json
{
- "api_key": "your api key",
- "auto_pass": true,
- "session_timeout": 60,
- "max_tokens": 1024,
- "model": "text-davinci-003",
- "temperature": 1,
- "reply_prefix": "来自机器人回复:",
- "session_clear_token": "清空会话"
+ "api_key": "your api key", # openai账号里设置的api_key
+ "auto_pass": true, # 是否自动通过好友添加
+ "session_timeout": 60, # 会话超时时间,默认60秒,单位秒,在会话时间内所有发送给机器人的信息会作为上下文
+ "max_tokens": 1024, # GPT响应字符数,最大2048,默认值512。会影响接口响应速度,字符越大响应越慢
+ "model": "text-davinci-003", # GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
+ "temperature": 1, # GPT热度,0到1,默认0.9,数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
+ "reply_prefix": "来自机器人回复:", # 私聊回复前缀
+ "session_clear_token": "清空会话" # 会话清空口令,默认`下一个问题`
}
+```
-api_key:openai api_key
-auto_pass:是否自动通过好友添加
-session_timeout:会话超时时间,默认60秒,单位秒,在会话时间内所有发送给机器人的信息会作为上下文。
-max_tokens: GPT响应字符数,最大2048,默认值512。max_tokens会影响接口响应速度,字符越大响应越慢。
-model: GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
-temperature: GPT热度,0到1,默认0.9。数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
-reply_prefix: 私聊回复前缀
-session_clear_token: 会话清空口令,默认`下一个问题`
-````
-
-# 使用示例
-### 私聊
-
-
-
-### 群聊@回复
-
-
-
+### 友情提示
+本项目是 fork 他人的项目来进行学习和使用,请勿商用,可以下载下来做自定义的功能。
+项目基于[openwechat](https://github.com/eatmoreapple/openwechat) 开发。
\ No newline at end of file
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index 7a3166f..1708bb1 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -2,9 +2,10 @@ package bootstrap
import (
"fmt"
+ "github.com/eatmoreapple/openwechat"
"github.com/qingconglaixueit/wechatbot/handlers"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
- "github.com/eatmoreapple/openwechat"
+ "os"
)
func Run() {
@@ -14,23 +15,32 @@ func Run() {
// 注册消息处理函数
handler, err := handlers.NewHandler()
if err != nil {
- logger.Danger("register error: %v", err)
+ logger.Danger(fmt.Sprintf("handlers.NewHandler error: %v", err))
return
}
bot.MessageHandler = handler
// 注册登陆二维码回调
- bot.UUIDCallback = handlers.QrCodeCallBack
+ bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
// 创建热存储容器对象
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
// 执行热登录
- err = bot.HotLogin(reloadStorage, true)
+ err = bot.HotLogin(reloadStorage)
if err != nil {
- logger.Warning(fmt.Sprintf("login error: %v ", err))
- return
+ if err := os.Remove("storage.json"); err != nil {
+ logger.Warning(fmt.Sprintf("os.Remove storage.json error: %v", err))
+ return
+ }
+ reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
+ err = bot.HotLogin(reloadStorage)
+ if err != nil {
+ logger.Warning(fmt.Sprintf("bot.HotLogin error: %v", err))
+ return
+ }
}
+
// 阻塞主goroutine, 直到发生异常或者用户主动退出
- bot.Block()
+ _ = bot.Block()
}
diff --git a/config/config.go b/config/config.go
index 25b916a..b323578 100644
--- a/config/config.go
+++ b/config/config.go
@@ -3,12 +3,12 @@ package config
import (
"encoding/json"
"fmt"
- "github.com/qingconglaixueit/wechatbot/pkg/logger"
- "log"
"os"
"strconv"
"sync"
"time"
+
+ "github.com/qingconglaixueit/wechatbot/pkg/logger"
)
// Configuration 项目配置
@@ -44,7 +44,7 @@ func LoadConfig() *Configuration {
MaxTokens: 512,
Model: "text-davinci-003",
Temperature: 0.9,
- SessionClearToken: "下一个问题",
+ SessionClearToken: "下个问题",
}
// 判断配置文件是否存在,存在直接JSON读取
@@ -52,14 +52,14 @@ func LoadConfig() *Configuration {
if err == nil {
f, err := os.Open("config.json")
if err != nil {
- log.Fatalf("open config err: %v", err)
+ logger.Danger(fmt.Sprintf("open config error: %v", err))
return
}
defer f.Close()
encoder := json.NewDecoder(f)
err = encoder.Decode(config)
if err != nil {
- log.Fatalf("decode config err: %v", err)
+ logger.Danger(fmt.Sprintf("decode config error: %v", err))
return
}
}
@@ -81,7 +81,7 @@ func LoadConfig() *Configuration {
if SessionTimeout != "" {
duration, err := time.ParseDuration(SessionTimeout)
if err != nil {
- logger.Danger(fmt.Sprintf("config session timeout err: %v ,get is %v", err, SessionTimeout))
+ logger.Danger(fmt.Sprintf("config session timeout error: %v, get is %v", err, SessionTimeout))
return
}
config.SessionTimeout = duration
@@ -92,7 +92,7 @@ func LoadConfig() *Configuration {
if MaxTokens != "" {
max, err := strconv.Atoi(MaxTokens)
if err != nil {
- logger.Danger(fmt.Sprintf("config MaxTokens err: %v ,get is %v", err, MaxTokens))
+ logger.Danger(fmt.Sprintf("config max tokens error: %v ,get is %v", err, MaxTokens))
return
}
config.MaxTokens = uint(max)
@@ -100,7 +100,7 @@ func LoadConfig() *Configuration {
if Temperature != "" {
temp, err := strconv.ParseFloat(Temperature, 64)
if err != nil {
- logger.Danger(fmt.Sprintf("config Temperature err: %v ,get is %v", err, Temperature))
+ logger.Danger(fmt.Sprintf("config temperature error: %v, get is %v", err, Temperature))
return
}
config.Temperature = temp
@@ -114,7 +114,7 @@ func LoadConfig() *Configuration {
})
if config.ApiKey == "" {
- logger.Danger("config err: api key required")
+ logger.Danger("config error: api key required")
}
return config
diff --git a/gpt/gpt.go b/gpt/gpt.go
index ec6c39b..02ab74b 100644
--- a/gpt/gpt.go
+++ b/gpt/gpt.go
@@ -5,15 +5,13 @@ import (
"encoding/json"
"errors"
"fmt"
- "github.com/qingconglaixueit/wechatbot/config"
- "github.com/qingconglaixueit/wechatbot/pkg/logger"
"io/ioutil"
"log"
"net/http"
"time"
-)
-const BASEURL = "https://api.openai.com/v1/"
+ "github.com/qingconglaixueit/wechatbot/config"
+)
// ChatGPTResponseBody 请求体
type ChatGPTResponseBody struct {
@@ -23,6 +21,12 @@ type ChatGPTResponseBody struct {
Model string `json:"model"`
Choices []ChoiceItem `json:"choices"`
Usage map[string]interface{} `json:"usage"`
+ Error struct {
+ Message string `json:"message"`
+ Type string `json:"type"`
+ Param interface{} `json:"param"`
+ Code interface{} `json:"code"`
+ } `json:"error"`
}
type ChoiceItem struct {
@@ -49,7 +53,37 @@ type ChatGPTRequestBody struct {
//-H "Authorization: Bearer your chatGPT key"
//-d '{"model": "text-davinci-003", "prompt": "give me good song", "temperature": 0, "max_tokens": 7}'
func Completions(msg string) (string, error) {
+ var gptResponseBody *ChatGPTResponseBody
+ var resErr error
+ for retry := 1; retry <= 3; retry++ {
+ if retry > 1 {
+ time.Sleep(time.Duration(retry-1) * 100 * time.Millisecond)
+ }
+ gptResponseBody, resErr = httpRequestCompletions(msg, retry)
+ if resErr != nil {
+ log.Printf("gpt request(%d) error: %v\n", retry, resErr)
+ continue
+ }
+ if gptResponseBody.Error.Message == "" {
+ break
+ }
+ }
+ if resErr != nil {
+ return "", resErr
+ }
+ var reply string
+ if gptResponseBody != nil && len(gptResponseBody.Choices) > 0 {
+ reply = gptResponseBody.Choices[0].Text
+ }
+ return reply, nil
+}
+
+func httpRequestCompletions(msg string, runtimes int) (*ChatGPTResponseBody, error) {
cfg := config.LoadConfig()
+ if cfg.ApiKey == "" {
+ return nil, errors.New("api key required")
+ }
+
requestBody := ChatGPTRequestBody{
Model: cfg.Model,
Prompt: msg,
@@ -60,46 +94,37 @@ func Completions(msg string) (string, error) {
PresencePenalty: 0,
}
requestData, err := json.Marshal(requestBody)
-
if err != nil {
- return "", err
+ return nil, fmt.Errorf("json.Marshal requestBody error: %v", err)
}
- logger.Info(fmt.Sprintf("request gpt json string : %v", string(requestData)))
- req, err := http.NewRequest("POST", BASEURL+"completions", bytes.NewBuffer(requestData))
+
+ log.Printf("gpt request(%d) json: %s\n", runtimes, string(requestData))
+
+ req, err := http.NewRequest(http.MethodPost, "https://api.openai.com/v1/completions", bytes.NewBuffer(requestData))
if err != nil {
- return "", err
+ return nil, fmt.Errorf("http.NewRequest error: %v", err)
}
- apiKey := config.LoadConfig().ApiKey
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", "Bearer "+apiKey)
- client := &http.Client{Timeout: 30 * time.Second}
+ req.Header.Set("Authorization", "Bearer "+cfg.ApiKey)
+ client := &http.Client{Timeout: 15 * time.Second}
response, err := client.Do(req)
if err != nil {
- return "", err
+ return nil, fmt.Errorf("client.Do error: %v", err)
}
defer response.Body.Close()
- if response.StatusCode != 200 {
- body, _ := ioutil.ReadAll(response.Body)
- return "", errors.New(fmt.Sprintf("请求GTP出错了,gpt api status code not equals 200,code is %d ,details: %v ", response.StatusCode, string(body)))
- }
+
body, err := ioutil.ReadAll(response.Body)
if err != nil {
- return "", err
+ return nil, fmt.Errorf("ioutil.ReadAll error: %v", err)
}
- logger.Info(fmt.Sprintf("response gpt json string : %v", string(body)))
+
+ log.Printf("gpt response(%d) json: %s\n", runtimes, string(body))
gptResponseBody := &ChatGPTResponseBody{}
- log.Println(string(body))
err = json.Unmarshal(body, gptResponseBody)
if err != nil {
- return "", err
- }
-
- var reply string
- if len(gptResponseBody.Choices) > 0 {
- reply = gptResponseBody.Choices[0].Text
+ return nil, fmt.Errorf("json.Marshal responseBody error: %v", err)
}
- logger.Info(fmt.Sprintf("gpt response text: %s ", reply))
- return reply, nil
+ return gptResponseBody, nil
}
diff --git a/handlers/group_msg_handler.go b/handlers/group_msg_handler.go
index 4fb5fde..c9aafe0 100644
--- a/handlers/group_msg_handler.go
+++ b/handlers/group_msg_handler.go
@@ -1,13 +1,16 @@
package handlers
import (
- "errors"
"fmt"
+ "log"
+ "math/rand"
+ "strings"
+ "time"
+
"github.com/eatmoreapple/openwechat"
"github.com/qingconglaixueit/wechatbot/gpt"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/qingconglaixueit/wechatbot/service"
- "strings"
)
var _ MessageHandlerInterface = (*GroupMessageHandler)(nil)
@@ -32,14 +35,14 @@ func GroupMessageContextHandler() func(ctx *openwechat.MessageContext) {
// 获取用户消息处理器
handler, err := NewGroupMessageHandler(msg)
if err != nil {
- logger.Warning(fmt.Sprintf("init group message handler error: %s", err))
+ logger.Warning(fmt.Sprintf("init group message handler error: %v", err))
return
}
// 处理用户消息
err = handler.handle()
if err != nil {
- logger.Warning(fmt.Sprintf("handle group message error: %s", err))
+ logger.Warning(fmt.Sprintf("handle group message error: %v", err))
}
}
}
@@ -78,7 +81,16 @@ func (g *GroupMessageHandler) handle() error {
// ReplyText 发息送文本消到群
func (g *GroupMessageHandler) ReplyText() error {
- logger.Info(fmt.Sprintf("Received Group %v Text Msg : %v", g.group.NickName, g.msg.Content))
+ if time.Now().Unix()-g.msg.CreateTime > 60 {
+ return nil
+ }
+
+ maxInt := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(5)
+ time.Sleep(time.Duration(maxInt+1) * time.Second)
+
+ log.Printf("Received Group[%v], Content[%v], CreateTime[%v]", g.group.NickName, g.msg.Content,
+ time.Unix(g.msg.CreateTime, 0).Format("2006/01/02 15:04:05"))
+
var (
err error
reply string
@@ -92,18 +104,20 @@ func (g *GroupMessageHandler) ReplyText() error {
// 2.获取请求的文本,如果为空字符串不处理
requestText := g.getRequestText()
if requestText == "" {
- logger.Info("user message is null")
+ log.Println("group message is empty")
return nil
}
// 3.请求GPT获取回复
reply, err = gpt.Completions(requestText)
if err != nil {
- // 2.1 将GPT请求失败信息输出给用户,省得整天来问又不知道日志在哪里。
- errMsg := fmt.Sprintf("gpt request error: %v", err)
- _, err = g.msg.ReplyText(errMsg)
+ text := err.Error()
+ if strings.Contains(err.Error(), "context deadline exceeded") {
+ text = deadlineExceededText
+ }
+ _, err = g.msg.ReplyText(text)
if err != nil {
- return errors.New(fmt.Sprintf("response group error: %v ", err))
+ return fmt.Errorf("reply group error: %v", err)
}
return err
}
@@ -112,7 +126,7 @@ func (g *GroupMessageHandler) ReplyText() error {
g.service.SetUserSessionContext(requestText, reply)
_, err = g.msg.ReplyText(g.buildReplyText(reply))
if err != nil {
- return errors.New(fmt.Sprintf("response user error: %v ", err))
+ return fmt.Errorf("reply group error: %v ", err)
}
// 5.返回错误信息
@@ -132,7 +146,7 @@ func (g *GroupMessageHandler) getRequestText() string {
return ""
}
- // 3.获取上下文,拼接在一起,如果字符长度超出4000,截取为4000。(GPT按字符长度算),达芬奇3最大为4068,也许后续为了适应要动态进行判断。
+ // 3.获取上下文拼接在一起,如果字符长度超出4000截取为4000(GPT按字符长度算),达芬奇3最大为4068,也许后续为了适应要动态进行判断
sessionText := g.service.GetUserSessionContext()
if sessionText != "" {
requestText = sessionText + "\n" + requestText
@@ -146,7 +160,7 @@ func (g *GroupMessageHandler) getRequestText() string {
runeRequestText := []rune(requestText)
lastChar := string(runeRequestText[len(runeRequestText)-1:])
if strings.Index(punctuation, lastChar) < 0 {
- requestText = requestText + "?" // 判断最后字符是否加了标点,没有的话加上句号,避免openai自动补齐引起混乱。
+ requestText = requestText + "?" // 判断最后字符是否加了标点,没有的话加上句号,避免openai自动补齐引起混乱
}
// 5.返回请求文本
@@ -164,13 +178,14 @@ func (g *GroupMessageHandler) buildReplyText(reply string) string {
}
reply = strings.TrimSpace(reply)
if reply == "" {
- return atText + " 请求得不到任何有意义的回复,请具体提出问题。"
+ return atText + " " + deadlineExceededText
}
- // 2.拼接回复,@我的用户,问题,回复
+ // 2.拼接回复, @我的用户, 问题, 回复
replaceText := "@" + g.self.NickName
question := strings.TrimSpace(strings.ReplaceAll(g.msg.Content, replaceText, ""))
- reply = atText + "\n" + question + "\n --------------------------------\n" + reply
+ hr := strings.Repeat("-", 36)
+ reply = atText + "\n" + question + "\n" + hr + "\n" + reply
reply = strings.Trim(reply, "\n")
// 3.返回回复的内容
diff --git a/handlers/handler.go b/handlers/handler.go
index 7a608df..66b6647 100644
--- a/handlers/handler.go
+++ b/handlers/handler.go
@@ -2,10 +2,10 @@ package handlers
import (
"fmt"
- "github.com/qingconglaixueit/wechatbot/config"
- "github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/eatmoreapple/openwechat"
"github.com/patrickmn/go-cache"
+ "github.com/qingconglaixueit/wechatbot/config"
+ "github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/skip2/go-qrcode"
"log"
"runtime"
@@ -13,6 +13,8 @@ import (
"time"
)
+const deadlineExceededText = "请求GPT服务器超时[裂开]得不到回复,请重新发送问题[旺柴]"
+
var c = cache.New(config.LoadConfig().SessionTimeout, time.Minute*5)
// MessageHandlerInterface 消息处理接口
@@ -23,13 +25,11 @@ type MessageHandlerInterface interface {
// QrCodeCallBack 登录扫码回调,
func QrCodeCallBack(uuid string) {
- if runtime.GOOS == "windows" {
- // 运行在Windows系统上
+ if runtime.GOOS == "windows" { // 运行在Windows系统上
openwechat.PrintlnQrcodeUrl(uuid)
} else {
- log.Println("login in linux")
url := "https://login.weixin.qq.com/l/" + uuid
- log.Printf("如果二维码无法扫描,请缩小控制台尺寸,或更换命令行工具,缩小二维码像素")
+ log.Println("如果二维码无法扫描,请缩小控制台尺寸,或更换命令行工具,缩小二维码像素。")
q, _ := qrcode.New(url, qrcode.High)
fmt.Println(q.ToSmallString(true))
}
diff --git a/handlers/token_msg_handler.go b/handlers/token_msg_handler.go
index 13393c3..651c6c0 100644
--- a/handlers/token_msg_handler.go
+++ b/handlers/token_msg_handler.go
@@ -2,9 +2,11 @@ package handlers
import (
"fmt"
+ "github.com/eatmoreapple/openwechat"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/qingconglaixueit/wechatbot/service"
- "github.com/eatmoreapple/openwechat"
+ "math/rand"
+ "time"
)
var _ MessageHandlerInterface = (*TokenMessageHandler)(nil)
@@ -63,17 +65,19 @@ func (t *TokenMessageHandler) handle() error {
// ReplyText 回复清空口令
func (t *TokenMessageHandler) ReplyText() error {
- logger.Info("user clear token")
+ maxInt := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(5)
+ time.Sleep(time.Duration(maxInt+1) * time.Second)
+
t.service.ClearUserSessionContext()
var err error
if t.msg.IsComeFromGroup() {
if !t.msg.IsAt() {
return err
}
- atText := "@" + t.sender.NickName + "上下文已经清空,请问下一个问题。"
+ atText := "@" + t.sender.NickName + "上下文已经清空,请问下个问题"
_, err = t.msg.ReplyText(atText)
} else {
- _, err = t.msg.ReplyText("上下文已经清空,请问下一个问题。")
+ _, err = t.msg.ReplyText("上下文已经清空,请问下个问题")
}
return err
}
diff --git a/handlers/user_msg_handler.go b/handlers/user_msg_handler.go
index 8558526..1e2a63e 100644
--- a/handlers/user_msg_handler.go
+++ b/handlers/user_msg_handler.go
@@ -1,14 +1,17 @@
package handlers
import (
- "errors"
"fmt"
+ "log"
+ "math/rand"
+ "strings"
+ "time"
+
"github.com/eatmoreapple/openwechat"
"github.com/qingconglaixueit/wechatbot/config"
"github.com/qingconglaixueit/wechatbot/gpt"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/qingconglaixueit/wechatbot/service"
- "strings"
)
var _ MessageHandlerInterface = (*UserMessageHandler)(nil)
@@ -65,7 +68,16 @@ func (h *UserMessageHandler) handle() error {
// ReplyText 发送文本消息到群
func (h *UserMessageHandler) ReplyText() error {
- logger.Info(fmt.Sprintf("Received User %v Text Msg : %v", h.sender.NickName, h.msg.Content))
+ if time.Now().Unix()-h.msg.CreateTime > 60 {
+ return nil
+ }
+
+ maxInt := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(5)
+ time.Sleep(time.Duration(maxInt+1) * time.Second)
+
+ log.Printf("Received User[%v], Content[%v], CreateTime[%v]", h.sender.NickName, h.msg.Content,
+ time.Unix(h.msg.CreateTime, 0).Format("2006/01/02 15:04:05"))
+
var (
reply string
err error
@@ -73,18 +85,20 @@ func (h *UserMessageHandler) ReplyText() error {
// 1.获取上下文,如果字符串为空不处理
requestText := h.getRequestText()
if requestText == "" {
- logger.Info("user message is null")
+ log.Println("user message is empty")
return nil
}
- logger.Info(fmt.Sprintf("h.sender.NickName == %+v", h.sender.NickName))
+
// 2.向GPT发起请求,如果回复文本等于空,不回复
reply, err = gpt.Completions(h.getRequestText())
if err != nil {
- // 2.1 将GPT请求失败信息输出给用户,省得整天来问又不知道日志在哪里。
- errMsg := fmt.Sprintf("gpt request error: %v", err)
- _, err = h.msg.ReplyText(errMsg)
+ text := err.Error()
+ if strings.Contains(err.Error(), "context deadline exceeded") {
+ text = deadlineExceededText
+ }
+ _, err = h.msg.ReplyText(text)
if err != nil {
- return errors.New(fmt.Sprintf("response user error: %v ", err))
+ return fmt.Errorf("reply user error: %v ", err)
}
return err
}
@@ -93,7 +107,7 @@ func (h *UserMessageHandler) ReplyText() error {
h.service.SetUserSessionContext(requestText, reply)
_, err = h.msg.ReplyText(buildUserReply(reply))
if err != nil {
- return errors.New(fmt.Sprintf("response user error: %v ", err))
+ return fmt.Errorf("reply user error: %v ", err)
}
// 3.返回错误
@@ -135,11 +149,9 @@ func buildUserReply(reply string) string {
trimText := textSplit[0]
reply = strings.Trim(reply, trimText)
}
- reply = strings.TrimSpace(reply)
-
reply = strings.TrimSpace(reply)
if reply == "" {
- return "请求得不到任何有意义的回复,请具体提出问题。"
+ return deadlineExceededText
}
// 2.如果用户有配置前缀,加上前缀
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index bdc0b4b..8f65033 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -24,7 +24,7 @@ func Info(args ...interface{}) {
// Danger 错误 为什么不命名为 error?避免和 error 类型重名
func Danger(args ...interface{}) {
Logger.SetPrefix("[ERROR]")
- Logger.Fatal(args...)
+ Logger.Println(args...)
}
// Warning 警告
@@ -35,6 +35,6 @@ func Warning(args ...interface{}) {
// DeBug debug
func DeBug(args ...interface{}) {
- Logger.SetPrefix("[DeBug]")
+ Logger.SetPrefix("[DEBUG]")
Logger.Println(args...)
}
diff --git a/rule/rule.go b/rule/rule.go
index f9f3608..1b83a85 100644
--- a/rule/rule.go
+++ b/rule/rule.go
@@ -22,13 +22,14 @@ func (r *Rule) SetWork(work bool) {
isWork = work
return
}
+
func (r *Rule) GetWork() bool {
lock.Lock()
defer lock.Unlock()
return isWork
}
-// 判断时间在今天的早上 9点到 晚上 9 点区间内
+// 判断时间在今天的早上9点到晚上9点区间内
func (r *Rule) IsWorkTime(s int, e int) bool {
if s < 0 || s > 24 {
s = STARTTIME