From 3da8b327d064c3c98b956e852ab989ecff033bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AF=BB=E6=AC=A2?= Date: Fri, 12 Apr 2024 10:48:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?chore(go-wxhelper):=20add=20AI=20command=20?= =?UTF-8?q?functionality=20=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add AI command functionality to handle AI commands in the WeChat plugin. Includes options to enable or disable AI features. --- plugin/plugins/command.go | 2 ++ plugin/plugins/command/ai.go | 65 ++++++++++++++++++++++++++++++++++ plugin/plugins/command/help.go | 7 ++++ readme.md | 4 +-- tasks/friends/friends.go | 3 ++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 plugin/plugins/command/ai.go diff --git a/plugin/plugins/command.go b/plugin/plugins/command.go index 408efc3..d3b1082 100644 --- a/plugin/plugins/command.go +++ b/plugin/plugins/command.go @@ -42,6 +42,8 @@ func Command(m *plugin.MessageContext) { command.LeiGodCmd(m.FromUser, msgArray[1], msgArray[2:]...) case "/肯德基", "/kfc": command.KfcCrazyThursdayCmd(m.FromUser) + case "/ai": + command.AiCmd(m.FromUser, m.GroupUser, msgArray[1]) default: utils.SendMessage(m.FromUser, m.GroupUser, "指令错误", 0) } diff --git a/plugin/plugins/command/ai.go b/plugin/plugins/command/ai.go new file mode 100644 index 0000000..db1aa92 --- /dev/null +++ b/plugin/plugins/command/ai.go @@ -0,0 +1,65 @@ +package command + +import ( + "fmt" + "go-wechat/client" + "go-wechat/entity" + "go-wechat/utils" + "log" + "strings" +) + +// AiCmd +// @description: AI指令 +// @param userId +// @param groupUserId +// @param cmd +func AiCmd(userId, groupUserId, cmd string) { + // 判断发信人是不是群主 + can := false + if strings.Contains(userId, "@chatroom") { + // 判断是不是群主 + err := client.MySQL.Model(&entity.GroupUser{}). + Where("group_id = ?", userId). + Where("wxid = ?", groupUserId). + Pluck("is_admin", &can).Error + if err != nil { + log.Printf("查询群主失败: %v", err) + return + } + } + if !can { + utils.SendMessage(userId, groupUserId, "您不是群主,无法使用指令", 0) + return + } + + var err error + replyMsg := "操作成功" + + switch cmd { + case "enable", "启用", "打开": + err = setAiEnable(userId, true) + case "disable", "停用", "禁用", "关闭": + err = setAiEnable(userId, false) + default: + replyMsg = "指令错误" + } + if err != nil { + log.Printf("AI指令执行失败: %v", err) + replyMsg = fmt.Sprintf("指令执行错误: %v", err) + } + utils.SendMessage(userId, groupUserId, replyMsg, 0) +} + +// setAiEnable +// @description: 设置AI启用状态 +// @param userId +// @param enable +// @return err +func setAiEnable(userId string, enable bool) (err error) { + // 更新 + err = client.MySQL.Model(&entity.Friend{}). + Where("wxid = ?", userId). + Update("enable_ai", enable).Error + return +} diff --git a/plugin/plugins/command/help.go b/plugin/plugins/command/help.go index c469c36..4ef4448 100644 --- a/plugin/plugins/command/help.go +++ b/plugin/plugins/command/help.go @@ -23,6 +23,13 @@ option: 指令选项,可选值: #2. 肯德基疯狂星期四文案 /kfc、/肯德基 + +#3. AI助手 +/ai option +option: 指令选项,可选值: + 启用: '启用'、'打开'、'enable' + 停用: '停用'、'禁用'、'关闭'、'disable' + ` utils.SendMessage(m.FromUser, m.GroupUser, str, 0) diff --git a/readme.md b/readme.md index 7ae30c5..467ac9f 100644 --- a/readme.md +++ b/readme.md @@ -70,9 +70,6 @@ services: image: mysql:8 container_name: gw-db restart: unless-stopped - depends_on: - wechat: - condition: service_healthy environment: - MYSQL_ROOT_PASSWORD=wechat - MYSQL_USER=wechat @@ -88,6 +85,7 @@ services: restart: unless-stopped depends_on: - mysql + - wechat volumes: # 配置文件请参阅项目根目录的config.yaml文件 - ./config/config.yaml:/app/config.yaml diff --git a/tasks/friends/friends.go b/tasks/friends/friends.go index 61cc4fc..6ab5b11 100644 --- a/tasks/friends/friends.go +++ b/tasks/friends/friends.go @@ -8,6 +8,7 @@ import ( "go-wechat/config" "go-wechat/entity" "go-wechat/model" + "go-wechat/utils" "gorm.io/gorm" "log" "slices" @@ -72,6 +73,8 @@ func Sync() { log.Printf("新增好友失败: %s", err.Error()) continue } + // 发送一条新消息 + utils.SendMessage(friend.Wxid, "", "大家好,我是一个AI机器人,可以直接@我询问你想问的问题。该功能默认未启用,请群主艾特我并发送 /ai enable 指令启用", 0) } else { pm := map[string]any{ "nickname": friend.Nickname, From b3ed0fcc6fb62f2c93f507d23254faeec3560656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AF=BB=E6=AC=A2?= Date: Fri, 12 Apr 2024 11:37:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?:new:=20=E6=96=B0=E5=A2=9E=E7=BE=A4?= =?UTF-8?q?=E8=81=8A=E5=AF=B9=E8=AF=9D=E8=AE=B0=E5=BD=95=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/friend.go | 23 ++++++++++++ config.yaml | 25 ++++++++----- config/ai.go | 13 ++++--- config/task.go | 7 ++-- entity/friend.go | 1 + router/router.go | 1 + service/friend.go | 9 +++++ service/message.go | 20 ++++++++++ tasks/summary/summary.go | 81 ++++++++++++++++++++++++++++++++++++++++ tasks/tasks.go | 8 ++++ views/index.html | 10 +++++ views/static/js/index.js | 23 ++++++++++++ vo/friend.go | 1 + vo/message.go | 8 ++++ 14 files changed, 211 insertions(+), 19 deletions(-) create mode 100644 tasks/summary/summary.go create mode 100644 vo/message.go diff --git a/app/friend.go b/app/friend.go index d131d7a..e9d28f4 100644 --- a/app/friend.go +++ b/app/friend.go @@ -90,6 +90,29 @@ func ChangeEnableGroupRankStatus(ctx *gin.Context) { ctx.String(http.StatusOK, "操作成功") } +// ChangeEnableSummaryStatus +// @description: 修改是否开启聊天记录总结 +// @param ctx +func ChangeEnableSummaryStatus(ctx *gin.Context) { + var p changeStatusParam + if err := ctx.ShouldBindJSON(&p); err != nil { + ctx.String(http.StatusBadRequest, "参数错误") + return + } + log.Printf("待修改的群Id:%s", p.WxId) + + err := client.MySQL.Model(&entity.Friend{}). + Where("wxid = ?", p.WxId). + Update("`enable_summary`", gorm.Expr(" !`enable_summary`")).Error + if err != nil { + log.Printf("修改开启聊天记录总结失败:%s", err) + ctx.String(http.StatusInternalServerError, "操作失败: %s", err) + return + } + + ctx.String(http.StatusOK, "操作成功") +} + // ChangeEnableWelcomeStatus // @description: 修改是否开启迎新 // @param ctx diff --git a/config.yaml b/config.yaml index b5ff71e..044a642 100644 --- a/config.yaml +++ b/config.yaml @@ -3,7 +3,7 @@ wechat: # 微信HOOK接口地址 host: 10.0.0.73:19088 # 微信容器映射出来的vnc页面地址,没有就不填 -# vncUrl: http://192.168.1.175:19087/vnc_lite.html + # vncUrl: http://192.168.1.175:19087/vnc_lite.html # 是否在启动的时候自动设置hook服务的回调 autoSetCallback: false # 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port @@ -23,17 +23,20 @@ mysql: schema: public # postgres 专用 task: - enable: false + enable: true syncFriends: - enable: false - cron: '*/5 * * * *' # 五分钟一次 + enable: false + cron: '*/5 * * * *' # 五分钟一次 + groupSummary: + enable: true + cron: '30 0 * * *' # 每天0:30 waterGroup: - enable: true - cron: - yesterday: '30 9 * * *' # 每天9:30 - week: '30 9 * * 1' # 每周一9:30 - month: '30 9 1 * *' # 每月1号9:30 - year: '0 9 1 1 *' # 每年1月1号9:30 + enable: false + cron: + yesterday: '30 9 * * *' # 每天9:30 + week: '30 9 * * 1' # 每周一9:30 + month: '30 9 1 * *' # 每月1号9:30 + year: '0 9 1 1 *' # 每年1月1号9:30 # MQ配置 mq: @@ -53,6 +56,8 @@ ai: enable: false # 模型,不填默认gpt-3.5-turbo-0613 model: gpt-3.5-turbo-0613 + # 群聊总结模型 + summaryModel: gpt-4-0613 # OpenAI Api key apiKey: sk-xxxx # 接口代理域名,不填默认ChatGPT官方地址 diff --git a/config/ai.go b/config/ai.go index 1aeff19..fb05374 100644 --- a/config/ai.go +++ b/config/ai.go @@ -3,12 +3,13 @@ package config // ai // @description: AI配置 type ai struct { - Enable bool `json:"enable" yaml:"enable"` // 是否启用AI - Model string `json:"model" yaml:"model"` // 模型 - ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key - BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址 - Personality string `json:"personality" yaml:"personality"` // 人设 - Models []aiModel `json:"models" yaml:"models"` // 模型列表 + Enable bool `json:"enable" yaml:"enable"` // 是否启用AI + Model string `json:"model" yaml:"model"` // 模型 + SummaryModel string `json:"summaryModel" yaml:"summaryModel"` // 总结模型 + ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key + BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址 + Personality string `json:"personality" yaml:"personality"` // 人设 + Models []aiModel `json:"models" yaml:"models"` // 模型列表 } // aiModel diff --git a/config/task.go b/config/task.go index 9db7ed3..ce46e7d 100644 --- a/config/task.go +++ b/config/task.go @@ -3,9 +3,10 @@ package config // task // @description: 定时任务 type task struct { - Enable bool `json:"enable" yaml:"enable"` // 是否启用 - SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友 - WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜 + Enable bool `json:"enable" yaml:"enable"` // 是否启用 + SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友 + WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜 + GroupSummary syncFriends `json:"groupSummary" yaml:"groupSummary"` // 群聊总结 } // syncFriends diff --git a/entity/friend.go b/entity/friend.go index 4261784..0cb57be 100644 --- a/entity/friend.go +++ b/entity/friend.go @@ -17,6 +17,7 @@ type Friend struct { AiModel string `json:"aiModel"` // AI模型 EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行 EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新 + EnableSummary bool `json:"enableSummary" gorm:"type:tinyint(1) default 0 not null"` // 是否启用总结 IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常 } diff --git a/router/router.go b/router/router.go index 5213f82..33848da 100644 --- a/router/router.go +++ b/router/router.go @@ -28,4 +28,5 @@ func Init(g *gin.Engine) { api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态 api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态 api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表 + api.PUT("/summary/status", app.ChangeEnableSummaryStatus) // 修改是否开启群聊总结状态 } diff --git a/service/friend.go b/service/friend.go index 3ba8cc2..856120c 100644 --- a/service/friend.go +++ b/service/friend.go @@ -53,6 +53,15 @@ func GetAllEnableChatRank() (records []entity.Friend, err error) { return } +// GetAllEnableSummary +// @description: 取出所有启用了总结的群组 +// @return records +// @return err +func GetAllEnableSummary() (records []entity.Friend, err error) { + err = client.MySQL.Where("enable_summary = ?", 1).Where("wxid LIKE '%@chatroom'").Find(&records).Error + return +} + // CheckIsEnableCommand // @description: 检查用户是否启用了指令 // @param userId diff --git a/service/message.go b/service/message.go index 2e0c9cf..343fd0e 100644 --- a/service/message.go +++ b/service/message.go @@ -3,6 +3,7 @@ package service import ( "go-wechat/client" "go-wechat/entity" + "go-wechat/vo" "log" "os" "strconv" @@ -39,3 +40,22 @@ func SaveMessage(msg entity.Message) { go updateLastActive(msg) } } + +// GetTextMessagesById +// @description: 根据群id或者用户Id获取消息 +// @param id +// @return records +// @return err +func GetTextMessagesById(id string) (records []vo.TextMessageItem, err error) { + tx := client.MySQL. + Table("`t_message` AS tm"). + Joins("LEFT JOIN t_group_user AS tgu ON tm.group_user = tgu.wxid AND tgu.group_id = tm.from_user"). + Select("tgu.nickname", "IF( tm.type = 49, EXTRACTVALUE ( tm.content, \"/msg/appmsg/title\" ), tm.content ) AS message"). + Where("tm.`from_user` = ?", id). + Where(`(tm.type = 1 OR ( tm.type = 49 AND EXTRACTVALUE ( tm.content, "/msg/appmsg/type" ) = '57' ))`). + Where("DATE ( tm.create_at ) = DATE ( CURDATE() - INTERVAL 1 DAY )"). + Order("tm.create_at ASC") + + err = tx.Find(&records).Error + return +} diff --git a/tasks/summary/summary.go b/tasks/summary/summary.go new file mode 100644 index 0000000..0f27f82 --- /dev/null +++ b/tasks/summary/summary.go @@ -0,0 +1,81 @@ +package summary + +import ( + "context" + "fmt" + "github.com/sashabaranov/go-openai" + "go-wechat/config" + "go-wechat/service" + "go-wechat/utils" + "go-wechat/vo" + "log" + "strings" +) + +// AiSummary +// @description: AI总结群聊记录 +func AiSummary() { + groups, err := service.GetAllEnableSummary() + if err != nil { + log.Printf("获取启用了聊天排行榜的群组失败, 错误信息: %v", err) + return + } + + for _, group := range groups { + // 获取对话记录 + var records []vo.TextMessageItem + if records, err = service.GetTextMessagesById(group.Wxid); err != nil { + log.Printf("获取群[%s]对话记录失败, 错误信息: %v", group.Wxid, err) + continue + } + //if len(records) < 100 { + // log.Printf("群[%s]对话记录不足100条,跳过总结", group.Wxid) + // continue + //} + // 组装对话记录为字符串 + var content []string + for _, record := range records { + content = append(content, fmt.Sprintf("%s: %s\n-----end-----", record.Nickname, record.Message)) + } + + msg := fmt.Sprintf("请帮我总结一下一下的群聊内容的梗概(内容尽可能详细一些)。\n"+ + "注意,他们可能是多个话题,请仔细甄别。\n"+ + "每一行代表一个人的发言,每一行的的格式为: \n{nickname}: {content}\n-----end-----"+ + "\n\n聊天记录如下: \n%s", strings.Join(content, "\n")) + + // AI总结 + messages := []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: msg, + }, + } + + // 默认使用AI回复 + conf := openai.DefaultConfig(config.Conf.Ai.ApiKey) + if config.Conf.Ai.BaseUrl != "" { + conf.BaseURL = fmt.Sprintf("%s/v1", config.Conf.Ai.BaseUrl) + } + ai := openai.NewClientWithConfig(conf) + var resp openai.ChatCompletionResponse + resp, err = ai.CreateChatCompletion( + context.Background(), + openai.ChatCompletionRequest{ + Model: config.Conf.Ai.SummaryModel, + Messages: messages, + }, + ) + + if err != nil { + log.Printf("群聊记录总结失败: %v", err.Error()) + continue + } + + // 返回消息为空 + if resp.Choices[0].Message.Content == "" { + continue + } + replyMsg := fmt.Sprintf("#昨日消息总结\n\n%s", resp.Choices[0].Message.Content) + utils.SendMessage(group.Wxid, "", replyMsg, 0) + } +} diff --git a/tasks/tasks.go b/tasks/tasks.go index d6ef579..630f643 100644 --- a/tasks/tasks.go +++ b/tasks/tasks.go @@ -4,6 +4,7 @@ import ( "github.com/go-co-op/gocron" "go-wechat/config" "go-wechat/tasks/friends" + "go-wechat/tasks/summary" "go-wechat/tasks/watergroup" "log" "time" @@ -37,6 +38,13 @@ func InitTasks() { } } + // 群聊总结 + if config.Conf.Task.GroupSummary.Enable { + log.Printf("群聊总结任务已启用,执行表达式: %s", config.Conf.Task.GroupSummary.Cron) + _, _ = s.Cron(config.Conf.Task.GroupSummary.Cron).Do(summary.AiSummary) + + } + // 更新好友列表 if config.Conf.Task.SyncFriends.Enable { log.Printf("更新好友列表任务已启用,执行表达式: %s", config.Conf.Task.SyncFriends.Cron) diff --git a/views/index.html b/views/index.html index e0e72f6..06fb6df 100644 --- a/views/index.html +++ b/views/index.html @@ -108,6 +108,7 @@ 是否在通讯录 是否启用AI 是否启用水群排行榜 + 是否启用聊天记录总结 是否启用迎新 是否启用指令 操作 @@ -164,6 +165,15 @@
❌已禁用
+ + + +