forked from lxh/go-wxhelper
✨ 完善插件
This commit is contained in:
parent
7e545cef95
commit
e6c0bfe2cc
@ -1,19 +1,44 @@
|
||||
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
|
||||
// @description: 设置机器人信息
|
||||
// @param info
|
||||
func SetRobotInfo(info model.RobotUserInfo) {
|
||||
robotInfo = info
|
||||
ri.info = info
|
||||
}
|
||||
|
||||
// GetRobotInfo
|
||||
// @description: 获取机器人信息
|
||||
// @return 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
|
||||
}
|
||||
|
@ -33,13 +33,13 @@ task:
|
||||
# AI回复
|
||||
ai:
|
||||
# 是否启用
|
||||
enable: false
|
||||
enable: true
|
||||
# 模型,不填默认gpt-3.5-turbo-0613
|
||||
model: gpt-3.5-turbo-0613
|
||||
# OpenAI Api key
|
||||
apiKey: sk-xxxx
|
||||
apiKey: sk-QECv0PugGIFz94AQ65072d301aAd47CfA59482Cd6fA2607d
|
||||
# 接口代理域名,不填默认ChatGPT官方地址
|
||||
baseUrl: https://sxxx
|
||||
baseUrl: https://aiapi.lixh.ltd
|
||||
# 人设
|
||||
personality: 你的名字叫张三,你是一个百科机器人,你的爱好是看电影,你的性格是开朗的,你的专长是讲故事,你的梦想是当一名童话故事作家。你对政治没有一点儿兴趣,也不会讨论任何与政治相关的话题,你甚至可以拒绝回答这一类话题。
|
||||
|
||||
|
38
initialization/plugin.go
Normal file
38
initialization/plugin.go
Normal 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))
|
||||
}
|
1
main.go
1
main.go
@ -18,6 +18,7 @@ import (
|
||||
func init() {
|
||||
initialization.InitConfig() // 初始化配置
|
||||
initialization.InitWechatRobotInfo() // 初始化机器人信息
|
||||
initialization.Plugin() // 注册插件
|
||||
tasks.InitTasks() // 初始化定时任务
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ type Message struct {
|
||||
Signature string `json:"signature"`
|
||||
ToUser string `json:"toUser"`
|
||||
Type types.MessageType `json:"type"`
|
||||
Raw string `json:"raw"`
|
||||
}
|
||||
|
||||
// systemMsgDataXml
|
||||
@ -89,3 +90,12 @@ func (m Message) IsNewUserJoin() bool {
|
||||
func (m Message) IsAt() bool {
|
||||
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
143
plugin/plugin.go
Normal 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)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"go-wechat/common/current"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/model"
|
||||
"go-wechat/plugin"
|
||||
"go-wechat/service"
|
||||
"go-wechat/types"
|
||||
"go-wechat/utils"
|
||||
@ -19,10 +19,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// handleAtMessage
|
||||
// @description: 处理At机器人的消息
|
||||
// AI
|
||||
// @description: AI消息
|
||||
// @param m
|
||||
func handleAtMessage(m model.Message) {
|
||||
func AI(m *plugin.MessageContext) {
|
||||
if !config.Conf.Ai.Enable {
|
||||
return
|
||||
}
|
||||
@ -54,13 +54,14 @@ func handleAtMessage(m model.Message) {
|
||||
|
||||
// 查询发信人前面几条文字信息,组装进来
|
||||
var oldMessages []entity.Message
|
||||
client.MySQL.Model(&entity.Message{}).
|
||||
Where("msg_id != ?", m.MsgId).
|
||||
Where("create_at >= DATE_SUB(NOW(),INTERVAL 30 MINUTE)").
|
||||
Where("from_user = ? AND group_user = ? AND display_full_content LIKE ?", m.FromUser, m.GroupUser, "%在群聊中@了你").
|
||||
Or("to_user = ? AND group_user = ?", m.FromUser, m.GroupUser).
|
||||
Order("create_at desc").
|
||||
Limit(4).Find(&oldMessages)
|
||||
if m.GroupUser == "" {
|
||||
// 私聊
|
||||
oldMessages = getUserPrivateMessages(m.FromUser)
|
||||
} else {
|
||||
// 群聊
|
||||
oldMessages = getGroupUserMessages(m.MsgId, m.FromUser, m.GroupUser)
|
||||
}
|
||||
|
||||
// 翻转数组
|
||||
slice.Reverse(oldMessages)
|
||||
// 循环填充消息
|
||||
@ -74,7 +75,7 @@ func handleAtMessage(m model.Message) {
|
||||
}
|
||||
// 填充消息
|
||||
role := openai.ChatMessageRoleUser
|
||||
if message.ToUser != current.GetRobotInfo().WxId {
|
||||
if message.FromUser == current.GetRobotInfo().WxId {
|
||||
// 如果收信人不是机器人,表示这条消息是 AI 发的
|
||||
role = openai.ChatMessageRoleAssistant
|
||||
}
|
||||
@ -129,5 +130,43 @@ func handleAtMessage(m model.Message) {
|
||||
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
27
plugin/plugins/save2db.go
Normal 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)
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package handler
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"go-wechat/client"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/model"
|
||||
"go-wechat/plugin"
|
||||
"go-wechat/utils"
|
||||
)
|
||||
|
||||
// handleNewUserJoin
|
||||
// WelcomeNew
|
||||
// @description: 欢迎新成员
|
||||
// @param m
|
||||
func handleNewUserJoin(m model.Message) {
|
||||
func WelcomeNew(m *plugin.MessageContext) {
|
||||
// 判断是否开启迎新
|
||||
var count int64
|
||||
client.MySQL.Model(&entity.Friend{}).Where("enable_welcome IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)
|
@ -1,10 +0,0 @@
|
||||
package plugins
|
||||
|
||||
// Message
|
||||
// @description: 插件消息
|
||||
type Message struct {
|
||||
GroupId string // 消息来源群Id
|
||||
UserId string // 消息来源用户Id
|
||||
Message string // 消息内容
|
||||
IsBreak bool // 是否中断消息传递
|
||||
}
|
@ -3,7 +3,6 @@ package tcpserver
|
||||
import (
|
||||
"bytes"
|
||||
"go-wechat/config"
|
||||
"go-wechat/handler"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@ -24,7 +23,7 @@ func process(conn net.Conn) {
|
||||
log.Printf("[%s]返回数据失败,错误信息: %v", conn.RemoteAddr(), err)
|
||||
}
|
||||
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 {
|
||||
|
@ -1,28 +1,28 @@
|
||||
package handler
|
||||
package tcpserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/common/current"
|
||||
"go-wechat/model"
|
||||
"go-wechat/service"
|
||||
"go-wechat/types"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Parse
|
||||
// parse
|
||||
// @description: 解析消息
|
||||
// @param msg
|
||||
func Parse(remoteAddr net.Addr, msg []byte) {
|
||||
func parse(remoteAddr net.Addr, msg []byte) {
|
||||
var m model.Message
|
||||
if err := json.Unmarshal(msg, &m); err != nil {
|
||||
log.Printf("[%s]消息解析失败: %v", remoteAddr, err)
|
||||
log.Printf("[%s]消息内容: %d -> %v", remoteAddr, len(msg), string(msg))
|
||||
return
|
||||
}
|
||||
// 记录原始数据
|
||||
m.Raw = string(msg)
|
||||
|
||||
// 提取出群成员信息
|
||||
// Sys类型的消息正文不包含微信 Id,所以不需要处理
|
||||
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)
|
||||
|
||||
// 异步处理消息
|
||||
go func() {
|
||||
if m.IsNewUserJoin() {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
// 插件不为空,开始执行
|
||||
if p := current.GetRobotMessageHandler(); p != nil {
|
||||
p(&m)
|
||||
}
|
||||
|
||||
// 转换为结构体之后入库
|
||||
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user