1
0
forked from lxh/go-wxhelper

完善插件

This commit is contained in:
李寻欢 2023-12-11 10:44:23 +08:00
parent 7e545cef95
commit e6c0bfe2cc
12 changed files with 320 additions and 72 deletions

View File

@ -1,19 +1,44 @@
package current package current
import "go-wechat/model" import (
"go-wechat/model"
plugin "go-wechat/plugin"
)
var robotInfo model.RobotUserInfo // robotInfo
// @description: 机器人信息
type robotInfo struct {
info model.RobotUserInfo
MessageHandler plugin.MessageHandler // 启用的插件
}
// 当前接入的机器人信息
var ri robotInfo
// SetRobotInfo // SetRobotInfo
// @description: 设置机器人信息 // @description: 设置机器人信息
// @param info // @param info
func SetRobotInfo(info model.RobotUserInfo) { func SetRobotInfo(info model.RobotUserInfo) {
robotInfo = info ri.info = info
} }
// GetRobotInfo // GetRobotInfo
// @description: 获取机器人信息 // @description: 获取机器人信息
// @return model.RobotUserInfo // @return model.RobotUserInfo
func GetRobotInfo() model.RobotUserInfo { func GetRobotInfo() model.RobotUserInfo {
return robotInfo return ri.info
}
// GetRobotMessageHandler
// @description: 获取机器人插件信息
// @return robotInfo
func GetRobotMessageHandler() plugin.MessageHandler {
return ri.MessageHandler
}
// SetRobotMessageHandler
// @description: 设置机器人插件信息
// @param handler
func SetRobotMessageHandler(handler plugin.MessageHandler) {
ri.MessageHandler = handler
} }

View File

@ -33,13 +33,13 @@ task:
# AI回复 # AI回复
ai: ai:
# 是否启用 # 是否启用
enable: false enable: true
# 模型不填默认gpt-3.5-turbo-0613 # 模型不填默认gpt-3.5-turbo-0613
model: gpt-3.5-turbo-0613 model: gpt-3.5-turbo-0613
# OpenAI Api key # OpenAI Api key
apiKey: sk-xxxx apiKey: sk-QECv0PugGIFz94AQ65072d301aAd47CfA59482Cd6fA2607d
# 接口代理域名不填默认ChatGPT官方地址 # 接口代理域名不填默认ChatGPT官方地址
baseUrl: https://sxxx baseUrl: https://aiapi.lixh.ltd
# 人设 # 人设
personality: 你的名字叫张三,你是一个百科机器人,你的爱好是看电影,你的性格是开朗的,你的专长是讲故事,你的梦想是当一名童话故事作家。你对政治没有一点儿兴趣,也不会讨论任何与政治相关的话题,你甚至可以拒绝回答这一类话题。 personality: 你的名字叫张三,你是一个百科机器人,你的爱好是看电影,你的性格是开朗的,你的专长是讲故事,你的梦想是当一名童话故事作家。你对政治没有一点儿兴趣,也不会讨论任何与政治相关的话题,你甚至可以拒绝回答这一类话题。

38
initialization/plugin.go Normal file
View File

@ -0,0 +1,38 @@
package initialization
import (
"go-wechat/common/current"
"go-wechat/model"
plugin "go-wechat/plugin"
"go-wechat/plugin/plugins"
)
// Plugin
// @description: 初始化插件
func Plugin() {
// 定义一个处理器
dispatcher := plugin.NewMessageMatchDispatcher()
// 设置为异步处理
dispatcher.SetAsync(true)
// 注册插件
// 保存消息进数据库
dispatcher.RegisterHandler(func(*model.Message) bool {
return true
}, plugins.SaveToDb)
// AI消息插件
dispatcher.RegisterHandler(func(m *model.Message) bool {
// 群内@或者私聊文字消息
return m.IsAt() || m.IsPrivateText()
}, plugins.AI)
// 欢迎新成员
dispatcher.RegisterHandler(func(m *model.Message) bool {
return m.IsNewUserJoin()
}, plugins.WelcomeNew)
// 注册消息处理器
current.SetRobotMessageHandler(plugin.DispatchMessage(dispatcher))
}

