From 038384851bd89ff31e68ab6a79545f552e8593e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AF=BB=E6=AC=A2?= Date: Mon, 19 Aug 2024 16:27:29 +0800 Subject: [PATCH] =?UTF-8?q?:fire:=20=E9=87=8D=E5=86=99=E9=80=9A=E8=AE=AF?= =?UTF-8?q?=E5=BD=95=E5=92=8C=E7=BE=A4=E6=88=90=E5=91=98=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yaml | 4 +- initialization/plugin.go | 3 +- model/dto/message.go | 11 ++ plugin/plugins/notify2configuser.go | 37 ++-- plugin/plugins/systemmessgae.go | 10 ++ plugin/plugins/welconenew.go | 6 +- service/friendsync.go | 257 ++++++++++++++++++++++++++++ utils/friend.go | 105 ++++++++++++ 8 files changed, 410 insertions(+), 23 deletions(-) create mode 100644 service/friendsync.go create mode 100644 utils/friend.go diff --git a/config.yaml b/config.yaml index 9134afcf..fa6ca149 100644 --- a/config.yaml +++ b/config.yaml @@ -61,9 +61,9 @@ task: hotTop: enable: true cron: '0 */1 * * *' # 每小时一次 - syncFriends: + syncFriends: # 逻辑已修改,每天大半夜同步一次即可(主要是用以修正数据),平时会根据收到的消息针对性同步 enable: false - cron: '*/5 * * * *' # 五分钟一次 + cron: '0 4 * * *' # 每天4:00 groupSummary: enable: false cron: '30 0 * * *' # 每天0:30 diff --git a/initialization/plugin.go b/initialization/plugin.go index 68926135..62946f9a 100644 --- a/initialization/plugin.go +++ b/initialization/plugin.go @@ -6,7 +6,6 @@ import ( plugin "go-wechat/plugin" "go-wechat/plugin/plugins" "go-wechat/service" - "go-wechat/types" ) // Plugin @@ -31,7 +30,7 @@ func Plugin() { }, plugins.NotifyInvitationJoinGroup) // 被移除群聊通知到配置用户 dispatcher.RegisterHandler(func(m *dto.Message) bool { - return m.Type == types.MsgTypeSys + return m.IsRemoveFromChatroom() }, plugins.NotifyRemoveFromChatroom) // 响应好友添加成功消息 dispatcher.RegisterHandler(func(m *dto.Message) bool { diff --git a/model/dto/message.go b/model/dto/message.go index 5ccf4b40..487c5350 100644 --- a/model/dto/message.go +++ b/model/dto/message.go @@ -252,3 +252,14 @@ func (m Message) IsJoinToGroup() (flag bool) { } return strings.Contains(m.Content, "\"邀请你加入了群聊,群聊参与人还有:") } + +// IsRemoveFromChatroom +// @description: 是否是被移出群聊消息 +// @receiver m +// @return flag +func (m Message) IsRemoveFromChatroom() (flag bool) { + if m.Type != types.MsgTypeSys { + return + } + return strings.HasPrefix(m.Content, "你被\"") && strings.HasSuffix(m.Content, "\"移出群聊") +} diff --git a/plugin/plugins/notify2configuser.go b/plugin/plugins/notify2configuser.go index dd7f40e9..10bbfd12 100644 --- a/plugin/plugins/notify2configuser.go +++ b/plugin/plugins/notify2configuser.go @@ -2,11 +2,12 @@ package plugins import ( "fmt" + "go-wechat/client" "go-wechat/config" + "go-wechat/model/entity" "go-wechat/plugin" "go-wechat/service" "go-wechat/utils" - "strings" ) // NotifyInvitationJoinGroup @@ -14,7 +15,7 @@ import ( // @param m func NotifyInvitationJoinGroup(m *plugin.MessageContext) { // 先回复一条固定句子 - utils.SendMessage(m.FromUser, m.GroupUser, "您的邀请消息已收到啦,正在通知我的主人来同意请求。在我加群之后将会进行初始化操作,直到收到我主动发送的消息就是初始化完成咯,在那之前请耐心等待喔~", 0) + _ = utils.SendMessage(m.FromUser, m.GroupUser, "您的邀请消息已收到啦,正在通知我的主人来同意请求。在我加群之后将会进行初始化操作,直到收到我主动发送的消息就是初始化完成咯,在那之前请耐心等待喔~", 0) // 如果是邀请进群,推送到配置的用户 if flag, dec := m.IsInvitationJoinGroup(); flag { @@ -32,24 +33,24 @@ func NotifyInvitationJoinGroup(m *plugin.MessageContext) { // @description: 被移除群聊通知到配置用户 // @param m func NotifyRemoveFromChatroom(m *plugin.MessageContext) { - // 如果是被移出群聊,推送到配置的用户 - if strings.HasPrefix(m.Content, "你被\"") && strings.HasSuffix(m.Content, "\"移出群聊") { - // 调用一下退出群聊接口,防止被移出后还能从好友列表接口拉到相关信息 - utils.QuitChatroom(m.FromUser, 0) + // 调用一下退出群聊接口,防止被移出后还能从好友列表接口拉到相关信息 + utils.QuitChatroom(m.FromUser, 0) - // 取出群名称 - groupInfo, err := service.GetFriendInfoById(m.FromUser) - if err != nil { - return - } - // 组装消息 - msg := fmt.Sprintf("#移除群聊提醒\n\n群Id: %s\n群名称: %s\n事件: %s", m.FromUser, groupInfo.Nickname, m.Content) + // 取出群名称 + groupInfo, err := service.GetFriendInfoById(m.FromUser) + if err != nil { + return + } + // 组装消息 + msg := fmt.Sprintf("#移除群聊提醒\n\n群Id: %s\n群名称: %s\n事件: %s", m.FromUser, groupInfo.Nickname, m.Content) - for _, user := range config.Conf.System.NewFriendNotify.ToUser { - if user != "" { - // 发送一条新消息 - utils.SendMessage(user, "", msg, 0) - } + for _, user := range config.Conf.System.NewFriendNotify.ToUser { + if user != "" { + // 发送一条新消息 + _ = utils.SendMessage(user, "", msg, 0) } } + + // 修改is_ok状态 + client.MySQL.Model(&entity.Friend{}).Where("wxid = ?", m.FromUser).Update("is_ok", false) } diff --git a/plugin/plugins/systemmessgae.go b/plugin/plugins/systemmessgae.go index c1c99d4b..0b669d88 100644 --- a/plugin/plugins/systemmessgae.go +++ b/plugin/plugins/systemmessgae.go @@ -2,6 +2,7 @@ package plugins import ( "go-wechat/plugin" + "go-wechat/service" "go-wechat/utils" ) @@ -15,4 +16,13 @@ func ReplyNewFriend(m *plugin.MessageContext) { if m.IsOldFriendBack() { _ = utils.SendMessage(m.FromUser, m.GroupUser, "嘿,我的朋友,你为何要离我而去?又为何去而复返?", 0) } + + go func() { + // 刷新好友列表 + service.SyncFriend() + // 如果是加入群,刷新群成员列表 + if m.IsJoinToGroup() { + service.SyncGroupMembers(m.FromUser) + } + }() } diff --git a/plugin/plugins/welconenew.go b/plugin/plugins/welconenew.go index b0865418..ddf29c3d 100644 --- a/plugin/plugins/welconenew.go +++ b/plugin/plugins/welconenew.go @@ -5,6 +5,7 @@ import ( "go-wechat/config" "go-wechat/model/entity" "go-wechat/plugin" + "go-wechat/service" "go-wechat/utils" ) @@ -28,7 +29,7 @@ func WelcomeNew(m *plugin.MessageContext) { switch conf.Type { case "text": // 文字类型 - utils.SendMessage(m.FromUser, "", conf.Path, 0) + _ = utils.SendMessage(m.FromUser, "", conf.Path, 0) case "image": // 图片类型 utils.SendImage(m.FromUser, conf.Path, 0) @@ -36,4 +37,7 @@ func WelcomeNew(m *plugin.MessageContext) { // 表情类型 utils.SendEmotion(m.FromUser, conf.Path, 0) } + + // 刷新群成员列表 + go service.SyncGroupMembers(m.FromUser) } diff --git a/service/friendsync.go b/service/friendsync.go new file mode 100644 index 00000000..70131e4f --- /dev/null +++ b/service/friendsync.go @@ -0,0 +1,257 @@ +package service + +import ( + "go-wechat/client" + "go-wechat/common/types" + "go-wechat/config" + "go-wechat/model/entity" + "go-wechat/utils" + "log" + "strings" + "time" +) + +// SyncFriend +// @description: 同步好友列表 +func SyncFriend() { + // 获取好友列表 + friends, err := utils.GetFriendList() + if err != nil { + log.Printf("获取好友列表失败: %s", err.Error()) + return + } + // 当前获取到的成员Id,用于后续设置is_ok状态 + nowIds := make([]string, 0) + // 取出已存在的成员信息 + var oldData []entity.Friend + err = client.MySQL.Find(&oldData).Error + if err != nil { + log.Printf("查询好友列表失败: %s", err.Error()) + return + } + // 将历史数据整理成map + oldMap := make(map[string]bool) + for _, item := range oldData { + oldMap[item.Wxid] = item.IsOk + } + + // 新增的成员,用于通知给指定的人 + var notifyMap = make(map[string]string) + + // 开启事务 + tx := client.MySQL.Begin() + defer tx.Commit() + + // 循环获取到的好友列表 + for _, item := range friends { + // 填充当前存在的账号 + nowIds = append(nowIds, item.Wxid) + + // 判断是否已经存在 + if _, ok := oldMap[item.Wxid]; ok { + // 已存在,修改 + pm := map[string]any{ + "nickname": item.Nickname, + "custom_account": item.CustomAccount, + "pinyin": item.Pinyin, + "pinyin_all": item.PinyinAll, + "is_ok": true, + } + err = tx.Model(&entity.Friend{}).Where("wxid = ?", item.Wxid).Updates(pm).Error + if err != nil { + log.Printf("修改好友失败: %s", err.Error()) + continue + } + + // 如果已存在但是是已退出的群,也通知一下 + if !oldMap[item.Wxid] { + notifyMap[item.Wxid] = item.Nickname + " #秽土转生" + + // 通知一下,初始化完成 + if conf, e := config.Conf.Resource["introduce"]; e { + // 发送一条新消息 + switch conf.Type { + case "text": + // 文字类型 + _ = utils.SendMessage(item.Wxid, "", conf.Path, 0) + case "image": + // 图片类型 + utils.SendImage(item.Wxid, conf.Path, 0) + case "emotion": + // 表情类型 + utils.SendEmotion(item.Wxid, conf.Path, 0) + } + } + // 发送配置网页 + if config.Conf.System.Domain != "" { + title := "欢迎使用微信机器人(切勿转发)" + desc := "点我可以配置功能喔,提示非微信官方网页,点击继续访问即可" + url := utils.GetManagerUrl(item.Wxid) + utils.SendPublicMsg(item.Wxid, title, desc, url, 0) + } + } + } else { + // 新增 + err = tx.Create(&entity.Friend{ + CustomAccount: item.CustomAccount, + Nickname: item.Nickname, + Pinyin: item.Pinyin, + PinyinAll: item.PinyinAll, + Wxid: item.Wxid, + IsOk: true, + EnableAi: config.Conf.System.DefaultRule.Ai, + EnableChatRank: config.Conf.System.DefaultRule.ChatRank, + EnableSummary: config.Conf.System.DefaultRule.Summary, + EnableWelcome: config.Conf.System.DefaultRule.Welcome, + EnableNews: config.Conf.System.DefaultRule.News, + EnableHotTop: config.Conf.System.DefaultRule.HotTop, + AiFreeLimit: config.Conf.System.DefaultRule.AiFreeLimit, + ClearMember: 0, + LastActive: types.DateTime(time.Now().Local()), + }).Error + if err != nil { + log.Printf("新增好友失败: %s", err.Error()) + continue + } + notifyMap[item.Wxid] = item.Nickname + if conf, e := config.Conf.Resource["introduce"]; e { + // 发送一条新消息 + switch conf.Type { + case "text": + // 文字类型 + _ = utils.SendMessage(item.Wxid, "", conf.Path, 0) + case "image": + // 图片类型 + utils.SendImage(item.Wxid, conf.Path, 0) + case "emotion": + // 表情类型 + utils.SendEmotion(item.Wxid, conf.Path, 0) + } + } + // 发送配置网页 + if config.Conf.System.Domain != "" { + title := "欢迎使用微信机器人(切勿转发)" + desc := "点我可以配置功能喔,提示非微信官方网页,点击继续访问即可" + url := utils.GetManagerUrl(item.Wxid) + utils.SendPublicMsg(item.Wxid, title, desc, url, 0) + } + } + } + + // 通知有新成员 + if len(notifyMap) > 0 && config.Conf.System.NewFriendNotify.Enable { + // 组装成一句话 + msg := []string{"#新好友通知\n"} + for wxId, nickname := range notifyMap { + msg = append(msg, "微信Id: "+wxId+"\n昵称: "+nickname) + } + for _, user := range config.Conf.System.NewFriendNotify.ToUser { + if user != "" { + // 发送一条新消息 + _ = utils.SendMessage(user, "", strings.Join(msg, "\n-------\n"), 0) + } + } + } + + // 清理不在列表中的好友 + clearPm := map[string]any{ + "is_ok": false, + } + err = tx.Model(&entity.Friend{}).Where("wxid NOT IN (?)", nowIds).Updates(clearPm).Error + if err != nil { + log.Printf("清理好友失败: %s", err.Error()) + } + + log.Println("同步好友列表完成") +} + +// SyncGroupMembers +// @description: 同步群成员 +// @param wxId +func SyncGroupMembers(wxId string) { + membersIds, admin, err := utils.GetGroupMembers(wxId) + if err != nil { + return + } + + // 修改不在数组的群成员状态为不在 + pm := map[string]any{ + "is_member": false, + "leave_time": time.Now().Local(), + } + err = client.MySQL.Model(&entity.GroupUser{}). + Where("group_id = ?", wxId). + Where("is_member IS TRUE"). + Where("wxid NOT IN (?)", membersIds). + Updates(pm).Error + if err != nil { + log.Printf("修改群成员状态失败: %s", err.Error()) + return + } + + // 取出当前数据库存在的成员信息 + var oldData []entity.GroupUser + err = client.MySQL.Model(&entity.GroupUser{}). + Where("group_id = ?", wxId). + Find(&oldData).Error + if err != nil { + log.Printf("查询群成员失败: %s", err.Error()) + return + } + // 将历史数据整理成map + oldMap := make(map[string]bool) + for _, item := range oldData { + oldMap[item.Wxid] = item.IsMember + } + + // 循环获取到的群成员列表 + for _, wxid := range membersIds { + // 如果历史数据中存在,且是成员,跳过 + if isMember, ok := oldMap[wxid]; ok && isMember { + continue + } + // 获取成员信息 + cp, e := utils.GetContactProfile(wxid) + if e != nil { + log.Printf("获取成员信息失败: %s", e.Error()) + continue + } + if cp.Wxid != "" { + if _, ok := oldMap[wxid]; ok { + // 历史数据中存在,修改 + // 修改 + gupm := map[string]any{ + "account": cp.Account, + "head_image": cp.HeadImage, + "nickname": cp.Nickname, + "is_member": true, + "is_admin": cp.Wxid == admin, + "leave_time": nil, + } + err = client.MySQL.Model(&entity.GroupUser{}).Where("group_id = ?", wxId).Where("wxid = ?", wxid).Updates(gupm).Error + if err != nil { + log.Printf("修改群成员失败: %s", err.Error()) + continue + } + } else { + // 新增的 + // 新增 + err = client.MySQL.Create(&entity.GroupUser{ + GroupId: wxid, + Account: cp.Account, + HeadImage: cp.HeadImage, + Nickname: cp.Nickname, + Wxid: cp.Wxid, + IsMember: true, + IsAdmin: cp.Wxid == admin, + JoinTime: time.Now().Local(), + LastActive: time.Now().Local(), + }).Error + if err != nil { + log.Printf("新增群成员失败: %s", err.Error()) + continue + } + } + } + } +} diff --git a/utils/friend.go b/utils/friend.go new file mode 100644 index 00000000..ed37f280 --- /dev/null +++ b/utils/friend.go @@ -0,0 +1,105 @@ +package utils + +import ( + "encoding/json" + "github.com/go-resty/resty/v2" + "go-wechat/common/constant" + "go-wechat/config" + "go-wechat/model/dto" + "log" + "slices" + "strings" +) + +// http客户端 +var hc = resty.New() + +// GetFriendList +// @description: 获取好友列表 +// @return friends +// @return err +func GetFriendList() (friends []dto.FriendItem, err error) { + var base dto.Response[[]dto.FriendItem] + resp, err := hc.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetResult(&base). + Post(config.Conf.Wechat.GetURL("/api/getContactList")) + if err != nil { + log.Printf("获取好友列表失败: %s", err.Error()) + return + } + log.Printf("获取好友列表结果: %s", resp.String()) + + // 循环获取到的好友列表 + for _, item := range base.Data { + // 跳过特殊账号 + // 跳过公众号和企业微信好友 + if strings.Contains(item.Wxid, "gh_") || strings.Contains(item.Wxid, "@openim") { + continue + } + // 特殊Id跳过 + if slices.Contains(constant.SpecialId, item.Wxid) { + continue + } + // 添加到待返回列表 + friends = append(friends, item) + } + + return +} + +// GetGroupMembers +// @description: 获取指定群成员 +// @param wxId 群Id +// @return ids 群成员id数组 +// @return members 群成员信息 +// @return err 错误信息 +func GetGroupMembers(wxId string) (ids []string, admin string, err error) { + var base dto.Response[dto.GroupUser] + // 组装参数 + param := map[string]any{ + "chatRoomId": wxId, // 群Id + } + pbs, _ := json.Marshal(param) + + _, err = hc.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(&base). + Post(config.Conf.Wechat.GetURL("/api/getMemberFromChatRoom")) + if err != nil { + log.Printf("获取群成员信息失败: %s", err.Error()) + return + } + // 昵称Id + ids = strings.Split(base.Data.Members, "^G") + admin = base.Data.Admin + return +} + +// GetContactProfile +// @description: 获取联系人信息 +// @param wxId 微信Id +// @return info 联系人信息 +// @return err 错误信息 +func GetContactProfile(wxId string) (info dto.ContactProfile, err error) { + var baseResp dto.Response[dto.ContactProfile] + + // 组装参数 + param := map[string]any{ + "wxid": wxId, // 群Id + } + pbs, _ := json.Marshal(param) + + _, err = hc.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(&baseResp). + Post(config.Conf.Wechat.GetURL("/api/getContactProfile")) + if err != nil { + log.Printf("获取成员详情失败: %s", err.Error()) + return + } + info = baseResp.Data + return +}