Compare commits

..

4 Commits

Author SHA1 Message Date
89c504d019 🆕 页面新增迎新开关实现
All checks were successful
BuildImage / build-image (push) Successful in 1m19s
2023-12-04 14:24:30 +08:00
14d407eff1 🆕 新增迎新开关 2023-12-04 14:17:52 +08:00
d4fcfda112 🎨 消息处理逻辑优化 2023-12-04 14:17:32 +08:00
ce11fd40c4 🐛 Fix a bug. 2023-12-04 14:12:15 +08:00
12 changed files with 177 additions and 54 deletions

View File

@ -62,6 +62,29 @@ func ChangeEnableGroupRankStatus(ctx *gin.Context) {
ctx.String(http.StatusOK, "操作成功") ctx.String(http.StatusOK, "操作成功")
} }
// ChangeEnableWelcomeStatus
// @description: 修改是否开启迎新
// @param ctx
func ChangeEnableWelcomeStatus(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_welcome`", gorm.Expr(" !`enable_welcome`")).Error
if err != nil {
log.Printf("修改开启迎新失败:%s", err)
ctx.String(http.StatusInternalServerError, "操作失败: %s", err)
return
}
ctx.String(http.StatusOK, "操作成功")
}
// ChangeSkipGroupRankStatus // ChangeSkipGroupRankStatus
// @description: 修改是否跳过水群排行榜 // @description: 修改是否跳过水群排行榜
// @param ctx // @param ctx

View File

@ -14,6 +14,7 @@ type Friend struct {
PinyinAll string `json:"pinyinAll"` // 昵称全拼 PinyinAll string `json:"pinyinAll"` // 昵称全拼
EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行 EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常 IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
} }

View File

@ -4,9 +4,10 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"go-wechat/client"
"go-wechat/config" "go-wechat/config"
"go-wechat/entity" "go-wechat/entity"
"go-wechat/service" "go-wechat/model"
"go-wechat/utils" "go-wechat/utils"
"log" "log"
"regexp" "regexp"
@ -16,26 +17,15 @@ import (
// handleAtMessage // handleAtMessage
// @description: 处理At机器人的消息 // @description: 处理At机器人的消息
// @param m // @param m
func handleAtMessage(m entity.Message) { func handleAtMessage(m model.Message) {
if !config.Conf.Ai.Enable { if !config.Conf.Ai.Enable {
return return
} }
// 取出所有启用了AI的好友或群组 // 取出所有启用了AI的好友或群组
us, err := service.GetAllEnableAI() var count int64
if err != nil { client.MySQL.Model(&entity.Friend{}).Where("enable_ai IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)
utils.SendMessage(m.FromUser, m.GroupUser, "#系统异常\n"+err.Error(), 0) if count < 1 {
return
}
// 判断是否启用,如果没有启用,直接返回
var canUse bool
for _, u := range us {
if u.Wxid == m.FromUser {
canUse = true
break
}
}
if !canUse {
return return
} }
@ -63,9 +53,9 @@ func handleAtMessage(m entity.Message) {
}) })
// 配置模型 // 配置模型
model := openai.GPT3Dot5Turbo0613 chatModel := openai.GPT3Dot5Turbo0613
if config.Conf.Ai.Model != "" { if config.Conf.Ai.Model != "" {
model = config.Conf.Ai.Model chatModel = config.Conf.Ai.Model
} }
// 默认使用AI回复 // 默认使用AI回复
@ -77,7 +67,7 @@ func handleAtMessage(m entity.Message) {
resp, err := client.CreateChatCompletion( resp, err := client.CreateChatCompletion(
context.Background(), context.Background(),
openai.ChatCompletionRequest{ openai.ChatCompletionRequest{
Model: model, Model: chatModel,
Messages: messages, Messages: messages,
}, },
) )

View File

@ -6,6 +6,7 @@ import (
"go-wechat/model" "go-wechat/model"
"go-wechat/service" "go-wechat/service"
"go-wechat/types" "go-wechat/types"
"go-wechat/utils"
"log" "log"
"net" "net"
"strings" "strings"
@ -23,43 +24,47 @@ func Parse(remoteAddr net.Addr, msg []byte) {
return return
} }
// 提取出群成员信息 // 提取出群成员信息
groupUser := "" //groupUser := ""
msgStr := m.Content //msgStr := m.Content
if strings.Contains(m.FromUser, "@") { if strings.Contains(m.FromUser, "@") {
switch m.Type { // 群消息,处理一下消息和发信人
case types.MsgTypeRecalled: groupUser := strings.Split(m.Content, "\n")[0]
// 消息撤回 groupUser = strings.ReplaceAll(groupUser, ":", "")
case types.MsgTypeSys: // 如果两个id一致说明是系统发的
// 系统消息 if m.FromUser != groupUser {
go handleSysMessage(m) m.GroupUser = groupUser
default:
// 默认消息处理
groupUser = strings.Split(m.Content, "\n")[0]
groupUser = strings.ReplaceAll(groupUser, ":", "")
// 文字消息单独提出来处理一下
msgStr = strings.Join(strings.Split(m.Content, "\n")[1:], "\n")
} }
// 用户的操作单独提出来处理一下
m.Content = strings.Join(strings.Split(m.Content, "\n")[1:], "\n")
} }
log.Printf("%s\n消息来源: %s\n群成员: %s\n消息类型: %v\n消息内容: %s", remoteAddr, m.FromUser, groupUser, m.Type, msgStr) log.Printf("%s\n消息来源: %s\n群成员: %s\n消息类型: %v\n消息内容: %s", remoteAddr, m.FromUser, m.GroupUser, m.Type, m.Content)
// 异步处理消息
go func() {
if m.IsNewUserJoin() {
// 欢迎新成员
go handleNewUserJoin(m)
} else if m.IsAt() {
// @机器人的消息
go handleAtMessage(m)
} else if !strings.Contains(m.FromUser, "@") && m.Type == types.MsgTypeText {
// 私聊消息处理
utils.SendMessage(m.FromUser, "", "暂未开启私聊AI", 0)
}
}()
// 转换为结构体之后入库 // 转换为结构体之后入库
var ent entity.Message var ent entity.Message
ent.MsgId = m.MsgId ent.MsgId = m.MsgId
ent.CreateTime = m.CreateTime ent.CreateTime = m.CreateTime
ent.CreateAt = time.Unix(int64(m.CreateTime), 0) ent.CreateAt = time.Unix(int64(m.CreateTime), 0)
ent.Content = msgStr ent.Content = m.Content
ent.FromUser = m.FromUser ent.FromUser = m.FromUser
ent.GroupUser = groupUser ent.GroupUser = m.GroupUser
ent.ToUser = m.ToUser ent.ToUser = m.ToUser
ent.Type = m.Type ent.Type = m.Type
ent.DisplayFullContent = m.DisplayFullContent ent.DisplayFullContent = m.DisplayFullContent
ent.Raw = string(msg) ent.Raw = string(msg)
// 处理At机器人的消息
if strings.HasSuffix(m.DisplayFullContent, "在群聊中@了你") {
go handleAtMessage(ent)
}
go service.SaveMessage(ent) go service.SaveMessage(ent)
} }

View File

@ -1,19 +1,25 @@
package handler package handler
import ( import (
"go-wechat/client"
"go-wechat/entity"
"go-wechat/model" "go-wechat/model"
"go-wechat/utils" "go-wechat/utils"
"strings"
) )
// handleSysMessage // handleNewUserJoin
// @description: 系统消息处理 // @description: 欢迎新成员
// @param m // @param m
func handleSysMessage(m model.Message) { func handleNewUserJoin(m model.Message) {
// 有人进群 // 判断是否开启迎新
if strings.Contains(m.Content, "\"邀请\"") && strings.Contains(m.Content, "\"加入了群聊") { var count int64
// 发一张图乐呵乐呵 client.MySQL.Model(&entity.Friend{}).Where("enable_welcome IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)
// 自己欢迎自己图片地址 D:\Share\emoticon\welcome-yourself.gif if count < 1 {
utils.SendImage(m.FromUser, "D:\\Share\\emoticon\\welcome-yourself.gif", 0) return
} }
// 发一张图乐呵乐呵
// 自己欢迎自己图片地址 D:\Share\emoticon\welcome-yourself.gif
utils.SendImage(m.FromUser, "D:\\Share\\emoticon\\welcome-yourself.gif", 0)
} }

View File

@ -1,18 +1,87 @@
package model package model
import "go-wechat/types" import (
"encoding/xml"
"go-wechat/types"
"strings"
)
// Message // Message
// @description: 消息 // @description: 消息
type Message struct { type Message struct {
MsgId int64 `json:"msgId" gorm:"primarykey"` MsgId int64 `json:"msgId"`
CreateTime int `json:"createTime"` CreateTime int `json:"createTime"`
Content string `json:"content"` Content string `json:"content"`
DisplayFullContent string `json:"displayFullContent" gorm:"-"` DisplayFullContent string `json:"displayFullContent"`
FromUser string `json:"fromUser"` FromUser string `json:"fromUser"`
GroupUser string `json:"-"`
MsgSequence int `json:"msgSequence"` MsgSequence int `json:"msgSequence"`
Pid int `json:"pid"` Pid int `json:"pid"`
Signature string `json:"signature"` Signature string `json:"signature"`
ToUser string `json:"toUser"` ToUser string `json:"toUser"`
Type types.MessageType `json:"type"` Type types.MessageType `json:"type"`
} }
// systemMsgDataXml
// @description: 微信系统消息的xml结构
type systemMsgDataXml struct {
SysMsg sysMsg `xml:"sysmsg"`
Type string `xml:"type,attr"`
}
// sysMsg
// @description: 消息主体
type sysMsg struct{}
// IsPat
// @description: 是否是拍一拍消息
// @receiver m
// @return bool
func (m Message) IsPat() bool {
// 解析xml
var d systemMsgDataXml
if err := xml.Unmarshal([]byte(m.Content), &d); err != nil {
return false
}
return m.Type == types.MsgTypeRecalled && d.Type == "pat"
}
// IsRevokeMsg
// @description: 是否是撤回消息
// @receiver m
// @return bool
func (m Message) IsRevokeMsg() bool {
// 解析xml
var d systemMsgDataXml
if err := xml.Unmarshal([]byte(m.Content), &d); err != nil {
return false
}
return m.Type == types.MsgTypeRecalled && d.Type == "revokemsg"
}
// IsNewUserJoin
// @description: 是否是新人入群
// @receiver m
// @return bool
func (m Message) IsNewUserJoin() bool {
sysFlag := m.Type == types.MsgTypeSys && strings.Contains(m.Content, "\"邀请\"") && strings.Contains(m.Content, "\"加入了群聊")
if sysFlag {
return true
}
// 解析另一种情况
var d systemMsgDataXml
if err := xml.Unmarshal([]byte(m.Content), &d); err != nil {
return false
}
return m.Type == types.MsgTypeSys && d.Type == "delchatroommember"
}
// IsAt
// @description: 是否是At机器人的消息
// @receiver m
// @return bool
func (m Message) IsAt() bool {
return strings.HasSuffix(m.DisplayFullContent, "在群聊中@了你")
}

View File

@ -22,6 +22,7 @@ func Init(g *gin.Engine) {
// 接口 // 接口
api := g.Group("/api") api := g.Group("/api")
api.PUT("/ai/status", app.ChangeEnableAiStatus) // 修改是否开启AI状态 api.PUT("/ai/status", app.ChangeEnableAiStatus) // 修改是否开启AI状态
api.PUT("/welcome/status", app.ChangeEnableWelcomeStatus) // 修改是否开启迎新状态
api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态 api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态
api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态 api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态
api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表 api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表

View File

@ -65,6 +65,7 @@ func Sync() {
Pinyin: friend.Pinyin, Pinyin: friend.Pinyin,
PinyinAll: friend.PinyinAll, PinyinAll: friend.PinyinAll,
Wxid: friend.Wxid, Wxid: friend.Wxid,
IsOk: true,
}).Error }).Error
if err != nil { if err != nil {
log.Printf("新增好友失败: %s", err.Error()) log.Printf("新增好友失败: %s", err.Error())

View File

@ -4,6 +4,7 @@ import "fmt"
type MessageType int type MessageType int
// 微信定义的消息类型
const ( const (
MsgTypeText MessageType = 1 // 文本消息 MsgTypeText MessageType = 1 // 文本消息
MsgTypeImage MessageType = 3 // 图片消息 MsgTypeImage MessageType = 3 // 图片消息

View File

@ -90,6 +90,7 @@
<th>是否在通讯录</th> <th>是否在通讯录</th>
<th>是否启用AI</th> <th>是否启用AI</th>
<th>是否启用水群排行榜</th> <th>是否启用水群排行榜</th>
<th>是否启用迎新</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>
@ -134,6 +135,14 @@
<div class="swap-off">❌已禁用</div> <div class="swap-off">❌已禁用</div>
</label> </label>
</td> </td>
<td>
<label class="swap swap-flip {{ checkSwap .EnableWelcome }}">
<input type="checkbox" onclick="changeWelcomeEnableStatus({{.Wxid}})"/>
<div class="swap-on">✔️已启用</div>
<div class="swap-off">❌已禁用</div>
</label>
</td>
<td> <td>
<button class="btn btn-link" onclick="getGroupUsers({{.Wxid}}, {{.Nickname}})">查看群成员</button> <button class="btn btn-link" onclick="getGroupUsers({{.Wxid}}, {{.Nickname}})">查看群成员</button>
</td> </td>

View File

@ -35,6 +35,22 @@ function changeGroupRankEnableStatus(wxId) {
}) })
} }
// 修改欢迎语开启状态
function changeWelcomeEnableStatus(wxId) {
axios({
method: 'put',
url: '/api/welcome/status',
data: {
wxId: wxId
}
}).then(function (response) {
console.log(`返回结果: ${JSON.stringify(response)}`);
}).catch(function (error) {
console.log(`错误信息: ${error}`);
alert("修改失败")
})
}
// 修改群成员是否参与排行榜状态 // 修改群成员是否参与排行榜状态
function changeUserGroupRankSkipStatus(groupId, userId) { function changeUserGroupRankSkipStatus(groupId, userId) {
console.log("修改水群排行榜开启状态: ", groupId, userId) console.log("修改水群排行榜开启状态: ", groupId, userId)

View File

@ -14,6 +14,7 @@ type FriendItem struct {
Wxid string // 微信原始Id Wxid string // 微信原始Id
EnableAi bool // 是否使用AI EnableAi bool // 是否使用AI
EnableChatRank bool // 是否使用聊天排行 EnableChatRank bool // 是否使用聊天排行
EnableWelcome bool // 是否使用迎新
IsOk bool // 是否还在通讯库(群聊是要还在群里也算) IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
LastActiveTime types.DateTime // 最后活跃时间 LastActiveTime types.DateTime // 最后活跃时间
} }