View File

@ -18,6 +18,7 @@ import (
func init() { func init() {
initialization.InitConfig() // 初始化配置 initialization.InitConfig() // 初始化配置
initialization.InitWechatRobotInfo() // 初始化机器人信息 initialization.InitWechatRobotInfo() // 初始化机器人信息
initialization.Plugin() // 注册插件
tasks.InitTasks() // 初始化定时任务 tasks.InitTasks() // 初始化定时任务
} }

View File

@ -20,6 +20,7 @@ type Message struct {
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"`
Raw string `json:"raw"`
} }
// systemMsgDataXml // systemMsgDataXml
@ -89,3 +90,12 @@ func (m Message) IsNewUserJoin() bool {
func (m Message) IsAt() bool { func (m Message) IsAt() bool {
return strings.HasSuffix(m.DisplayFullContent, "在群聊中@了你") return strings.HasSuffix(m.DisplayFullContent, "在群聊中@了你")
} }
// IsPrivateText
// @description: 是否是私聊消息
// @receiver m
// @return bool
func (m Message) IsPrivateText() bool {
// 发信人不以@chatroom结尾且消息类型为文本
return !strings.HasSuffix(m.FromUser, "chatroom") && m.Type == types.MsgTypeText
}

143
plugin/plugin.go Normal file
View File

@ -0,0 +1,143 @@
package plugin
import (
"go-wechat/model"
)
// MessageHandler 消息处理函数
type MessageHandler func(msg *model.Message)
// MessageDispatcher 消息分发处理接口
// 跟 DispatchMessage 结合封装成 MessageHandler
type MessageDispatcher interface {
Dispatch(msg *model.Message)
}
// DispatchMessage 跟 MessageDispatcher 结合封装成 MessageHandler
func DispatchMessage(dispatcher MessageDispatcher) func(msg *model.Message) {
return func(msg *model.Message) { dispatcher.Dispatch(msg) }
}
// MessageDispatcher impl
// MessageContextHandler 消息处理函数
type MessageContextHandler func(ctx *MessageContext)
type MessageContextHandlerGroup []MessageContextHandler
// MessageContext 消息处理上下文对象
type MessageContext struct {
index int
abortIndex int
messageHandlers MessageContextHandlerGroup
*model.Message
}
// Next 主动调用下一个消息处理函数(或开始调用)
func (c *MessageContext) Next() {
c.index++
for c.index <= len(c.messageHandlers) {
if c.IsAbort() {
return
}
handle := c.messageHandlers[c.index-1]
handle(c)
c.index++
}
}
// IsAbort 判断是否被中断
func (c *MessageContext) IsAbort() bool {
return c.abortIndex > 0
}
// Abort 中断当前消息处理, 不会调用下一个消息处理函数, 但是不会中断当前的处理函数
func (c *MessageContext) Abort() {
c.abortIndex = c.index
}
// AbortHandler 获取当前中断的消息处理函数
func (c *MessageContext) AbortHandler() MessageContextHandler {
if c.abortIndex > 0 {
return c.messageHandlers[c.abortIndex-1]
}
return nil
}
// MatchFunc 消息匹配函数,返回为true则表示匹配
type MatchFunc func(*model.Message) bool
// MatchFuncList 将多个MatchFunc封装成一个MatchFunc
func MatchFuncList(matchFuncs ...MatchFunc) MatchFunc {
return func(message *model.Message) bool {
for _, matchFunc := range matchFuncs {
if !matchFunc(message) {
return false
}
}
return true
}
}
type matchNode struct {
matchFunc MatchFunc
group MessageContextHandlerGroup
}
type matchNodes []*matchNode
// MessageMatchDispatcher impl MessageDispatcher interface
//
// dispatcher := NewMessageMatchDispatcher()
// dispatcher.OnText(func(msg *model.Message){
// msg.ReplyText("hello")
// })
// bot := DefaultBot()
// bot.MessageHandler = DispatchMessage(dispatcher)
type MessageMatchDispatcher struct {
async bool
matchNodes matchNodes
}
// NewMessageMatchDispatcher Constructor
func NewMessageMatchDispatcher() *MessageMatchDispatcher {
return &MessageMatchDispatcher{}
}
// SetAsync 设置是否异步处理
func (m *MessageMatchDispatcher) SetAsync(async bool) {
m.async = async
}
// Dispatch impl MessageDispatcher
// 遍历 MessageMatchDispatcher 所有的消息处理函数
// 获取所有匹配上的函数
// 执行处理的消息处理方法
func (m *MessageMatchDispatcher) Dispatch(msg *model.Message) {
var group MessageContextHandlerGroup
for _, node := range m.matchNodes {
if node.matchFunc(msg) {
group = append(group, node.group...)
}
}
ctx := &MessageContext{Message: msg, messageHandlers: group}
if m.async {
go m.do(ctx)
} else {
m.do(ctx)
}
}
func (m *MessageMatchDispatcher) do(ctx *MessageContext) {
ctx.Next()
}
// RegisterHandler 注册消息处理函数, 根据自己的需求自定义
// matchFunc返回true则表示处理对应的handlers
func (m *MessageMatchDispatcher) RegisterHandler(matchFunc MatchFunc, handlers ...MessageContextHandler) {
if matchFunc == nil {
panic("MatchFunc can not be nil")
}
node := &matchNode{matchFunc: matchFunc, group: handlers}
m.matchNodes = append(m.matchNodes, node)
}

View File

@ -1,4 +1,4 @@
package handler package plugins
import ( import (
"context" "context"
@ -9,7 +9,7 @@ import (
"go-wechat/common/current" "go-wechat/common/current"
"go-wechat/config" "go-wechat/config"
"go-wechat/entity" "go-wechat/entity"
"go-wechat/model" "go-wechat/plugin"
"go-wechat/service" "go-wechat/service"
"go-wechat/types" "go-wechat/types"
"go-wechat/utils" "go-wechat/utils"
@ -19,10 +19,10 @@ import (
"time" "time"
) )
// handleAtMessage // AI
// @description: 处理At机器人的消息 // @description: AI消息
// @param m // @param m
func handleAtMessage(m model.Message) { func AI(m *plugin.MessageContext) {
if !config.Conf.Ai.Enable { if !config.Conf.Ai.Enable {
return return
} }
@ -54,13 +54,14 @@ func handleAtMessage(m model.Message) {
// 查询发信人前面几条文字信息,组装进来 // 查询发信人前面几条文字信息,组装进来
var oldMessages []entity.Message var oldMessages []entity.Message
client.MySQL.Model(&entity.Message{}). if m.GroupUser == "" {
Where("msg_id != ?", m.MsgId). // 私聊
Where("create_at >= DATE_SUB(NOW(),INTERVAL 30 MINUTE)"). oldMessages = getUserPrivateMessages(m.FromUser)
Where("from_user = ? AND group_user = ? AND display_full_content LIKE ?", m.FromUser, m.GroupUser, "%在群聊中@了你"). } else {
Or("to_user = ? AND group_user = ?", m.FromUser, m.GroupUser). // 群聊
Order("create_at desc"). oldMessages = getGroupUserMessages(m.MsgId, m.FromUser, m.GroupUser)
Limit(4).Find(&oldMessages) }
// 翻转数组 // 翻转数组
slice.Reverse(oldMessages) slice.Reverse(oldMessages)
// 循环填充消息 // 循环填充消息
@ -74,7 +75,7 @@ func handleAtMessage(m model.Message) {
} }
// 填充消息 // 填充消息
role := openai.ChatMessageRoleUser role := openai.ChatMessageRoleUser
if message.ToUser != current.GetRobotInfo().WxId { if message.FromUser == current.GetRobotInfo().WxId {
// 如果收信人不是机器人,表示这条消息是 AI 发的 // 如果收信人不是机器人,表示这条消息是 AI 发的
role = openai.ChatMessageRoleAssistant role = openai.ChatMessageRoleAssistant
} }
@ -129,5 +130,43 @@ func handleAtMessage(m model.Message) {
service.SaveMessage(replyMessage) // 保存消息 service.SaveMessage(replyMessage) // 保存消息
// 发送消息 // 发送消息
utils.SendMessage(m.FromUser, m.GroupUser, "\n"+resp.Choices[0].Message.Content, 0) replyMsg := resp.Choices[0].Message.Content
if m.GroupUser != "" {
replyMsg = "\n" + resp.Choices[0].Message.Content
}
utils.SendMessage(m.FromUser, m.GroupUser, replyMsg, 0)
}
// getGroupUserMessages
// @description: 获取群成员消息
// @return records
func getGroupUserMessages(msgId int64, groupId, groupUserId string) (records []entity.Message) {
subQuery := client.MySQL.
Where("from_user = ? AND group_user = ? AND display_full_content LIKE ?", groupId, groupUserId, "%在群聊中@了你").
Or("to_user = ? AND group_user = ?", groupId, groupUserId)
client.MySQL.Model(&entity.Message{}).
Where("msg_id != ?", msgId).
Where("type = ?", types.MsgTypeText).
Where("create_at >= DATE_SUB(NOW(),INTERVAL 30 MINUTE)").
Where(subQuery).
Order("create_at desc").
Limit(4).Find(&records)
return
}
// getUserPrivateMessages
// @description: 获取用户私聊消息
// @return records
func getUserPrivateMessages(userId string) (records []entity.Message) {
subQuery := client.MySQL.
Where("from_user = ?", userId).Or("to_user = ?", userId)
client.MySQL.Model(&entity.Message{}).
Where("type = ?", types.MsgTypeText).
Where("create_at >= DATE_SUB(NOW(),INTERVAL 30 MINUTE)").
Where(subQuery).
Order("create_at desc").
Limit(4).Find(&records)
return
} }

27
plugin/plugins/save2db.go Normal file
View File

@ -0,0 +1,27 @@
package plugins
import (
"go-wechat/entity"
"go-wechat/plugin"
"go-wechat/service"
"time"
)
// SaveToDb
// @description: 保存消息到数据库
// @param m
func SaveToDb(m *plugin.MessageContext) {
var ent entity.Message
ent.MsgId = m.MsgId
ent.CreateTime = m.CreateTime
ent.CreateAt = time.Unix(int64(m.CreateTime), 0)
ent.Content = m.Content
ent.FromUser = m.FromUser
ent.GroupUser = m.GroupUser
ent.ToUser = m.ToUser
ent.Type = m.Type
ent.DisplayFullContent = m.DisplayFullContent
ent.Raw = m.Raw
// 保存入库
service.SaveMessage(ent)
}

View File

@ -1,17 +1,17 @@
package handler package plugins
import ( import (
"go-wechat/client" "go-wechat/client"
"go-wechat/config" "go-wechat/config"
"go-wechat/entity" "go-wechat/entity"
"go-wechat/model" "go-wechat/plugin"
"go-wechat/utils" "go-wechat/utils"
) )
// handleNewUserJoin // WelcomeNew
// @description: 欢迎新成员 // @description: 欢迎新成员
// @param m // @param m
func handleNewUserJoin(m model.Message) { func WelcomeNew(m *plugin.MessageContext) {
// 判断是否开启迎新 // 判断是否开启迎新
var count int64 var count int64
client.MySQL.Model(&entity.Friend{}).Where("enable_welcome IS TRUE").Where("wxid = ?", m.FromUser).Count(&count) client.MySQL.Model(&entity.Friend{}).Where("enable_welcome IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)

View File

@ -1,10 +0,0 @@
package plugins
// Message
// @description: 插件消息
type Message struct {
GroupId string // 消息来源群Id
UserId string // 消息来源用户Id
Message string // 消息内容
IsBreak bool // 是否中断消息传递
}

View File

@ -3,7 +3,6 @@ package tcpserver
import ( import (
"bytes" "bytes"
"go-wechat/config" "go-wechat/config"
"go-wechat/handler"
"io" "io"
"log" "log"
"net" "net"
@ -24,7 +23,7 @@ func process(conn net.Conn) {
log.Printf("[%s]返回数据失败,错误信息: %v", conn.RemoteAddr(), err) log.Printf("[%s]返回数据失败,错误信息: %v", conn.RemoteAddr(), err)
} }
log.Printf("[%s]数据长度: %d", conn.RemoteAddr(), buf.Len()) log.Printf("[%s]数据长度: %d", conn.RemoteAddr(), buf.Len())
go handler.Parse(conn.RemoteAddr(), buf.Bytes()) go parse(conn.RemoteAddr(), buf.Bytes())
// 转发到其他地方去 // 转发到其他地方去
if len(config.Conf.Wechat.Forward) > 0 { if len(config.Conf.Wechat.Forward) > 0 {

View File

@ -1,28 +1,28 @@
package handler package tcpserver
import ( import (
"encoding/json" "encoding/json"
"go-wechat/entity" "go-wechat/common/current"
"go-wechat/model" "go-wechat/model"
"go-wechat/service"
"go-wechat/types" "go-wechat/types"
"go-wechat/utils"
"log" "log"
"net" "net"
"strings" "strings"
"time"
) )
// Parse // parse
// @description: 解析消息 // @description: 解析消息
// @param msg // @param msg
func Parse(remoteAddr net.Addr, msg []byte) { func parse(remoteAddr net.Addr, msg []byte) {
var m model.Message var m model.Message
if err := json.Unmarshal(msg, &m); err != nil { if err := json.Unmarshal(msg, &m); err != nil {
log.Printf("[%s]消息解析失败: %v", remoteAddr, err) log.Printf("[%s]消息解析失败: %v", remoteAddr, err)
log.Printf("[%s]消息内容: %d -> %v", remoteAddr, len(msg), string(msg)) log.Printf("[%s]消息内容: %d -> %v", remoteAddr, len(msg), string(msg))
return return
} }
// 记录原始数据
m.Raw = string(msg)
// 提取出群成员信息 // 提取出群成员信息
// Sys类型的消息正文不包含微信 Id所以不需要处理 // Sys类型的消息正文不包含微信 Id所以不需要处理
if m.IsGroup() && m.Type != types.MsgTypeSys { if m.IsGroup() && m.Type != types.MsgTypeSys {
@ -38,33 +38,9 @@ func Parse(remoteAddr net.Addr, msg []byte) {
} }
log.Printf("%s\n消息来源: %s\n群成员: %s\n消息类型: %v\n消息内容: %s", remoteAddr, m.FromUser, m.GroupUser, m.Type, m.Content) log.Printf("%s\n消息来源: %s\n群成员: %s\n消息类型: %v\n消息内容: %s", remoteAddr, m.FromUser, m.GroupUser, m.Type, m.Content)
// 异步处理消息 // 插件不为空,开始执行
go func() { if p := current.GetRobotMessageHandler(); p != nil {
if m.IsNewUserJoin() { p(&m)
log.Printf("%s -> 开始迎新 -> %s", m.FromUser, m.Content) }
// 欢迎新成员
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
ent.MsgId = m.MsgId
ent.CreateTime = m.CreateTime
ent.CreateAt = time.Unix(int64(m.CreateTime), 0)
ent.Content = m.Content
ent.FromUser = m.FromUser
ent.GroupUser = m.GroupUser
ent.ToUser = m.ToUser
ent.Type = m.Type
ent.DisplayFullContent = m.DisplayFullContent
ent.Raw = string(msg)
go service.SaveMessage(ent)
} }