forked from lxh/go-wxhelper
Merge pull request 'hotfix' (#35) from hotfix into main
Reviewed-on: lxh/go-wxhelper#35
This commit is contained in:
commit
3478f4f9e2
@ -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
|
||||
|
11
config.yaml
11
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,12 +23,15 @@ mysql:
|
||||
schema: public # postgres 专用
|
||||
|
||||
task:
|
||||
enable: false
|
||||
enable: true
|
||||
syncFriends:
|
||||
enable: false
|
||||
cron: '*/5 * * * *' # 五分钟一次
|
||||
waterGroup:
|
||||
groupSummary:
|
||||
enable: true
|
||||
cron: '30 0 * * *' # 每天0:30
|
||||
waterGroup:
|
||||
enable: false
|
||||
cron:
|
||||
yesterday: '30 9 * * *' # 每天9:30
|
||||
week: '30 9 * * 1' # 每周一9:30
|
||||
@ -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官方地址
|
||||
|
@ -5,6 +5,7 @@ package config
|
||||
type ai struct {
|
||||
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"` // 人设
|
||||
|
@ -6,6 +6,7 @@ type task struct {
|
||||
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
|
||||
|
@ -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"` // 是否正常
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
65
plugin/plugins/command/ai.go
Normal file
65
plugin/plugins/command/ai.go
Normal file
@ -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
|
||||
}
|
@ -23,6 +23,13 @@ option: 指令选项,可选值:
|
||||
|
||||
#2. 肯德基疯狂星期四文案
|
||||
/kfc、/肯德基
|
||||
|
||||
#3. AI助手
|
||||
/ai option
|
||||
option: 指令选项,可选值:
|
||||
启用: '启用'、'打开'、'enable'
|
||||
停用: '停用'、'禁用'、'关闭'、'disable'
|
||||
|
||||
`
|
||||
utils.SendMessage(m.FromUser, m.GroupUser, str, 0)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) // 修改是否开启群聊总结状态
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
81
tasks/summary/summary.go
Normal file
81
tasks/summary/summary.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -108,6 +108,7 @@
|
||||
<th>是否在通讯录</th>
|
||||
<th>是否启用AI</th>
|
||||
<th>是否启用水群排行榜</th>
|
||||
<th>是否启用聊天记录总结</th>
|
||||
<th>是否启用迎新</th>
|
||||
<th>是否启用指令</th>
|
||||
<th>操作</th>
|
||||
@ -164,6 +165,15 @@
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<!-- EnableSummary -->
|
||||
<label class="swap swap-flip {{ checkSwap .EnableSummary }}">
|
||||
<input type="checkbox" onclick="changeSummaryEnableStatus({{.Wxid}})"/>
|
||||
|
||||
<div class="swap-on">✔️已启用</div>
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label class="swap swap-flip {{ checkSwap .EnableWelcome }}">
|
||||
<input type="checkbox" onclick="changeWelcomeEnableStatus({{.Wxid}})"/>
|
||||
|
@ -12,6 +12,7 @@ function changeAiEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@ -29,6 +30,25 @@ function changeGroupRankEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
})
|
||||
}
|
||||
|
||||
// 修改水群排行榜状态
|
||||
function changeSummaryEnableStatus(wxId) {
|
||||
// console.log("修改聊天记录总结开启状态: ", wxId)
|
||||
axios({
|
||||
method: 'put',
|
||||
url: '/api/summary/status',
|
||||
data: {
|
||||
wxId: wxId
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@ -45,6 +65,7 @@ function changeWelcomeEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@ -61,6 +82,7 @@ function changeCommandEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@ -79,6 +101,7 @@ function changeUserGroupRankSkipStatus(groupId, userId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
|
@ -18,6 +18,7 @@ type FriendItem struct {
|
||||
EnableChatRank bool // 是否使用聊天排行
|
||||
EnableWelcome bool // 是否使用迎新
|
||||
EnableCommand bool // 是否启用指令
|
||||
EnableSummary bool // 是否启用总结
|
||||
IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
|
||||
}
|
||||
|
||||
|
8
vo/message.go
Normal file
8
vo/message.go
Normal file
@ -0,0 +1,8 @@
|
||||
package vo
|
||||
|
||||
// TextMessageItem
|
||||
// @description: 文字消息
|
||||
type TextMessageItem struct {
|
||||
Nickname string `json:"nickname"`
|
||||
Message string `json:"message"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user