Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c40dbead3e | ||
|
|
cf03704ea8 | ||
|
|
e2029e48c5 | ||
|
|
e4af82d73e | ||
|
|
f7c5ef21af | ||
|
|
a1e3af7953 | ||
|
|
e6c0bfe2cc | ||
|
|
7e545cef95 | ||
|
|
3fbaf7aeb6 | ||
|
|
3bc95e1317 | ||
|
|
cab6b2633e |
@@ -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
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ wechat:
|
||||
host: 10.0.0.73:19088
|
||||
# 是否在启动的时候自动设置hook服务的回调
|
||||
autoSetCallback: false
|
||||
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port
|
||||
# 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port
|
||||
callback: 10.0.0.51
|
||||
# 转发到其他地址
|
||||
forward:
|
||||
@@ -50,3 +50,7 @@ resource:
|
||||
welcome-new:
|
||||
type: emotion
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
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 (
|
||||
"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 // 是否中断消息传递
|
||||
}
|
||||
@@ -16,8 +16,8 @@ wechat:
|
||||
host: wechat:19088
|
||||
# 是否在启动的时候自动设置hook服务的回调
|
||||
autoSetCallback: true
|
||||
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port,如果使用项目提供的docker-compsoe.yaml文件启动,可以不写
|
||||
callback:
|
||||
# 回调IP,如果是Docker运行,本参数必填,如果Docker修改了映射,格式为 ip:port,如果使用项目提供的docker-compsoe.yaml文件启动,可以填`auto`
|
||||
callback: auto
|
||||
|
||||
# 数据库
|
||||
mysql:
|
||||
|
||||
@@ -4,12 +4,18 @@ import (
|
||||
"go-wechat/client"
|
||||
"go-wechat/entity"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SaveMessage
|
||||
// @description: 消息入库
|
||||
// @param msg
|
||||
func SaveMessage(msg entity.Message) {
|
||||
if flag, _ := strconv.ParseBool(os.Getenv("DONT_SAVE")); flag {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查消息是否存在,存在就跳过
|
||||
var count int64
|
||||
err := client.MySQL.Model(&entity.Message{}).Where("msg_id = ?", msg.MsgId).Count(&count).Error
|
||||
@@ -18,6 +24,7 @@ func SaveMessage(msg entity.Message) {
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
//log.Printf("消息已存在,消息Id: %d", msg.MsgId)
|
||||
return
|
||||
}
|
||||
err = client.MySQL.Create(&msg).Error
|
||||
|
||||
@@ -2,6 +2,7 @@ package watergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/config"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -21,11 +22,17 @@ func Month() {
|
||||
for _, group := range groups {
|
||||
// 消息统计
|
||||
dealMonth(group.Wxid)
|
||||
|
||||
res, ok := config.Conf.Resource["wordcloud"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取上个月月份
|
||||
yd := time.Now().Local().AddDate(0, 0, -1).Format("200601")
|
||||
// 发送词云
|
||||
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 (
|
||||
"fmt"
|
||||
"go-wechat/config"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -21,11 +22,17 @@ func Week() {
|
||||
for _, group := range groups {
|
||||
// 消息统计
|
||||
dealWeek(group.Wxid)
|
||||
|
||||
res, ok := config.Conf.Resource["wordcloud"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取上周周数
|
||||
year, weekNo := time.Now().Local().AddDate(0, 0, -1).ISOWeek()
|
||||
// 发送词云
|
||||
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 (
|
||||
"fmt"
|
||||
"go-wechat/config"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -23,11 +24,17 @@ func Yesterday() {
|
||||
for _, group := range groups {
|
||||
// 消息统计
|
||||
dealYesterday(group.Wxid)
|
||||
|
||||
res, ok := config.Conf.Resource["wordcloud"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取昨日日期
|
||||
yd := time.Now().Local().AddDate(0, 0, -1).Format("20060102")
|
||||
// 发送词云
|
||||
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 (
|
||||
"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)
|
||||
}
|
||||
@@ -29,9 +29,13 @@ func ClearCallback() {
|
||||
// @param host
|
||||
func SetCallback(userHost string) {
|
||||
// 获取本机IP地址
|
||||
host := net.ParseIP(netutil.GetInternalIp()).String()
|
||||
host := userHost
|
||||
if userHost == "auto" {
|
||||
host = net.ParseIP(netutil.GetInternalIp()).String()
|
||||
}
|
||||
|
||||
port := 19099
|
||||
if userHost != "" {
|
||||
if userHost != "" && userHost != "auto" {
|
||||
uh := strings.Split(strings.TrimSpace(userHost), ":")
|
||||
host = uh[0]
|
||||
if len(uh) == 2 {
|
||||
|
||||
Reference in New Issue
Block a user