Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c40dbead3e | ||
|
|
cf03704ea8 | ||
|
|
e2029e48c5 | ||
|
|
e4af82d73e | ||
|
|
f7c5ef21af | ||
|
|
a1e3af7953 | ||
|
|
e6c0bfe2cc | ||
|
|
7e545cef95 | ||
|
|
3fbaf7aeb6 | ||
|
|
3bc95e1317 | ||
|
|
cab6b2633e |
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ wechat:
|
|||||||
host: 10.0.0.73:19088
|
host: 10.0.0.73:19088
|
||||||
# 是否在启动的时候自动设置hook服务的回调
|
# 是否在启动的时候自动设置hook服务的回调
|
||||||
autoSetCallback: false
|
autoSetCallback: false
|
||||||
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port
|
# 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port
|
||||||
callback: 10.0.0.51
|
callback: 10.0.0.51
|
||||||
# 转发到其他地址
|
# 转发到其他地址
|
||||||
forward:
|
forward:
|
||||||
@@ -50,3 +50,7 @@ resource:
|
|||||||
welcome-new:
|
welcome-new:
|
||||||
type: emotion
|
type: emotion
|
||||||
path: 58e4150be2bba8f7b71974b10391f9e9
|
path: 58e4150be2bba8f7b71974b10391f9e9
|
||||||
|
# 水群排行榜词云,只能是图片,末尾的`\%s`也是必须的
|
||||||
|
wordcloud:
|
||||||
|
type: image
|
||||||
|
path: D:\Share\wordcloud\%s
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
"go-wechat/client"
|
|
||||||
"go-wechat/config"
|
|
||||||
"go-wechat/entity"
|
|
||||||
"go-wechat/model"
|
|
||||||
"go-wechat/utils"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handleAtMessage
|
|
||||||
// @description: 处理At机器人的消息
|
|
||||||
// @param m
|
|
||||||
func handleAtMessage(m model.Message) {
|
|
||||||
if !config.Conf.Ai.Enable {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取出所有启用了AI的好友或群组
|
|
||||||
var count int64
|
|
||||||
client.MySQL.Model(&entity.Friend{}).Where("enable_ai IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)
|
|
||||||
if count < 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预处理一下发送的消息,用正则去掉@机器人的内容
|
|
||||||
re := regexp.MustCompile(`@([^ ]+)`)
|
|
||||||
matches := re.FindStringSubmatch(m.Content)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
// 过滤掉第一个匹配到的
|
|
||||||
m.Content = strings.Replace(m.Content, matches[0], "", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组装消息体
|
|
||||||
messages := make([]openai.ChatCompletionMessage, 0)
|
|
||||||
if config.Conf.Ai.Personality != "" {
|
|
||||||
// 填充人设
|
|
||||||
messages = append(messages, openai.ChatCompletionMessage{
|
|
||||||
Role: openai.ChatMessageRoleSystem,
|
|
||||||
Content: config.Conf.Ai.Personality,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 填充用户消息
|
|
||||||
messages = append(messages, openai.ChatCompletionMessage{
|
|
||||||
Role: openai.ChatMessageRoleUser,
|
|
||||||
Content: m.Content,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 配置模型
|
|
||||||
chatModel := openai.GPT3Dot5Turbo0613
|
|
||||||
if config.Conf.Ai.Model != "" {
|
|
||||||
chatModel = config.Conf.Ai.Model
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认使用AI回复
|
|
||||||
conf := openai.DefaultConfig(config.Conf.Ai.ApiKey)
|
|
||||||
if config.Conf.Ai.BaseUrl != "" {
|
|
||||||
conf.BaseURL = fmt.Sprintf("%s/v1", config.Conf.Ai.BaseUrl)
|
|
||||||
}
|
|
||||||
client := openai.NewClientWithConfig(conf)
|
|
||||||
resp, err := client.CreateChatCompletion(
|
|
||||||
context.Background(),
|
|
||||||
openai.ChatCompletionRequest{
|
|
||||||
Model: chatModel,
|
|
||||||
Messages: messages,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("OpenAI聊天发起失败: %v", err.Error())
|
|
||||||
utils.SendMessage(m.FromUser, m.GroupUser, "AI炸啦~", 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
utils.SendMessage(m.FromUser, m.GroupUser, "\n"+resp.Choices[0].Message.Content, 0)
|
|
||||||
}
|
|
||||||
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() {
|
func init() {
|
||||||
initialization.InitConfig() // 初始化配置
|
initialization.InitConfig() // 初始化配置
|
||||||
initialization.InitWechatRobotInfo() // 初始化机器人信息
|
initialization.InitWechatRobotInfo() // 初始化机器人信息
|
||||||
|
initialization.Plugin() // 注册插件
|
||||||
tasks.InitTasks() // 初始化定时任务
|
tasks.InitTasks() // 初始化定时任务
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
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)
|
||||||
|
}
|
||||||
172
plugin/plugins/ai.go
Normal file
172
plugin/plugins/ai.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
"go-wechat/client"
|
||||||
|
"go-wechat/common/current"
|
||||||
|
"go-wechat/config"
|
||||||
|
"go-wechat/entity"
|
||||||
|
"go-wechat/plugin"
|
||||||
|
"go-wechat/service"
|
||||||
|
"go-wechat/types"
|
||||||
|
"go-wechat/utils"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI
|
||||||
|
// @description: AI消息
|
||||||
|
// @param m
|
||||||
|
func AI(m *plugin.MessageContext) {
|
||||||
|
if !config.Conf.Ai.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取出所有启用了AI的好友或群组
|
||||||
|
var count int64
|
||||||
|
client.MySQL.Model(&entity.Friend{}).Where("enable_ai IS TRUE").Where("wxid = ?", m.FromUser).Count(&count)
|
||||||
|
if count < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预处理一下发送的消息,用正则去掉@机器人的内容
|
||||||
|
re := regexp.MustCompile(`@([^ | ]+)`)
|
||||||
|
matches := re.FindStringSubmatch(m.Content)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
// 过滤掉第一个匹配到的
|
||||||
|
m.Content = strings.Replace(m.Content, matches[0], "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装消息体
|
||||||
|
messages := make([]openai.ChatCompletionMessage, 0)
|
||||||
|
if config.Conf.Ai.Personality != "" {
|
||||||
|
// 填充人设
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleSystem,
|
||||||
|
Content: config.Conf.Ai.Personality,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询发信人前面几条文字信息,组装进来
|
||||||
|
var oldMessages []entity.Message
|
||||||
|
if m.GroupUser == "" {
|
||||||
|
// 私聊
|
||||||
|
oldMessages = getUserPrivateMessages(m.FromUser)
|
||||||
|
} else {
|
||||||
|
// 群聊
|
||||||
|
oldMessages = getGroupUserMessages(m.MsgId, m.FromUser, m.GroupUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻转数组
|
||||||
|
slice.Reverse(oldMessages)
|
||||||
|
// 循环填充消息
|
||||||
|
for _, message := range oldMessages {
|
||||||
|
// 剔除@机器人的内容
|
||||||
|
msgStr := message.Content
|
||||||
|
matches = re.FindStringSubmatch(msgStr)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
// 过滤掉第一个匹配到的
|
||||||
|
msgStr = strings.Replace(msgStr, matches[0], "", 1)
|
||||||
|
}
|
||||||
|
// 填充消息
|
||||||
|
role := openai.ChatMessageRoleUser
|
||||||
|
if message.FromUser == current.GetRobotInfo().WxId {
|
||||||
|
// 如果收信人不是机器人,表示这条消息是 AI 发的
|
||||||
|
role = openai.ChatMessageRoleAssistant
|
||||||
|
}
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: role,
|
||||||
|
Content: msgStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充用户消息
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: m.Content,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 配置模型
|
||||||
|
chatModel := openai.GPT3Dot5Turbo0613
|
||||||
|
if config.Conf.Ai.Model != "" {
|
||||||
|
chatModel = config.Conf.Ai.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认使用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)
|
||||||
|
resp, err := ai.CreateChatCompletion(
|
||||||
|
context.Background(),
|
||||||
|
openai.ChatCompletionRequest{
|
||||||
|
Model: chatModel,
|
||||||
|
Messages: messages,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("OpenAI聊天发起失败: %v", err.Error())
|
||||||
|
utils.SendMessage(m.FromUser, m.GroupUser, "AI炸啦~", 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存一下AI 返回的消息,消息 Id 使用传入 Id 的负数
|
||||||
|
var replyMessage entity.Message
|
||||||
|
replyMessage.MsgId = -m.MsgId
|
||||||
|
replyMessage.CreateTime = int(time.Now().Local().Unix())
|
||||||
|
replyMessage.CreateAt = time.Now().Local()
|
||||||
|
replyMessage.Content = resp.Choices[0].Message.Content
|
||||||
|
replyMessage.FromUser = current.GetRobotInfo().WxId // 发信人是机器人
|
||||||
|
replyMessage.GroupUser = m.GroupUser // 群成员
|
||||||
|
replyMessage.ToUser = m.FromUser // 收信人是发信人
|
||||||
|
replyMessage.Type = types.MsgTypeText
|
||||||
|
service.SaveMessage(replyMessage) // 保存消息
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
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 (
|
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)
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package plugins
|
|
||||||
|
|
||||||
// Message
|
|
||||||
// @description: 插件消息
|
|
||||||
type Message struct {
|
|
||||||
GroupId string // 消息来源群Id
|
|
||||||
UserId string // 消息来源用户Id
|
|
||||||
Message string // 消息内容
|
|
||||||
IsBreak bool // 是否中断消息传递
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,8 @@ wechat:
|
|||||||
host: wechat:19088
|
host: wechat:19088
|
||||||
# 是否在启动的时候自动设置hook服务的回调
|
# 是否在启动的时候自动设置hook服务的回调
|
||||||
autoSetCallback: true
|
autoSetCallback: true
|
||||||
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port,如果使用项目提供的docker-compsoe.yaml文件启动,可以不写
|
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port,如果使用项目提供的docker-compsoe.yaml文件启动,可以填`auto`
|
||||||
callback:
|
callback: auto
|
||||||
|
|
||||||
# 数据库
|
# 数据库
|
||||||
mysql:
|
mysql:
|
||||||
|
|||||||
@@ -4,12 +4,18 @@ import (
|
|||||||
"go-wechat/client"
|
"go-wechat/client"
|
||||||
"go-wechat/entity"
|
"go-wechat/entity"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SaveMessage
|
// SaveMessage
|
||||||
// @description: 消息入库
|
// @description: 消息入库
|
||||||
// @param msg
|
// @param msg
|
||||||
func SaveMessage(msg entity.Message) {
|
func SaveMessage(msg entity.Message) {
|
||||||
|
if flag, _ := strconv.ParseBool(os.Getenv("DONT_SAVE")); flag {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 检查消息是否存在,存在就跳过
|
// 检查消息是否存在,存在就跳过
|
||||||
var count int64
|
var count int64
|
||||||
err := client.MySQL.Model(&entity.Message{}).Where("msg_id = ?", msg.MsgId).Count(&count).Error
|
err := client.MySQL.Model(&entity.Message{}).Where("msg_id = ?", msg.MsgId).Count(&count).Error
|
||||||
@@ -18,6 +24,7 @@ func SaveMessage(msg entity.Message) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
//log.Printf("消息已存在,消息Id: %d", msg.MsgId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = client.MySQL.Create(&msg).Error
|
err = client.MySQL.Create(&msg).Error
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package watergroup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go-wechat/config"
|
||||||
"go-wechat/service"
|
"go-wechat/service"
|
||||||
"go-wechat/utils"
|
"go-wechat/utils"
|
||||||
"log"
|
"log"
|
||||||
@@ -21,11 +22,17 @@ func Month() {
|
|||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
// 消息统计
|
// 消息统计
|
||||||
dealMonth(group.Wxid)
|
dealMonth(group.Wxid)
|
||||||
|
|
||||||
|
res, ok := config.Conf.Resource["wordcloud"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 获取上个月月份
|
// 获取上个月月份
|
||||||
yd := time.Now().Local().AddDate(0, 0, -1).Format("200601")
|
yd := time.Now().Local().AddDate(0, 0, -1).Format("200601")
|
||||||
// 发送词云
|
// 发送词云
|
||||||
fileName := fmt.Sprintf("%s_%s.png", yd, group.Wxid)
|
fileName := fmt.Sprintf("%s_%s.png", yd, group.Wxid)
|
||||||
utils.SendImage(group.Wxid, "D:\\Share\\wordcloud\\"+fileName, 0)
|
utils.SendImage(group.Wxid, fmt.Sprintf(res.Path, fileName), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package watergroup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go-wechat/config"
|
||||||
"go-wechat/service"
|
"go-wechat/service"
|
||||||
"go-wechat/utils"
|
"go-wechat/utils"
|
||||||
"log"
|
"log"
|
||||||
@@ -21,11 +22,17 @@ func Week() {
|
|||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
// 消息统计
|
// 消息统计
|
||||||
dealWeek(group.Wxid)
|
dealWeek(group.Wxid)
|
||||||
|
|
||||||
|
res, ok := config.Conf.Resource["wordcloud"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 获取上周周数
|
// 获取上周周数
|
||||||
year, weekNo := time.Now().Local().AddDate(0, 0, -1).ISOWeek()
|
year, weekNo := time.Now().Local().AddDate(0, 0, -1).ISOWeek()
|
||||||
// 发送词云
|
// 发送词云
|
||||||
fileName := fmt.Sprintf("%d%d_%s.png", year, weekNo, group.Wxid)
|
fileName := fmt.Sprintf("%d%d_%s.png", year, weekNo, group.Wxid)
|
||||||
utils.SendImage(group.Wxid, "D:\\Share\\wordcloud\\"+fileName, 0)
|
utils.SendImage(group.Wxid, fmt.Sprintf(res.Path, fileName), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package watergroup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go-wechat/config"
|
||||||
"go-wechat/service"
|
"go-wechat/service"
|
||||||
"go-wechat/utils"
|
"go-wechat/utils"
|
||||||
"log"
|
"log"
|
||||||
@@ -23,11 +24,17 @@ func Yesterday() {
|
|||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
// 消息统计
|
// 消息统计
|
||||||
dealYesterday(group.Wxid)
|
dealYesterday(group.Wxid)
|
||||||
|
|
||||||
|
res, ok := config.Conf.Resource["wordcloud"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 获取昨日日期
|
// 获取昨日日期
|
||||||
yd := time.Now().Local().AddDate(0, 0, -1).Format("20060102")
|
yd := time.Now().Local().AddDate(0, 0, -1).Format("20060102")
|
||||||
// 发送词云
|
// 发送词云
|
||||||
fileName := fmt.Sprintf("%s_%s.png", yd, group.Wxid)
|
fileName := fmt.Sprintf("%s_%s.png", yd, group.Wxid)
|
||||||
utils.SendImage(group.Wxid, "D:\\Share\\wordcloud\\"+fileName, 0)
|
utils.SendImage(group.Wxid, fmt.Sprintf(res.Path, fileName), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,13 @@ func ClearCallback() {
|
|||||||
// @param host
|
// @param host
|
||||||
func SetCallback(userHost string) {
|
func SetCallback(userHost string) {
|
||||||
// 获取本机IP地址
|
// 获取本机IP地址
|
||||||
host := net.ParseIP(netutil.GetInternalIp()).String()
|
host := userHost
|
||||||
|
if userHost == "auto" {
|
||||||
|
host = net.ParseIP(netutil.GetInternalIp()).String()
|
||||||
|
}
|
||||||
|
|
||||||
port := 19099
|
port := 19099
|
||||||
if userHost != "" {
|
if userHost != "" && userHost != "auto" {
|
||||||
uh := strings.Split(strings.TrimSpace(userHost), ":")
|
uh := strings.Split(strings.TrimSpace(userHost), ":")
|
||||||
host = uh[0]
|
host = uh[0]
|
||||||
if len(uh) == 2 {
|
if len(uh) == 2 {
|
||||||
|
|||||||
Reference in New Issue
Block a user