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 @@