first commit

This commit is contained in:
2026-02-17 13:06:23 +08:00
commit 7cbd3d061d
349 changed files with 126558 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
package wxtask
import (
"xiawan/wx/protobuf/wechat"
)
// MessageWrapper 消息包装结构体,用于WebSocket和callback消息同步时包含UUID信息
type MessageWrapper struct {
Key string `json:"key"` // UUID标识,用于区分是哪个微信收到的消息
Message *wechat.AddMsg `json:"message"` // 原始消息内容
Type string `json:"type"` // 消息类型,如"message"、"contact_sync"等
}
// NewMessageWrapper 创建新的消息包装器
func NewMessageWrapper(key string, message *wechat.AddMsg, msgType string) *MessageWrapper {
return &MessageWrapper{
Key: key,
Message: message,
Type: msgType,
}
}
+469
View File
@@ -0,0 +1,469 @@
package wxtask
import (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/srv/wxface"
)
// WXFriendTask 微信好友任务
type WXFriendTask struct {
wxConn wxface.IWXConnect
friendForList []*wechat.ModContact
friendForZombieFansList []*wechat.ModContact
labels []*wechat.LabelPair
waitTimes uint32 // 毫秒
isZombieFansDet bool // 检测僵死粉任务
isZombieFansDel bool // 删除僵死粉任务
}
// NewWXFriendTask 新建一个好友任务
func NewWXFriendTask(wxConn wxface.IWXConnect) *WXFriendTask {
return &WXFriendTask{
wxConn: wxConn,
friendForList: make([]*wechat.ModContact, 0),
friendForZombieFansList: make([]*wechat.ModContact, 0),
labels: make([]*wechat.LabelPair, 0),
isZombieFansDet: false,
isZombieFansDel: false,
waitTimes: uint32(1000), // 间隔 1000 毫秒
}
}
// 获取下一个待验证的好友
func (gpt *WXFriendTask) getNextFriendForContact() *wechat.ModContact {
if len(gpt.friendForList) <= 0 {
return nil
}
retContact := gpt.friendForList[0]
gpt.friendForList = gpt.friendForList[1:]
return retContact
}
// 获取 1500 - 2500 毫秒的随机数
func (gpt *WXFriendTask) getRandWaitTimes() int {
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 生成 [1500, 2500] 之间的随机数
min := 1500
max := 2500
randomNumber := rand.Intn(max-min+1) + min
return randomNumber
}
// 获取 2000 - 4000 毫秒的随机数
func (gpt *WXFriendTask) getRandWaitTimes2() int {
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 生成 [2000, 4000] 之间的随机数
min := 2000
max := 4000
randomNumber := rand.Intn(max-min+1) + min
return randomNumber
}
// 获取 5000 - 12000 毫秒的随机数(5秒 - 12秒)
func (gpt *WXFriendTask) getRandWaitTimes3() int {
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 生成 [5000, 12000] 之间的随机数
min := 5000
max := 12000
randomNumber := rand.Intn(max-min+1) + min
return randomNumber
}
// 10 - 20秒 删除一个
// 获取 10000 - 20000 毫秒的随机数
func (gpt *WXFriendTask) getRandWaitTimes4() int {
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 生成 [10000, 20000] 之间的随机数
min := 10000
max := 20000
randomNumber := rand.Intn(max-min+1) + min
return randomNumber
}
// 添加至标签 删除我的人 拉黑我的人
func (gpt *WXFriendTask) addTagForContact(labeName string, friendWxids []string) {
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
// 判断存在则删除,不存在则添加
labels := gpt.getTagForList()
// 当前标签
currentLabel := make([]*wechat.LabelPair, 0)
for _, label := range labels {
if label.GetLabelName() == labeName {
// 删除
currentWXReqInvoker.SendDelContactLabelRequest(strconv.FormatUint(uint64(label.GetLabelId()), 10))
break
}
}
if len(currentLabel) <= 0 {
// 添加标签
labelNames := make([]string, 0)
labelNames = append(labelNames, labeName)
resp, err := currentWXReqInvoker.SendAddContactLabelRequest(labelNames, true)
if err != nil {
return
}
currentLabel = resp.GetLabelPairList()
}
// 修改标签
if len(currentLabel) > 0 {
UserLabelList := make([]baseinfo.UserLabelInfoItem, 0)
for _, friendWxid := range friendWxids {
userLabel := baseinfo.UserLabelInfoItem{
UserName: friendWxid,
LabelIDList: strconv.FormatUint(uint64(currentLabel[0].GetLabelId()), 10),
}
UserLabelList = append(UserLabelList, userLabel)
}
currentWXReqInvoker.SendModifyLabelRequest(UserLabelList)
}
}
// 获取全部标签
func (gpt *WXFriendTask) getTagForList() []*wechat.LabelPair {
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
// 获取标签
resp, err := currentWXReqInvoker.SendGetContactLabelListRequest(true)
if err != nil {
return make([]*wechat.LabelPair, 0)
}
return resp.GetLabelPairList()
}
// 获取剩余僵死粉数量
// GetLessCount 获取剩余群聊数量
func (gpt *WXFriendTask) GetLessCount() uint32 {
return uint32(len(gpt.friendForZombieFansList))
}
// StartFriendTask 开启好友检测任务
func (gpt *WXFriendTask) StartFriendTask(taskType int) bool {
// currentWXAccount := gpt.wxConn.GetWXAccount()
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
currentWXFileHelperMgr := gpt.wxConn.GetWXFileHelperMgr()
// 如果正在执行执行任务,则返回
if gpt.isZombieFansDet {
tipText := "正在检测僵死粉,请稍后"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
return false
}
// 如果正在执行删除任务,则返回
if gpt.isZombieFansDel {
tipText := "正在清理僵死粉,请稍后"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
return false
}
if taskType == 1 {
gpt.isZombieFansDet = true
tmpTip := "执行检测僵死粉\n开始加载好友列表……"
currentWXFileHelperMgr.AddNewTipMsg(tmpTip)
}
if taskType == 2 {
gpt.isZombieFansDel = true
tmpTip := "执行清理僵死粉\n开始加载好友……"
currentWXFileHelperMgr.AddNewTipMsg(tmpTip)
}
gpt.friendForList = make([]*wechat.ModContact, 0)
gpt.friendForZombieFansList = make([]*wechat.ModContact, 0)
friendWxids := gpt.GetFriendWxidList()
// friendList := make([]*wechat.ModContact, 0)
go func() {
if len(friendWxids) <= 0 {
tipText := "好友检索完成,总数 0 个,无需操作"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
gpt.isZombieFansDel = false
gpt.isZombieFansDet = false
return
}
// 好友检索完成,好友总数:
tipText := "好友加载完成!!\n开始检测好友,请耐心等待……"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
for i := 0; i < len(friendWxids); i += 1 {
// 获取详情
friend := new(wechat.ModContact)
friend.UserName = new(wechat.SKBuiltinString)
friend.Remark = new(wechat.SKBuiltinString)
friend.UserName.Str = new(string)
*friend.UserName.Str = friendWxids[i]
gpt.friendForList = append(gpt.friendForList, friend)
}
temFriendCount := 0
for {
// 先判断是否还处于链接当中
if !gpt.wxConn.IsConnected() {
gpt.isZombieFansDel = false
gpt.isZombieFansDet = false
return
}
// 获取下一个好友
tmpModContact := gpt.getNextFriendForContact()
temFriendCount++
if tmpModContact == nil {
zembieFansCount := len(gpt.friendForZombieFansList)
friendForZombieFansList := make([]*wechat.ModContact, 0)
friendNamesDel := ""
friendWxidsDel := make([]string, 0)
// 拉黑我的人
friendFroBlack := ""
friendWxidsBlack := make([]string, 0)
if zembieFansCount > 0 {
// jsonStr0, _ := json.Marshal(gpt.friendForList)
// fmt.Println(string(jsonStr0))
for i := 0; i < zembieFansCount; i += 20 {
end := i + 20
if end > len(gpt.friendForZombieFansList) {
end = len(gpt.friendForZombieFansList)
}
oldFriends := gpt.friendForZombieFansList[i:end]
friendWxids := make([]string, 0)
for _, tmpFriend := range oldFriends {
friendWxids = append(friendWxids, tmpFriend.GetUserName().GetStr())
}
// 获取详情
contactList, err := currentWXReqInvoker.SendGetContactRequestForList(friendWxids, nil)
if err != nil {
fmt.Println("获取好友详情失败:", err)
continue
}
// 先判断是否还处于链接当中
if !gpt.wxConn.IsConnected() {
gpt.isZombieFansDel = false
gpt.isZombieFansDet = false
return
}
// friends := contactList.GetContactList()
verifyUserList := contactList.GetVerifyUserValidTicketList()
friends := contactList.GetContactList()
// 转 json 并写入文件
// jsonStr, _ := json.Marshal(friends)
// fmt.Println(string(jsonStr))
// jsonStr2, _ := json.Marshal(verifyUserList)
// fmt.Println(string(jsonStr2))
count := len(friends)
for index := 0; index < count; index++ {
friendItem := friends[index]
verifyUserItem := verifyUserList[index]
CardWxId := friendItem.GetUserName().GetStr()
CardNickName := friendItem.GetNickName().GetStr()
// CardAlias := friendItem.GetAlias()
// 联系人
// 如果是僵死粉
if strings.HasPrefix(verifyUserItem.GetAntispamticket(), "v4_") && friendItem.GetHeadImgMd5() != "" {
if friendNamesDel != "" {
friendNamesDel += "\n"
}
friendItem.Remark = new(wechat.SKBuiltinString)
friendItem.Remark.Str = new(string)
*friendItem.Remark.Str = "删除我的人"
friendNamesDel += "删除我的人"
friendNamesDel += ("【" + CardNickName + "】")
friendWxidsDel = append(friendWxidsDel, CardWxId)
// baseutils.PrintLog("僵尸粉:" + tmpContact.GetNickName().GetStr())
} else {
// 拉黑
if friendFroBlack != "" {
friendFroBlack += "\n"
}
friendItem.Remark = new(wechat.SKBuiltinString)
friendItem.Remark.Str = new(string)
*friendItem.Remark.Str = "拉黑我的人"
friendFroBlack += "拉黑我的人"
friendFroBlack += ("【" + CardNickName + "】")
friendWxidsBlack = append(friendWxidsBlack, CardWxId)
}
friendForZombieFansList = append(friendForZombieFansList, friendItem)
}
// 暂停 1.5 - 2.5 秒
randWaitTime := gpt.getRandWaitTimes()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
}
}
// 添加到标签
// 发送名单列表
if friendNamesDel != "" {
gpt.addTagForContact("删除我的人", friendWxidsDel)
currentWXFileHelperMgr.AddNewTipMsg(friendNamesDel)
}
if friendFroBlack != "" {
gpt.addTagForContact("拉黑我的人", friendWxidsBlack)
currentWXFileHelperMgr.AddNewTipMsg(friendFroBlack)
}
tipText := "检测完成 \n"
tipText += "删除我的人 - 【" + strconv.Itoa(len(friendWxidsDel)) + "】个\n"
tipText += "拉黑我的人 - 【" + strconv.Itoa(len(friendWxidsBlack)) + "】个\n"
tipText += "僵死粉已标记到标签中,以便查看"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
gpt.isZombieFansDet = false
if gpt.isZombieFansDel {
tipText := "由于官方限制,删除好友所需时间较长,请耐心等待……,请勿退出 Ipad 登录!!"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
// 暂停 1 - 2 分钟
randWaitTime := gpt.getRandWaitTimes3()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
// 删除僵死粉
for _, friendWxid := range friendWxidsDel {
// 删除
err := currentWXReqInvoker.SendDelContactRequest(friendWxid)
if err != nil {
gpt.isZombieFansDet = false
gpt.isZombieFansDel = false
fmt.Printf("删除好友 del friend err %+v", err)
return
}
for _, friendRemak := range friendForZombieFansList {
if friendRemak.GetUserName().GetStr() == friendWxid {
tipText := "删除-删除我的人【" + friendRemak.GetNickName().GetStr() + "】"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
}
}
// 暂停 10s - 20s
randWaitTime := gpt.getRandWaitTimes4()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
}
for _, friendWxid := range friendWxidsBlack {
// 删除
err := currentWXReqInvoker.SendDelContactRequest(friendWxid)
if err != nil {
gpt.isZombieFansDet = false
gpt.isZombieFansDel = false
fmt.Printf("删除好友 del friend err %+v", err)
return
}
for _, friendRemak := range friendForZombieFansList {
if friendRemak.GetUserName().GetStr() == friendWxid {
tipText := "删除-拉黑我的人【" + friendRemak.GetNickName().GetStr() + "】"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
}
}
// 暂停 2 - 5 分钟
randWaitTime := gpt.getRandWaitTimes3()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
}
gpt.isZombieFansDet = false
gpt.isZombieFansDel = false
tipText = "清理完成【已清理】:" + strconv.Itoa(len(friendWxidsDel)+len(friendWxidsBlack))
currentWXFileHelperMgr.AddNewTipMsg(tipText)
}
return
}
// 判断每次超过 25 个好友,则返回%分比进度
if temFriendCount%25 == 0 {
// 取整 temFriendCount / len(friendWxids) * 100 去掉小数点
numCount := int(float64(temFriendCount) / float64(len(friendWxids)) * 100)
tipText := "已检测【" + strconv.Itoa(numCount) + "%】好友"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
}
// 获取好友关系请求
friendUserName := tmpModContact.GetUserName().GetStr()
// 微信团队跳过
if friendUserName == "Weixin" {
continue
}
// 判断是否是僵死粉
resp, err := currentWXReqInvoker.SendGetFriendRelationRequest(friendUserName)
if err != nil {
continue
}
if resp.GetFriendRelation() == 0 {
continue
}
// 1:删除 4:自己拉黑 5:被拉黑
if resp.GetFriendRelation() == 1 {
tmpModContact.Remark.Str = new(string)
*tmpModContact.Remark.Str = "删除我的人"
// 添加到僵尸粉列表
gpt.friendForZombieFansList = append(gpt.friendForZombieFansList, tmpModContact)
}
if resp.GetFriendRelation() == 5 {
tmpModContact.Remark.Str = new(string)
*tmpModContact.Remark.Str = "拉黑我的人"
// 添加到僵尸粉列表
gpt.friendForZombieFansList = append(gpt.friendForZombieFansList, tmpModContact)
}
// 暂停 1.5 - 2.5 秒
randWaitTime := gpt.getRandWaitTimes()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
}
}()
return true
}
// 获取联系人
func (gpt *WXFriendTask) GetFriendWxidList() []string {
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
currentWXFileHelperMgr := gpt.wxConn.GetWXFileHelperMgr()
CurrentWxcontactSeq := uint32(0)
CurrentChatRoomContactSeq := uint32(0)
friendUserNames := make([]string, 0)
pages := 0
// 死循环
for {
pages++
resp, err := currentWXReqInvoker.SendGetContactListPageRequest(CurrentWxcontactSeq, CurrentChatRoomContactSeq)
if err != nil {
return friendUserNames
}
UserNameList := resp.GetContactUsernameList()
if resp.GetContactUsernameList() == nil {
return friendUserNames
}
if len(UserNameList) == 0 {
return friendUserNames
}
tipText := "正在加载第 " + strconv.Itoa(pages) + " 页"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
for _, contactUserName := range UserNameList {
if contactUserName == "weixin" ||
contactUserName == "fmessage" ||
contactUserName == "medianote" ||
contactUserName == "floatbottle" ||
contactUserName == "qqmail" ||
contactUserName == "filehelper" ||
contactUserName == "mphelper" ||
contactUserName == "qmessage" {
continue
}
// 判断 gh 开头
if strings.HasPrefix(contactUserName, "gh_") {
continue
}
// 判断 @chatroom 结尾
if strings.HasSuffix(contactUserName, "@chatroom") {
continue
}
friendUserNames = append(friendUserNames, contactUserName)
}
CurrentWxcontactSeq = resp.GetCurrentWxcontactSeq()
CurrentChatRoomContactSeq = resp.GetCurrentChatRoomContactSeq()
// 暂停 1.5 - 2.5 秒
randWaitTime := gpt.getRandWaitTimes()
time.Sleep(time.Duration(randWaitTime) * time.Millisecond)
}
}
+357
View File
@@ -0,0 +1,357 @@
package wxtask
import (
"sync"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/db"
"xiawan/wx/srv/wxface"
)
// WXGrabHBTask 抢红包管理器
type WXGrabHBTask struct {
wxConn wxface.IWXConnect
// 待领红包项列表
hongBaoItemChan chan *baseinfo.HongBaoItem
// 可不可以抢红包
canDoNext chan bool
// 结束标志
endChan chan bool
// 是否正在运行
isRunning bool
// 当前在抢的红包
currentHBItem *baseinfo.HongBaoItem
// autoGrap
autoGropFlag bool
//延迟抢红包
delayOpen int
//是否自动回复感谢
autoReply bool
//感谢内容
autoReplyContent string
//是否过滤测挂
filterCheats bool
// 过滤测挂关键字
filterCheatsKey string
//是否过滤文字/字母包
filterText bool
// 接收个人红包
autoPerson bool
// 接收转账
autoTransfer bool
//接收转账后是否自动回复
autoTransferReply bool
//接收转账后自动回复的内容
autoTransferReplyContent string
//群是否置顶
topGroup map[string]string
// 红包统计开关
hbStat bool
// 转账统计开关
transStat bool
// 是否只抢置顶群(false 不抢置顶群, true 只抢置顶群)
notTopGroup bool
mu sync.Mutex // 互斥锁
// 是否开启
isStart bool
}
// NewWXGrabHBTask 新建抢红包管理器
func NewWXGrabHBTask(wxConn wxface.IWXConnect) *WXGrabHBTask {
return &WXGrabHBTask{
wxConn: wxConn,
hongBaoItemChan: make(chan *baseinfo.HongBaoItem, 200),
canDoNext: make(chan bool, 1),
endChan: make(chan bool, 1),
isRunning: false,
autoGropFlag: false,
delayOpen: 0,
autoReply: true,
autoReplyContent: "",
filterCheats: false,
filterText: false,
autoPerson: false,
autoTransfer: false,
filterCheatsKey: "测,挂,cg,死,罚,踢,专",
autoTransferReply: true,
autoTransferReplyContent: "",
topGroup: make(map[string]string),
hbStat: false,
transStat: false,
notTopGroup: false,
mu: sync.Mutex{},
isStart: false,
}
}
func (ghbm *WXGrabHBTask) grapHB() {
for {
select {
case ghbm.currentHBItem = <-ghbm.hongBaoItemChan:
// 直接发送抢红包请求,不等待响应
go ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(ghbm.currentHBItem)
// 立即准备抢下一个红包,不等待当前红包处理完成
ghbm.GrapNext()
return
case <-ghbm.endChan:
//ghbm.endChan <- true
//close(ghbm.endChan)
return
}
}
}
// startGrap 开始抢红包
func (ghbm *WXGrabHBTask) startGrap() {
for {
select {
case <-ghbm.canDoNext:
ghbm.grapHB()
case <-ghbm.endChan:
return
}
}
}
// Start 开始抢红包协程
func (ghbm *WXGrabHBTask) Start() {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
if ghbm.isStart {
return
}
ghbm.isStart = true
go ghbm.startGrap()
ghbm.canDoNext <- true
}
// Stop 结束抢红包协程
func (ghbm *WXGrabHBTask) Stop() {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
if !ghbm.isStart {
return
}
ghbm.isStart = false
ghbm.endChan <- true
//close(ghbm.endChan)
}
// GrapNext 抢下一个红包
func (ghbm *WXGrabHBTask) GrapNext() {
ghbm.canDoNext <- true
}
// AddHBItem 添加红包项
func (ghbm *WXGrabHBTask) AddHBItem(hbItem *baseinfo.HongBaoItem) {
// 抢红包操作
// ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(hbItem)
ghbm.hongBaoItemChan <- hbItem
if !ghbm.isStart {
ghbm.Start()
}
}
// 直接接收转账
func (ghbm *WXGrabHBTask) DirectTransfer(transferItem *baseinfo.TenPayReqItem, wcPayInfo *baseinfo.WCPayInfo2) {
ghbm.wxConn.GetWXReqInvoker().SendReceiveZZ(transferItem, wcPayInfo)
}
// GetCurrentHBItem 获取当前正在抢的红包项
func (ghbm *WXGrabHBTask) GetCurrentHBItem() *baseinfo.HongBaoItem {
return ghbm.currentHBItem
}
// IsAutoGrap 是否开启了自动抢红包功能
func (ghbm *WXGrabHBTask) IsAutoGrap() bool {
return ghbm.autoGropFlag
}
func (ghbm *WXGrabHBTask) IsFilterCheats() bool {
return ghbm.filterCheats
}
func (ghbm *WXGrabHBTask) IsFilterText() bool {
return ghbm.filterText
}
// 是否自动接收个人红包
func (ghbm *WXGrabHBTask) IsAutoPerson() bool {
return ghbm.autoPerson
}
// 是否自动接收转账
func (ghbm *WXGrabHBTask) IsAutoTransfer() bool {
return ghbm.autoTransfer
}
// 设置是否自动接收个人红包
func (ghbm *WXGrabHBTask) SetAutoPerson(bFlag bool) {
ghbm.autoPerson = bFlag
}
// 设置是否自动接收转账
func (ghbm *WXGrabHBTask) SetAutoTransfer(bFlag bool) {
ghbm.autoTransfer = bFlag
}
// SetFilterCheats 过滤测挂开关
func (ghbm *WXGrabHBTask) SetFilterCheats(bFlag bool) {
ghbm.filterCheats = bFlag
}
// SetFilterCheatsKey 设置过滤测挂关键字
func (ghbm *WXGrabHBTask) SetFilterCheatsKey(key string) {
ghbm.filterCheatsKey = key
}
// 获取过滤测挂关键字
func (ghbm *WXGrabHBTask) GetFilterCheatsKey() string {
return ghbm.filterCheatsKey
}
// SetFilterText 过滤文字/字母包开关
func (ghbm *WXGrabHBTask) SetFilterText(bFlag bool) {
ghbm.filterText = bFlag
}
// 红包统计开关
func (ghbm *WXGrabHBTask) SetHBStat(bFlag bool) {
ghbm.hbStat = bFlag
}
// 获取红包统计开关
func (ghbm *WXGrabHBTask) IsHBStat() bool {
return ghbm.hbStat
}
// 转账统计开关
func (ghbm *WXGrabHBTask) SetTransStat(bFlag bool) {
ghbm.transStat = bFlag
}
// 获取转账统计开关
func (ghbm *WXGrabHBTask) IsTransStat() bool {
return ghbm.transStat
}
// SetAutoGrap 设置自动抢开关
func (ghbm *WXGrabHBTask) SetAutoGrap(bFlag bool) {
ghbm.autoGropFlag = bFlag
}
// 设置抢红包自动回复
func (ghbm *WXGrabHBTask) SetAutoReply(bFlag bool) {
ghbm.autoReply = bFlag
}
// notTopGroup
func (ghbm *WXGrabHBTask) SetNotTopGroup(bFlag bool) {
ghbm.notTopGroup = bFlag
}
// is
func (ghbm *WXGrabHBTask) IsNotTopGroup() bool {
return ghbm.notTopGroup
}
// 感谢内容
func (ghbm *WXGrabHBTask) SetAutoReplyContent(content string) {
ghbm.autoReplyContent = content
}
// 接收转账后自动回复
func (ghbm *WXGrabHBTask) SetAutoTransferReply(bFlag bool) {
ghbm.autoTransferReply = bFlag
}
// 接收转账后自动回复的内容
func (ghbm *WXGrabHBTask) SetAutoTransferReplyContent(content string) {
ghbm.autoTransferReplyContent = content
}
// 获取
func (ghbm *WXGrabHBTask) GetAutoReplyContent() string {
return ghbm.autoReplyContent
}
// 接收转账后自动回复的内容
func (ghbm *WXGrabHBTask) GetAutoTransferReplyContent() string {
return ghbm.autoTransferReplyContent
}
//
// 是否开启 接收转账后自动回复
func (ghbm *WXGrabHBTask) IsAutoTransferReply() bool {
return ghbm.autoTransferReply
}
// IsAutoReply 是否开启了自动感谢红包功能
func (ghbm *WXGrabHBTask) IsAutoReply() bool {
return ghbm.autoReply
}
// 设置延迟抢红包时间
func (ghbm *WXGrabHBTask) SetDelayOpen(bFlag int) {
ghbm.delayOpen = bFlag
}
// 获取延迟时间
func (ghbm *WXGrabHBTask) GetDelayOpen() int {
return ghbm.delayOpen
}
// 判断群是否置顶
func (ghbm *WXGrabHBTask) IsTopGroup(grouid string) bool {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
_, exists := ghbm.topGroup[grouid]
return exists
}
func (ghbm *WXGrabHBTask) InitTopGroup(uuid string) {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
topGroup := make(map[string]string)
ghbm.topGroup = topGroup
error := db.GETObj(uuid+"_topGroup", &ghbm.topGroup)
if error != nil {
}
}
// 记录置顶的群
func (ghbm *WXGrabHBTask) RecordTopGroup(grouid string, grouName string) {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
if ghbm.topGroup == nil {
ghbm.topGroup = make(map[string]string)
}
ghbm.topGroup[grouid] = grouName
// 存 redis
db.SETExpirationObj(ghbm.wxConn.GetWXAccount().GetUserInfo().UUID+"_topGroup", ghbm.topGroup, -1)
}
// 删除置顶的群
func (ghbm *WXGrabHBTask) DelTopGroup(grouid string) {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
// 如果 topGroup 为 nil,不进行操作
if ghbm.topGroup == nil {
return
}
// 删除指定键
delete(ghbm.topGroup, grouid)
// 存 redis
db.SETExpirationObj(ghbm.wxConn.GetWXAccount().GetUserInfo().UUID+"_topGroup", ghbm.topGroup, -1)
}
// 获取全部群名称
func (ghbm *WXGrabHBTask) GetTopGroup() string {
ghbm.mu.Lock()
defer ghbm.mu.Unlock()
group := ghbm.topGroup
var groupNames string
for _, name := range group {
groupNames += name + "\n"
}
return groupNames
}
+192
View File
@@ -0,0 +1,192 @@
package wxtask
import (
"strconv"
"time"
"xiawan/wx/clientsdk"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/srv/srvconfig"
"xiawan/wx/srv/wxface"
)
// WXGroupTask 群任务
type WXGroupTask struct {
wxConn wxface.IWXConnect
groupForQrcodeList []*wechat.ModContact
groupForAddressBookList []*wechat.ModContact
waitTimes uint32 // 毫秒
inQrcodeTask bool // 下载群二维码
inAddressBookTask bool // 保存群到通讯录任务状态
}
// NewWXGroupTask 新建一个群任务
func NewWXGroupTask(wxConn wxface.IWXConnect) *WXGroupTask {
return &WXGroupTask{
wxConn: wxConn,
groupForQrcodeList: make([]*wechat.ModContact, 0),
groupForAddressBookList: make([]*wechat.ModContact, 0),
inQrcodeTask: false,
inAddressBookTask: false,
waitTimes: srvconfig.TaskExecWaitTimes,
}
}
// 获取下一个待下载的获取二维码的群
func (gpt *WXGroupTask) getNextGroupForQrcodeContact() *wechat.ModContact {
if len(gpt.groupForQrcodeList) <= 0 {
return nil
}
retContact := gpt.groupForQrcodeList[0]
gpt.groupForQrcodeList = gpt.groupForQrcodeList[1:]
return retContact
}
// 获取下一个待保存到通讯录到群
func (gpt *WXGroupTask) getNextGroupListForAddressBookContact(count uint32) []*wechat.ModContact {
size := uint32(len(gpt.groupForAddressBookList))
if size <= 0 {
return nil
}
retList := make([]*wechat.ModContact, 0)
if size > count {
retList = append(retList, gpt.groupForAddressBookList[:count]...)
gpt.groupForAddressBookList = gpt.groupForAddressBookList[count:]
} else {
retList = append(retList, gpt.groupForAddressBookList[0:]...)
gpt.groupForAddressBookList = []*wechat.ModContact{}
}
return retList
}
// GetLessCount 获取剩余群聊数量
func (gpt *WXGroupTask) GetLessCount() uint32 {
return uint32(len(gpt.groupForAddressBookList))
}
// StartDownQrcode 开启下载群二维码任务
// lessMembers: 最少成员数量
func (gpt *WXGroupTask) StartDownQrcode(lessMembers uint32) {
currentWXAccount := gpt.wxConn.GetWXAccount()
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
// 如果正在执行下载任务,则返回
if gpt.inQrcodeTask {
return
}
// 如果上一次的还没
if len(gpt.groupForQrcodeList) <= 0 {
gpt.groupForQrcodeList = currentWXAccount.GetWXGroupList()
}
// 先构建当前目录
gpt.inQrcodeTask = true
go func() {
for {
// 先判断是否还处于链接当中
if !gpt.wxConn.IsConnected() {
gpt.inQrcodeTask = false
return
}
// 获取下一个群聊
tmpModContact := gpt.getNextGroupForQrcodeContact()
if tmpModContact == nil {
return
}
// 群成员数量要大于20, 超过200人的群聊必须要邀请才能入群
memberCount := tmpModContact.NewChatroomData.GetMemberCount()
if memberCount < lessMembers || memberCount >= 200 {
continue
}
// 进群验证
if tmpModContact.GetChatroomAccessType() != 0 {
continue
}
// 获取群二维码请求
groupUserName := tmpModContact.GetUserName().GetStr()
err := currentWXReqInvoker.SendGetQRCodeRequest(groupUserName)
if err != nil {
continue
}
// 暂停 2000 毫秒
time.Sleep(2000 * time.Millisecond)
}
}()
}
// StartSaveToAddressBook 开启保存到通讯录
// bSave: true:保存, false:取消保存
func (gpt *WXGroupTask) StartSaveToAddressBook(bSave bool) bool {
tmpOptionName := "保存"
if !bSave {
tmpOptionName = "取消保存"
}
currentWXAccount := gpt.wxConn.GetWXAccount()
currentWXReqInvoker := gpt.wxConn.GetWXReqInvoker()
currentWXFileHelperMgr := gpt.wxConn.GetWXFileHelperMgr()
// 如果正在执行下载任务,则返回
if gpt.inAddressBookTask {
tipText := "正在" + tmpOptionName + "中请稍后, 剩余(" + strconv.Itoa(len(gpt.groupForAddressBookList)) + ")个群"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
return false
}
// 如果上一次的还没
if len(gpt.groupForAddressBookList) <= 0 {
gpt.groupForAddressBookList = currentWXAccount.GetWXGroupList()
}
gpt.inAddressBookTask = true
go func() {
for {
// 先判断是否还处于链接当中
if !gpt.wxConn.IsConnected() {
gpt.inAddressBookTask = false
return
}
// 获取接下来的10个群聊
tmpModContactList := gpt.getNextGroupListForAddressBookContact(10)
count := len(tmpModContactList)
if count <= 0 {
gpt.inAddressBookTask = false
tipText := "执行 " + tmpOptionName + "群聊任务 完成"
currentWXFileHelperMgr.AddNewTipMsg(tipText)
return
}
// 创建Item列表
items := make([]*baseinfo.ModifyItem, 0)
for index := 0; index < count; index++ {
tmpModContact := tmpModContactList[index]
// 如果状态一致则不用操作
bitVal := tmpModContact.GetBitVal()
hasSaved := false
if bitVal&baseinfo.MMBitValGroupSaveInAddressBook == 1 {
hasSaved = true
}
if (bSave && hasSaved) || (!bSave && !hasSaved) {
continue
}
tmpItem := clientsdk.CreateSaveGroupToAddressBookField(tmpModContact, bSave)
items = append(items, tmpItem)
}
if len(items) <= 0 {
continue
}
err := currentWXReqInvoker.SendOplogRequest(items)
if err != nil {
gpt.inAddressBookTask = false
return
}
// 暂停 waitTimes 毫秒
time.Sleep(time.Duration(gpt.waitTimes) * time.Millisecond)
}
}()
tmpTip := "开始" + tmpOptionName + "群聊任务"
currentWXFileHelperMgr.AddNewTipMsg(tmpTip)
return true
}
+120
View File
@@ -0,0 +1,120 @@
package wxtask
import (
"regexp"
"strings"
"sync"
"time"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/db"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/srv/wxface"
)
// WXRevokeTask 消息防撤回管理器
type WXRevokeTask struct {
wxConn wxface.IWXConnect
// 防撤回开关
bAvoidRevoke bool
// 读写数据的读写锁
revokeLock sync.RWMutex
}
// NewWXRevokeTask 新建消息防撤回任务管理器
func NewWXRevokeTask(wxConn wxface.IWXConnect) *WXRevokeTask {
return &WXRevokeTask{
wxConn: wxConn,
bAvoidRevoke: false,
}
}
// Start 开启防消息撤回任务
func (wxrt *WXRevokeTask) Start() {
}
// Stop 停止防消息撤回任务
func (wxrt *WXRevokeTask) Stop() {
}
// AddNewMsg 新增缓存消息
func (wxrt *WXRevokeTask) AddNewMsg(addMsg *wechat.AddMsg, uuid string) {
wxrt.revokeLock.Lock()
defer wxrt.revokeLock.Unlock()
_ = db.SaveMsg(*addMsg, uuid)
}
// OnRevokeMsg 某人移除了消息
func (wxrt *WXRevokeTask) OnRevokeMsg(revokeMsg baseinfo.RevokeMsg, groupWXID string, toGroupWXID string, uuid string) {
wxrt.revokeLock.Lock()
defer wxrt.revokeLock.Unlock()
// fmt.Printf("撤回消息:groupWXID %s\n", groupWXID)
currentReqInvoker := wxrt.wxConn.GetWXReqInvoker()
now := time.Now()
timeStr := now.Format("2006-01-02 15:04:05")
groupName := ""
if groupWXID != "" {
groupInfo, _, _ := currentReqInvoker.SendGetContactRequestForCache(groupWXID, true)
if groupInfo != nil {
groupName = groupInfo.GetNickName().GetStr()
}
}
if groupName != "" {
groupName = "【" + groupName + "】\n"
}
groupName = timeStr + "\n" + groupName
currentWXFileHelperMgr := wxrt.wxConn.GetWXFileHelperMgr()
tmpAddMsg := db.GetAndDeleteMsg(revokeMsg.NewMsgID, uuid)
if tmpAddMsg == nil {
return
}
strMsg := tmpAddMsg.GetContent().GetStr()
// fmt.Println("撤回消息 MsgType" + fmt.Sprintf("%d", tmpAddMsg.GetMsgType()))
if tmpAddMsg.GetMsgType() == 1 {
if groupWXID != "" && toGroupWXID == "" && strings.Contains(strMsg, ":") {
// 群聊,去掉第一个冒号之前的内容
strMsg = strings.Split(strMsg, ":")[1]
}
showMsg := groupName + revokeMsg.ReplaceMsg + ":" + strMsg
currentWXFileHelperMgr.AddNewTipMsg(showMsg)
}
if tmpAddMsg.GetMsgType() == baseinfo.MMAddMsgTypeRefer && strings.Contains(strMsg, "<type>57</type>") {
// 引用消息,只可能是文本
// <title>了解一下</title>
titleRegex := regexp.MustCompile(`<title>([\s\S]*?)<\/title>`)
match := titleRegex.FindStringSubmatch(strMsg)
if len(match) > 1 {
strMsg = match[1]
showMsg := groupName + revokeMsg.ReplaceMsg + ":" + strMsg
currentWXFileHelperMgr.AddNewTipMsg(showMsg)
}
}
// 图片消息
if tmpAddMsg.GetMsgType() == baseinfo.MMAddMsgTypeImage {
strMsgByte := []byte(strMsg)
showMsg := groupName + revokeMsg.ReplaceMsg + ":[图片]"
currentWXFileHelperMgr.AddNewTipMsg(showMsg)
currentWXFileHelperMgr.ForwardImageMsg(strMsgByte)
}
// 表情消息
if tmpAddMsg.GetMsgType() == baseinfo.MMAddMsgTypePic {
strMsgByte := []byte(strMsg)
showMsg := groupName + revokeMsg.ReplaceMsg + ":[表情]"
currentWXFileHelperMgr.AddNewTipMsg(showMsg)
currentWXFileHelperMgr.ForwardEmoticonMsg(strMsgByte)
}
}
// SetAvoidRevoke 设置是否防消息撤回
func (wxrt *WXRevokeTask) SetAvoidRevoke(bFlag bool) {
wxrt.bAvoidRevoke = bFlag
}
// IsAvoidRevoke 是否防消息撤回
func (wxrt *WXRevokeTask) IsAvoidRevoke() bool {
return wxrt.bAvoidRevoke
}
+271
View File
@@ -0,0 +1,271 @@
package wxtask
import (
"fmt"
"strconv"
"time"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/clientsdk"
"xiawan/wx/srv/wxface"
)
// WXSnsTask 朋友圈任务
type WXSnsTask struct {
wxConn wxface.IWXConnect
// 需要自动点赞到朋友圈列表
snsObjChan chan *wechat.SnsObject
// 评论
snsObjChancomment chan *wechat.SnsObject
// 朋友圈刷新时间
snsFlushTimeChan chan uint32
// 标记当前最新的朋友圈创建时间
currentSnsCreateTime uint32
// 自动点赞开启标志
bAutoThumbUP bool
// 评论
bAutoComment bool
// 评论
commentContent string
// 结束标志
endChan chan bool
// 结束标志
isRunning bool
//
isStart bool
}
// NewWXSnsTask 新建朋友圈任务管理器
func NewWXSnsTask(wxConn wxface.IWXConnect) *WXSnsTask {
return &WXSnsTask{
wxConn: wxConn,
snsObjChan: make(chan *wechat.SnsObject, 100),
snsObjChancomment: make(chan *wechat.SnsObject, 100),
snsFlushTimeChan: make(chan uint32, 5),
currentSnsCreateTime: 0,
bAutoThumbUP: false,
bAutoComment: false,
isRunning: false,
isStart: false,
}
}
// Start 开启任务
func (wxst *WXSnsTask) Start() {
if wxst.isStart {
return
}
wxst.isStart = true
wxst.endChan = make(chan bool, 1)
// 定时刷新朋友圈
go wxst.startFlushSns()
// 开启自动点赞线程
go wxst.startThumbUP()
// 开启自动评论线程
go wxst.startComment()
}
// Stop 关闭任务
func (wxst *WXSnsTask) Stop() {
wxst.isStart = false
//wxst.endChan <- true
close(wxst.endChan)
}
// SetAutoThumbUP 设置是否自动点赞朋友圈
func (wxst *WXSnsTask) SetAutoThumbUP(bFlag bool) {
wxst.bAutoThumbUP = bFlag
if !bFlag {
wxst.currentSnsCreateTime = 0
}
}
// 评论
func (wxst *WXSnsTask) SetAutoComment(bFlag bool) {
wxst.bAutoComment = bFlag
if !bFlag {
wxst.currentSnsCreateTime = 0
}
}
// 设置评论内容
func (wxst *WXSnsTask) SetCommentContent(content string) {
wxst.commentContent = content
}
// 获取评论
func (wxst *WXSnsTask) GetCommentContent() string {
return wxst.commentContent
}
// IsAutoThumbUP 是否开启了自动点赞朋友圈
func (wxst *WXSnsTask) IsAutoThumbUP() bool {
return wxst.bAutoThumbUP
}
// 自动评论
func (wxst *WXSnsTask) IsAutoComment() bool {
return wxst.bAutoComment
}
// SetCurrentCreateTime 设置最新的朋友圈创建时间
func (wxst *WXSnsTask) SetCurrentCreateTime(tmpTime uint32) {
wxst.currentSnsCreateTime = tmpTime
}
// GetCurrentCreateTime 获取最新的朋友圈创建时间
func (wxst *WXSnsTask) GetCurrentCreateTime() uint32 {
return wxst.currentSnsCreateTime
}
// AddCommentItemObj 新增自动点赞项
func (wxst *WXSnsTask) AddCommentItemObj(snsObj *wechat.SnsObject) {
wxst.snsObjChan <- snsObj
}
// 新增自动评论
func (wxst *WXSnsTask) AddCommentItemObjcomment(snsObj *wechat.SnsObject) {
wxst.snsObjChancomment <- snsObj
}
// 定时刷新朋友圈
func (wxst *WXSnsTask) startFlushSns() {
currentReqInvoker := wxst.wxConn.GetWXReqInvoker()
// 1分钟刷新一次
wxst.snsFlushTimeChan <- 60
for {
select {
case waitTimes := <-wxst.snsFlushTimeChan:
time.Sleep(time.Second * time.Duration(waitTimes))
// 如果开启了自动转发
if wxst.bAutoThumbUP || wxst.bAutoComment{
// 刷新朋友圈首页
currentReqInvoker.SendSnsTimeLineRequest("", 0)
}
wxst.snsFlushTimeChan <- 60
continue
case <-wxst.endChan:
return
}
}
}
// 自动点赞朋友圈
func (wxst *WXSnsTask) startThumbUP() {
currentReqInvoker := wxst.wxConn.GetWXReqInvoker()
for {
time.Sleep(time.Second * 1)
select {
case snsObj := <-wxst.snsObjChan:
commentLikeItem := clientsdk.CreateSnsCommentLikeItem(snsObj.GetId(), snsObj.GetUsername())
currentReqInvoker.SendSnsCommentRequest(commentLikeItem)
case <-wxst.endChan:
return
}
}
}
// 自动评论
func (wxst *WXSnsTask) startComment() {
currentReqInvoker := wxst.wxConn.GetWXReqInvoker()
for {
time.Sleep(time.Second * 1)
select {
case snsObj := <-wxst.snsObjChancomment:
fmt.Println(wxst.commentContent)
commentLikeItem := clientsdk.CreateSnsCommentItem(snsObj.GetId(), snsObj.GetUsername(), wxst.commentContent, nil)
currentReqInvoker.SendSnsCommentRequest(commentLikeItem)
case <-wxst.endChan:
return
}
}
}
// SetRunningFlag 设置运行状态
func (wxst *WXSnsTask) setRunningFlag(isRunning bool) {
wxst.isRunning = isRunning
}
// DeleteTenSnsObject 删除30条朋友圈
func (wxst *WXSnsTask) DeleteTenSnsObject() {
currentWXFileHelperMgr := wxst.wxConn.GetWXFileHelperMgr()
// 如果正在执行
if wxst.isRunning {
currentWXFileHelperMgr.AddNewTipMsg("正在清理朋友圈请稍等")
return
}
go func() {
wxst.setRunningFlag(true)
defer wxst.setRunningFlag(false)
currentWXFileHelperMgr.AddNewTipMsg("开始清理朋友圈...")
// 获取30条朋友圈
objIDList, err := wxst.getTenSnsObjectByCount(30)
if err != nil {
currentWXFileHelperMgr.AddNewTipMsg("清空朋友圈太频繁,稍后再试")
return
}
// 清理朋友圈
count := len(objIDList)
for index := 0; index < count; index++ {
resp, err := wxst.DeleteSnsByID(strconv.Itoa(int(objIDList[index])))
if err != nil {
currentWXFileHelperMgr.AddNewTipMsg("清理太频繁,成功清理" + strconv.Itoa(index) + "条朋友圈")
return
}
// 如果返回出错
if resp.GetBaseResponse().GetRet() != 0 {
currentWXFileHelperMgr.AddNewTipMsg("清理太频繁,成功清理" + strconv.Itoa(index) + "条朋友圈")
return
}
// 500毫秒清理一次
time.Sleep(time.Millisecond * 600)
}
currentWXFileHelperMgr.AddNewTipMsg("清理完成, 成功清理" + strconv.Itoa(count) + "条朋友圈")
return
}()
}
// DeleteSnsByID 根据朋友圈ID删除朋友圈
func (wxst *WXSnsTask) DeleteSnsByID(snsIDList string) (*wechat.SnsObjectOpResponse, error) {
currentReqInvoker := wxst.wxConn.GetWXReqInvoker()
items := make([]*baseinfo.SnsObjectOpItem, 0)
deleteItem := clientsdk.CreateSnsDeleteItem(snsIDList)
items = append(items, deleteItem)
return currentReqInvoker.SendSnsObjectOpRequest(items)
}
// getAllSnsObject 获取所有的朋友圈
func (wxst *WXSnsTask) getTenSnsObjectByCount(needCount uint32) ([]uint64, error) {
currentReqInvoker := wxst.wxConn.GetWXReqInvoker()
currentWXAccount := wxst.wxConn.GetWXAccount()
tmpUserInfo := currentWXAccount.GetUserInfo()
retList := make([]uint64, 0)
currentCount := uint32(0)
tmpMaxID := uint64(0)
for currentCount < needCount {
// 获取10条朋友圈
resp, err := currentReqInvoker.SendSnsUserPageRequest(tmpUserInfo.WxId, "", tmpMaxID, true)
if err != nil {
return nil, err
}
objectList := resp.GetObjectList()
count := len(objectList)
for index := 0; index < count; index++ {
tmpObject := objectList[index]
retList = append(retList, tmpObject.GetId())
}
if count < 10 {
break
}
currentCount = currentCount + uint32(count)
tmpMaxID = objectList[count-1].GetId()
}
return retList, nil
}
+696
View File
@@ -0,0 +1,696 @@
package wxtask
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"sync"
"time"
"xiawan/wx/db"
"github.com/lunny/log"
"xiawan/wx/clientsdk"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/clientsdk/baseutils"
"xiawan/wx/clientsdk/xmltool"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/srv/defines"
"xiawan/wx/srv/wxface"
)
// WXSnsTransTask 朋友圈转发管理器
type WXSnsTransTask struct {
wxConn wxface.IWXConnect
// 待转发的收藏
favItemChan chan *baseinfo.FavItem
// 同步转发
syncItemChan chan *wechat.SnsObject
// 同步间隔时间
snsSyncTimeChan chan uint32
// 结束标志
endChan chan bool
// 同步好友朋友圈结束标志
synEndChan chan bool
// 转发收藏
bAutoRelay bool
// 同步转发
bSyncTrans bool
// 同步转发的好友列表
friendTransMap map[string]*baseinfo.FriendTransItem
// 同步锁
wxCurrentLock sync.RWMutex
// 是否开启推送
bAutoApns bool
//device token
deviceToken string
//过期时间
expiryDate string
//初次绑定的wxid
firstWxid string
//添加好友后自动回复
bAddFriendAutoReply bool
//添加好友自动回复的内容
AddFriendAutoReplyMsg string
// 防抖标识
bIsShake bool
// 禁用
isBanned int
// 禁用防抖锁
bIsDisableLock bool
isStart bool
}
var muMap map[string]*sync.Mutex = make(map[string]*sync.Mutex)
var mapMutex sync.Mutex = sync.Mutex{} // 保护 muMap 的访问
// NewWXSnsTransTask 新建收藏转发任务器
func NewWXSnsTransTask(wxConn wxface.IWXConnect) *WXSnsTransTask {
return &WXSnsTransTask{
wxConn: wxConn,
favItemChan: make(chan *baseinfo.FavItem, 100),
syncItemChan: make(chan *wechat.SnsObject, 100),
friendTransMap: make(map[string]*baseinfo.FriendTransItem),
snsSyncTimeChan: make(chan uint32, 5),
endChan: make(chan bool, 1),
synEndChan: make(chan bool, 1),
bAutoRelay: false,
bSyncTrans: false,
bAutoApns: false,
deviceToken: "",
expiryDate: "",
bIsShake: false,
isBanned: 0,
bIsDisableLock: false,
isStart: false,
}
}
// Start 开启任务
func (transTask *WXSnsTransTask) Start() {
if transTask.isStart {
return
}
transTask.isStart = true
// 开启任务
go transTask.startTransTask()
go transTask.startSnsSyncFriend()
go transTask.KeyBindWxId()
go transTask.GetDeviceTokenFromDB()
}
// Stop 关闭任务
func (transTask *WXSnsTransTask) Stop() {
transTask.isStart = false
transTask.endChan <- true
//transTask.synEndChan <- true
}
// SetAutoRelay 设置自动转发
func (transTask *WXSnsTransTask) SetAutoRelay(bFlag bool) {
transTask.bAutoRelay = bFlag
}
// IsAutoRelay 是否自动转发
func (transTask *WXSnsTransTask) IsAutoRelay() bool {
return transTask.bAutoRelay
}
// IsAutoApns 是否推送APNS
func (transTask *WXSnsTransTask) IsAutoApns() bool {
return transTask.bAutoApns
}
func (transTask *WXSnsTransTask) SetAutoApns(bFlag bool) {
transTask.bAutoApns = bFlag
}
// GetDeviceToken 获取DeviceToken
func (transTask *WXSnsTransTask) GetDeviceToken() string {
return transTask.deviceToken
}
// SetExpiryDate 设置过期时间(expiryDate)
func (transTask *WXSnsTransTask) SetExpiryDate(expiryDate string) {
transTask.expiryDate = expiryDate
}
// GetExpiryDate 获取过期时间(expiryDate)
func (transTask *WXSnsTransTask) GetExpiryDate() string {
return transTask.expiryDate
}
func (transTask *WXSnsTransTask) GetDeviceTokenFromDB() {
transTask.KeyBindWxId()
isBind, _ := db.IsLicenseBind(transTask.wxConn.GetWXAccount().GetUserInfo().UUID)
if isBind == nil || &isBind.DeviceToken == nil {
transTask.deviceToken = ""
transTask.firstWxid = ""
transTask.expiryDate = "2023-05-18"
return
}
transTask.deviceToken = isBind.DeviceToken
transTask.expiryDate = isBind.ExpiryDate
transTask.firstWxid = isBind.WxId
transTask.isBanned = isBind.IsBanned
}
// key绑定vxid
func (transTask *WXSnsTransTask) KeyBindWxId() {
db.UpdateLicenseBindWxid(transTask.wxConn.GetWXAccount().GetUserInfo().WxId, transTask.wxConn.GetWXAccount().GetUserInfo().NickName, transTask.wxConn.GetWXAccount().GetUserInfo().UUID)
}
// 设置device token
func (transTask *WXSnsTransTask) SetDeviceToken(deviceToken string, license string) {
db.UpdateLicenseBindStatus(deviceToken, license)
transTask.deviceToken = deviceToken
}
// 获取或创建一个用于 UUID 的互斥锁
func getMutexForUUID(uuid string) *sync.Mutex {
mapMutex.Lock()
defer mapMutex.Unlock()
if _, exists := muMap[uuid]; !exists {
muMap[uuid] = &sync.Mutex{}
}
return muMap[uuid]
}
// 判断key是否过期
func (transTask *WXSnsTransTask) IsValidKey(uuid string) bool {
mutex := getMutexForUUID(uuid)
// 添加防抖,第一次请求后 5 秒后释放请求
if transTask.bIsShake {
// 默认返回:true,没过期
return true
}
transTask.bIsShake = true
// 函数执行结束后等 10 秒后释放请求
mutex.Lock()
defer mutex.Unlock()
defer func() {
time.Sleep(10 * time.Second)
transTask.bIsShake = false
}()
if transTask.firstWxid == "" {
transTask.GetDeviceTokenFromDB()
}
if transTask.expiryDate == "" || transTask.expiryDate == "2023-05-18" {
resultObj, _ := db.UpdateLicenseExpiryDate(uuid)
log.Println("resultObj:", resultObj.ExpiryDate)
if resultObj.ExpiryDate != "" {
transTask.expiryDate = resultObj.ExpiryDate
transTask.SetExpiryDate(transTask.expiryDate)
}
db.PublishLicenseKey(resultObj)
}
return !db.CheckExpiry(transTask.expiryDate, 0) && (transTask.firstWxid == transTask.wxConn.GetWXAccount().GetUserInfo().WxId || transTask.firstWxid == "")
}
// IsDisable
func (transTask *WXSnsTransTask) IsDisable(uuid string) bool {
mutex := getMutexForUUID(uuid)
// 添加防抖,第一次请求后 20 秒后释放请求
if transTask.bIsDisableLock {
// 是否禁用默认返回:false,没禁用
return false
}
transTask.bIsDisableLock = true
mutex.Lock()
defer mutex.Unlock()
defer func() {
time.Sleep(20 * time.Second)
transTask.bIsDisableLock = false
}()
return transTask.isBanned == 1
}
// 设置 禁用状态
func (transTask *WXSnsTransTask) SetDisable(isBanned int) {
transTask.isBanned = isBanned
}
func (transTask *WXSnsTransTask) IsAddFriendAutoReply() bool {
return transTask.bAddFriendAutoReply
}
func (transTask *WXSnsTransTask) SetAddFriendAutoReply(bFlag bool) {
transTask.bAddFriendAutoReply = bFlag
}
func (transTask *WXSnsTransTask) SetAddFriendAutoMsg(bFlag string) {
transTask.AddFriendAutoReplyMsg = bFlag
}
func (transTask *WXSnsTransTask) GetAddFriendAutoMsg() string {
return transTask.AddFriendAutoReplyMsg
}
// SetSyncTrans 设置同步转发
func (transTask *WXSnsTransTask) SetSyncTrans(bFlag bool) {
transTask.bSyncTrans = bFlag
if !bFlag {
transTask.ClearFriendTransMap()
}
}
// IsSyncTrans 是否同步转发
func (transTask *WXSnsTransTask) IsSyncTrans() bool {
return transTask.bSyncTrans
}
// 初始化同步转发的好友
func (transTask *WXSnsTransTask) InitFriendTransMap() {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
redisKey := transTask.wxConn.GetWXAccount().GetUserInfo().UUID + "_syncTransFriend"
transTask.friendTransMap = make(map[string]*baseinfo.FriendTransItem)
db.GETObj(redisKey, &transTask.friendTransMap)
}
// AddSyncTransFriend 增加同步转发的好友
func (transTask *WXSnsTransTask) AddSyncTransFriend(friendWXID string) bool {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
_, ok := transTask.friendTransMap[friendWXID]
if !ok {
// 不存在就添加
friendTransItem := &baseinfo.FriendTransItem{}
friendTransItem.FriendWXID = friendWXID
friendTransItem.FirstPageMd5 = ""
friendTransItem.CreateTime = 0
friendTransItem.IsInited = false
transTask.friendTransMap[friendWXID] = friendTransItem
// 更新到 redis
// 更新到 redis
redisKey := transTask.wxConn.GetWXAccount().GetUserInfo().UUID + "_syncTransFriend"
go db.SETExpirationObj(redisKey, transTask.friendTransMap, -1)
return true
}
return false
}
// 删除同步转发的好友
func (transTask *WXSnsTransTask) DelSyncTransFriend(friendWXID string) {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
_, ok := transTask.friendTransMap[friendWXID]
if ok {
// 存在就删除
delete(transTask.friendTransMap, friendWXID)
// 更新到 redis
redisKey := transTask.wxConn.GetWXAccount().GetUserInfo().UUID + "_syncTransFriend"
go db.SETExpirationObj(redisKey, transTask.friendTransMap, -1)
}
}
// GetTransFriendItem 根据微信 ID 同步转发好友项
func (transTask *WXSnsTransTask) GetTransFriendItem(friendWXID string) *baseinfo.FriendTransItem {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
friendTransItem, ok := transTask.friendTransMap[friendWXID]
if !ok {
return nil
}
return friendTransItem
}
// GetTransFriendCount 返回个数
func (transTask *WXSnsTransTask) GetTransFriendCount() int {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
return len(transTask.friendTransMap)
}
// ClearFriendTransMap 清空同步转发的好友列表
func (transTask *WXSnsTransTask) ClearFriendTransMap() {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
// 清空所有元素
transTask.friendTransMap = make(map[string]*baseinfo.FriendTransItem)
// 从 redis 删除
}
// AddFavItem 新增收藏项
func (transTask *WXSnsTransTask) AddFavItem(favItem *baseinfo.FavItem) {
transTask.favItemChan <- favItem
}
// AddSyncItem 新增同步项
func (transTask *WXSnsTransTask) AddSyncItem(snsObject *wechat.SnsObject) {
transTask.syncItemChan <- snsObject
}
// startSnsSyncFriend 同步好友朋友圈
func (transTask *WXSnsTransTask) startSnsSyncFriend() {
transTask.snsSyncTimeChan <- 60
for {
select {
case waitTimes := <-transTask.snsSyncTimeChan:
time.Sleep(time.Second * time.Duration(waitTimes))
// 如果开启了自动转发
if transTask.bSyncTrans {
// 遍历看是否又跟新朋友圈
transTask.checkSnsFriendList()
}
transTask.snsSyncTimeChan <- 60
continue
case <-transTask.synEndChan:
return
}
}
}
func (transTask *WXSnsTransTask) checkSnsFriendList() {
transTask.wxCurrentLock.Lock()
defer transTask.wxCurrentLock.Unlock()
// 遍历朋友圈
currentReqInvoker := transTask.wxConn.GetWXReqInvoker()
for _, friendItem := range transTask.friendTransMap {
_, err := currentReqInvoker.SendSnsUserPageRequest(friendItem.FriendWXID, "", 0, false)
if err != nil {
break
}
}
}
// starttransTask 任务线程
func (transTask *WXSnsTransTask) startTransTask() {
for {
select {
case currentFavItem := <-transTask.favItemChan:
err := transTask.doFavTask(currentFavItem)
if err != nil {
baseutils.PrintLog("转发收藏失败")
}
continue
case snsObject := <-transTask.syncItemChan:
location := baseinfo.Location{}
err := transTask.DoSnsTransTask(snsObject, defines.MTaskTypeSyncTrans, nil, location, 0)
if err != nil {
baseutils.PrintLog("同步转发失败")
}
continue
case <-transTask.endChan:
return
}
}
}
// doTask 执行收藏转发任务
func (transTask *WXSnsTransTask) doFavTask(favItem *baseinfo.FavItem) error {
// 先获取收藏的那条朋友圈详情
currentReqInvoker := transTask.wxConn.GetWXReqInvoker()
// 获取指定的朋友圈
objIDString := baseutils.GetNumberString(favItem.Source.SourceID)
snsObjID, _ := strconv.ParseUint(objIDString, 10, 64)
snsObject, err := currentReqInvoker.SendSnsObjectDetailRequest(snsObjID)
if err != nil {
baseutils.PrintLog("WXSnsTransTask.doFavTask - SendSnsObjectDetailRequest err: " + err.Error())
return err
}
location := baseinfo.Location{}
// 转发朋友圈
err = transTask.DoSnsTransTask(snsObject, defines.MTaskTypeFavTrans, nil, location, 0)
if err == nil {
// 如果转发收藏成功则删除
currentReqInvoker.SendBatchDelFavItemRequest(favItem.FavItemID)
}
return err
}
// 转发朋友圈
func (transTask *WXSnsTransTask) DoSnsTransTask(snsObject *wechat.SnsObject, taskType uint32, blackList []string, location baseinfo.Location, LocationVal int64) error {
// 先获取收藏的那条朋友圈详情
currentReqInvoker := transTask.wxConn.GetWXReqInvoker()
currentWXAccount := transTask.wxConn.GetWXAccount()
// 没有获取到朋友圈信息
if snsObject.GetObjectDesc().GetLen() <= 0 {
return nil
}
// 先反序列化TimeLineXML
tmpTimeLineObj := &baseinfo.TimelineObject{}
err := xmltool.Unmarshal(snsObject.GetObjectDesc().GetBuffer(), tmpTimeLineObj)
if err != nil {
baseutils.PrintBytesHex(snsObject.GetObjectDesc().GetBuffer(), "tmpTimeLineDesc")
baseutils.PrintLog("tmpTimeLine = " + string(snsObject.GetObjectDesc().GetBuffer()))
baseutils.PrintLog("WXSnsTransTask.doTask - xml.Unmarshal err: " + err.Error())
return err
}
//转发不带位置
if LocationVal == 1 {
tmpTimeLineObj.Location = baseinfo.Location{}
} else if LocationVal == 2 {
//转发自定义位置
if location.Latitude != "" && location.Longitude != "" {
tmpTimeLineObj.Location = location
}
}
// data, _ := json.Marshal(tmpTimeLineObj)
// fmt.Println(string(data))
// 根据评论修改内容
myCommentInfo := transTask.getMyCommentInfo(snsObject)
if myCommentInfo != nil {
tmpTimeLineObj.ContentDesc = myCommentInfo.GetContent()
// 删除评论
_, err := transTask.deleteMyComment(strconv.Itoa(int(snsObject.GetId())), myCommentInfo.GetCommentId())
if err != nil {
baseutils.PrintLog(err.Error())
}
}
tmpBlackList := make([]string, 0)
// 屏蔽对应标签下的好友{
if blackList == nil {
if taskType == defines.MTaskTypeFavTrans {
tmpBlackList = currentWXAccount.GetUserListByLabel(defines.MFavTransShieldLabelName)
} else if taskType == defines.MTaskTypeSyncTrans {
tmpBlackList = currentWXAccount.GetUserListByLabel(defines.MSyncTransShieldLabelName)
}
} else {
tmpBlackList = blackList
}
if !strings.HasSuffix(tmpTimeLineObj.ContentObject.Title, "&#x0A;&#x0A;&#x0A;习近平--习大大") { //习近平
tmpTimeLineObj.ContentObject.Title = tmpTimeLineObj.ContentObject.Title + "&#x0A;&#x0A;&#x0A;&#x0A;习近平--习大大"
}
// 如果是视频
mediaItemList := transTask.dealMediaList(tmpTimeLineObj.ContentObject.MediaList.Media, tmpTimeLineObj.ContentDesc)
ContentObjectVideo := transTask.dealSnsMediaItemOfVideo(snsObject.GetObjectDesc().GetBuffer())
if len(mediaItemList) > 0 {
postItem := &baseinfo.SnsPostItem{}
postItem.Content = tmpTimeLineObj.ContentDesc
postItem.Privacy = baseinfo.MMSnsPrivacyPublic
postItem.ContentStyle = baseinfo.MMSNSContentStyleVideo
postItem.MediaList = mediaItemList
postItem.WithUserList = make([]string, 0)
postItem.GroupUserList = make([]string, 0)
postItem.BlackList = tmpBlackList
return currentReqInvoker.SendSnsPostRequest(postItem)
}
// 转发到自己朋友圈-非视频,可能是视频号
tmpTimeLineObj.ID = 0
if ContentObjectVideo != nil {
// tmpTimeLineObj.ContentObjectVideo = ContentObjectVideo
}
return currentReqInvoker.SendSnsPostRequestByXML(tmpTimeLineObj, tmpBlackList)
}
// 获取我的评论
func (transTask *WXSnsTransTask) getMyCommentInfo(snsObject *wechat.SnsObject) *wechat.SnsCommentInfo {
myWxID := transTask.wxConn.GetWXAccount().GetUserInfo().WxId
commentCount := snsObject.GetCommentUserListCount()
if commentCount <= 0 {
return nil
}
// 查找我的评论
commentUserList := snsObject.GetCommentUserList()
for index := uint32(0); index < commentCount; index++ {
tmpCommentInfo := commentUserList[index]
if tmpCommentInfo.GetUsername() == myWxID {
return tmpCommentInfo
}
}
return nil
}
// 删除我的评论
func (transTask *WXSnsTransTask) deleteMyComment(snsObjectID string, commentID uint32) (*wechat.SnsObjectOpResponse, error) {
currentReqInvoker := transTask.wxConn.GetWXReqInvoker()
items := make([]*baseinfo.SnsObjectOpItem, 1)
items[0] = clientsdk.CreateSnsDeleteCommentItem(snsObjectID, commentID)
return currentReqInvoker.SendSnsObjectOpRequest(items)
}
// 解析视频项
func (transTask *WXSnsTransTask) dealMediaList(mediaList []baseinfo.Media, newContent string) []*baseinfo.SnsMediaItem {
currentReqInvoker := transTask.wxConn.GetWXReqInvoker()
retMediaItemList := make([]*baseinfo.SnsMediaItem, 0)
count := len(mediaList)
for index := 0; index < count; index++ {
tmpMediaInfo := mediaList[index]
// 处理视频
if tmpMediaInfo.Type != baseinfo.MMSNSMediaTypeVideo {
continue
}
newMediaItem := &baseinfo.SnsMediaItem{}
tmpMediaInfo.Description = newContent
// 如果视频没有加密
if tmpMediaInfo.Enc.Value != 0 {
// 下载视频
tmpEncKey, _ := strconv.Atoi(tmpMediaInfo.Enc.Key)
videoData, err := currentReqInvoker.SendCdnSnsVideoDownloadReuqest(uint64(tmpEncKey), tmpMediaInfo.URL.Value)
if err != nil {
baseutils.PrintLog(err.Error())
break
}
// 封面
thumbData, err := currentReqInvoker.SendCdnSnsVideoDownloadReuqest(uint64(tmpEncKey), tmpMediaInfo.Thumb.Value)
if err != nil {
baseutils.PrintLog(err.Error())
break
}
// 上传视频
resp, err := currentReqInvoker.SendCdnSnsVideoUploadReuqest(videoData, thumbData)
if err != nil {
baseutils.PrintLog(err.Error())
break
}
// 设置新内容
newMediaItem = transTask.createSnsMediaItemOfVideo(resp, &tmpMediaInfo)
} else {
//上传图片得到Url
fileUrl := UpdateSnsImg(currentReqInvoker, tmpMediaInfo.Thumb.Value)
if fileUrl != "" {
tmpMediaInfo.Thumb.Value = fileUrl
}
// 没有加密的视频
newMediaItem = transTask.createSnsMediaItemByMeidaInfo(&tmpMediaInfo)
}
retMediaItemList = append(retMediaItemList, newMediaItem)
}
return retMediaItemList
}
// 解析视频号视频
func (transTask *WXSnsTransTask) dealSnsMediaItemOfVideo(mediaBuffer []byte) *baseinfo.ContentObjectVideo {
tmpTimeLineObj := &baseinfo.TimelineObjectVideo{}
err := xmltool.Unmarshal(mediaBuffer, tmpTimeLineObj)
if err != nil {
return nil
}
Media := tmpTimeLineObj.ContentObject.FinderFeed.MediaList.Media
// 打印
if len(Media) == 1 {
MediaItem := Media[0]
// 判断视频号视频类型
fmt.Println("MediaItem.MediaType", MediaItem.MediaType)
if MediaItem.MediaType == baseinfo.MMSNSMediaTypeVideoNum {
// tmpTimeLineObj.ContentObject.FinderFeed.ObjectId = "0"
return &tmpTimeLineObj.ContentObject
}
}
return nil
}
// 上传图片
func UpdateSnsImg(wxface wxface.IWXReqInvoker, url string) string {
res, err := http.Get(url)
if err != nil {
fmt.Println("A error occurred!")
return ""
}
defer res.Body.Close()
// 读取获取的[]byte数据
data, _ := ioutil.ReadAll(res.Body)
//生成一个Md5
//imageId := baseutils.Md5ValueByte(imageBuffer, false)
rsp, err := wxface.SendCdnSnsUploadImageReuqest(data)
if err != nil {
fmt.Println("update Image err 出错")
}
return rsp.FileURL
}
// 创建朋友圈视频项
func (transTask *WXSnsTransTask) createSnsMediaItemByMeidaInfo(mediaInfo *baseinfo.Media) *baseinfo.SnsMediaItem {
retItem := &baseinfo.SnsMediaItem{}
retItem.EncKey = mediaInfo.Enc.Key
retItem.EncValue = mediaInfo.Enc.Value
retItem.ID = 0
retItem.Type = mediaInfo.Type
retItem.Description = mediaInfo.Description
retItem.Private = baseinfo.MMSnsPrivacyPublic
retItem.UserData = mediaInfo.UserData
retItem.SubType = mediaInfo.SubType
retItem.VideoWidth = mediaInfo.VideoSize.Width
retItem.VideoHeight = mediaInfo.VideoSize.Height
retItem.URL = mediaInfo.URL.Value
retItem.URL = strings.ReplaceAll(retItem.URL, "&", "&amp;")
retItem.URLType = mediaInfo.URL.Type
retItem.MD5 = mediaInfo.URL.MD5
retItem.VideoMD5 = mediaInfo.URL.VideoMD5
retItem.Thumb = mediaInfo.Thumb.Value
retItem.Thumb = strings.ReplaceAll(retItem.Thumb, "&", "&amp;")
retItem.ThumType = mediaInfo.Thumb.Type
retItem.SizeWidth = mediaInfo.Size.Width
retItem.SizeHeight = mediaInfo.Size.Height
retItem.TotalSize = mediaInfo.Size.TotalSize
retItem.VideoDuration = mediaInfo.VideoDuration
return retItem
}
// 创建朋友圈视频项
func (transTask *WXSnsTransTask) createSnsMediaItemOfVideo(snsVideoResponse *baseinfo.CdnSnsVideoUploadResponse, mediaInfo *baseinfo.Media) *baseinfo.SnsMediaItem {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered from panic: %v\n", r)
// 这里可以记录日志或者执行其他的恢复操作
}
}()
retItem := &baseinfo.SnsMediaItem{}
if snsVideoResponse == nil {
return retItem
}
if mediaInfo == nil {
return retItem
}
retItem.ID = 0
retItem.Type = mediaInfo.Type
retItem.Description = mediaInfo.Description
retItem.Private = baseinfo.MMSnsPrivacyPublic
retItem.UserData = mediaInfo.UserData
retItem.SubType = mediaInfo.SubType
retItem.VideoWidth = mediaInfo.VideoSize.Width
retItem.VideoHeight = mediaInfo.VideoSize.Height
retItem.URL = snsVideoResponse.FileURL
retItem.URL = strings.ReplaceAll(retItem.URL, "&", "&amp;")
retItem.URLType = mediaInfo.URL.Type
retItem.MD5 = snsVideoResponse.ReqData.RawFileMD5
retItem.VideoMD5 = snsVideoResponse.ReqData.Mp4Identify
retItem.Thumb = snsVideoResponse.ThumbURL
retItem.Thumb = strings.ReplaceAll(retItem.Thumb, "&", "&amp;")
retItem.ThumType = mediaInfo.Thumb.Type
retItem.SizeWidth = mediaInfo.Size.Width
retItem.SizeHeight = mediaInfo.Size.Height
retItem.TotalSize = mediaInfo.Size.TotalSize
retItem.VideoDuration = mediaInfo.VideoDuration
return retItem
}
+268
View File
@@ -0,0 +1,268 @@
package wxtask
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strconv"
"sync"
"xiawan/wx/protobuf/wechat"
"xiawan/wx/srv/wxface"
"github.com/gorilla/websocket"
)
// 全局写锁映射,保护每个连接的写操作
var connWriteMutexes sync.Map
// decodeUnicodeEscapes 解码Unicode转义字符
func decodeUnicodeEscapes(s string) string {
// 处理JSON中的Unicode转义字符,如\u003c
re := regexp.MustCompile(`\\u([0-9a-fA-F]{4})`)
result := re.ReplaceAllStringFunc(s, func(match string) string {
// 提取十六进制数字部分
hexStr := match[2:] // 去掉\u前缀
// 将十六进制转换为整数
if codePoint, err := strconv.ParseInt(hexStr, 16, 32); err == nil {
// 转换为Unicode字符
return string(rune(codePoint))
}
return match // 如果转换失败,返回原字符串
})
return result
}
// 长链接同步消息
type WXSocketMsgTask struct {
WxConn wxface.IWXConnect
// 待消费消息列表
MsgItemChan chan *wechat.AddMsg
// 可不可以消费下一条
canDoNext chan bool
// 结束标志
EndChan chan bool
// 是否正在运行
isRunning bool
// 是否启用WebSocket
wsEnabled bool
// 当前在消费的消息
CurrentMsgItem *wechat.AddMsg
// 存储所有连接的客户端
Clients map[string]*websocket.Conn
Upgrader websocket.Upgrader
// 定义一个互斥锁; 用于保护 Clients 的线程安全
//mu sync.Mutex
// 定义一个读写互斥锁; 用于保护 Clients 的线程安全
mu sync.RWMutex
}
// NewWXSocketMsgTask 新建抢红包管理器
func NewWXSocketMsgTask(wxConn wxface.IWXConnect) *WXSocketMsgTask {
return &WXSocketMsgTask{
WxConn: wxConn,
MsgItemChan: make(chan *wechat.AddMsg, 1000),
canDoNext: make(chan bool, 1),
EndChan: make(chan bool, 1),
isRunning: false,
wsEnabled: true, // 默认启用WebSocket功能,避免连接后延迟
Clients: make(map[string]*websocket.Conn),
Upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// 允许所有CORS跨域请求
CheckOrigin: func(r *http.Request) bool {
return true
},
},
mu: sync.RWMutex{},
}
}
func (wsmsg *WXSocketMsgTask) grapMsg() {
for {
select {
case wsmsg.CurrentMsgItem = <-wsmsg.MsgItemChan:
key := wsmsg.WxConn.GetWXAccount().GetUserInfo().UUID
// 使用消息包装器包含UUID信息
msgWrapper := NewMessageWrapper(key, wsmsg.CurrentMsgItem, "message")
jsonBytes, err := json.Marshal(msgWrapper)
if err != nil {
fmt.Println("消息序列化错误:", err)
continue // 继续处理下一条消息,而不是返回
}
conn := wsmsg.GetWebSocket(key)
if conn == nil {
fmt.Println("WebSocket连接不存在:", key)
continue // 继续处理下一条消息,而不是返回
}
// 对JSON内容进行Unicode解码处理
decodedJSON := decodeUnicodeEscapes(string(jsonBytes))
err = PushMessageToClient(decodedJSON, conn)
if err != nil {
fmt.Println("消息推送失败:", err)
wsmsg.DeleteWebSocket(key)
continue // 继续处理下一条消息,而不是返回
}
// 消息推送成功后继续处理下一条
case <-wsmsg.EndChan:
wsmsg.DeleteWebSocket(wsmsg.WxConn.GetWXAccount().GetUserInfo().UUID)
return
}
}
}
// Start 开始
func (wsmsg *WXSocketMsgTask) Start() {
if wsmsg.wsEnabled {
if wsmsg.isRunning {
return
}
wsmsg.isRunning = true
go wsmsg.grapMsg() // 直接启动消息处理循环
}
}
// Stop 结束协程
func (wsmsg *WXSocketMsgTask) Stop() {
if !wsmsg.isRunning {
return
}
wsmsg.isRunning = false
wsmsg.EndChan <- true
}
// GrapNext 抢下一个
func (wsmsg *WXSocketMsgTask) GrapNext() {
wsmsg.canDoNext <- true
}
// AddMsgItem 添加消息项
func (wsmsg *WXSocketMsgTask) AddMsgItem(msgItem *wechat.AddMsg) {
if !wsmsg.wsEnabled {
return
}
if !wsmsg.isRunning {
wsmsg.Start()
}
key := wsmsg.WxConn.GetWXAccount().GetUserInfo().UUID
conn := wsmsg.GetWebSocket(key)
// 打印日志
// fmt.Println("------------收到消息-----------\n",
// "NickName: ",wsmsg.WxConn.GetWXAccount().GetUserInfo().NickName,"\n",
// "Key: ",key,"\n",
// "Content: ",msgItem.Content.GetStr(),
// "\n",
// "----------------------------------")
if conn == nil {
// 只有 ws 长链接存在才会添加 收到的消息记录
// fmt.Println("WebSocket连接不存在,消息将不会发送:", key)
return
}
wsmsg.MsgItemChan <- msgItem
}
// GetCurrentMsgItem 获取当前正在消费的消息项
func (wsmsg *WXSocketMsgTask) GetCurrentMsgItem() *wechat.AddMsg {
return wsmsg.CurrentMsgItem
}
func PushMessageToClient(msg string, conn *websocket.Conn) error {
// 获取或创建当前连接的写锁
val, _ := connWriteMutexes.LoadOrStore(conn, &sync.Mutex{})
mutex := val.(*sync.Mutex)
// 使用互斥锁保护写操作
mutex.Lock()
defer mutex.Unlock()
// 使用 WebSocket 连接向客户端发送消息
fmt.Println("发送消息:", msg)
err := conn.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
// 如果发送错误,清理锁
connWriteMutexes.Delete(conn)
}
return err
}
// PushTypedMessageToClient 安全地发送特定类型的WebSocket消息
func PushTypedMessageToClient(messageType int, data []byte, conn *websocket.Conn) error {
// 获取或创建当前连接的写锁
val, _ := connWriteMutexes.LoadOrStore(conn, &sync.Mutex{})
mutex := val.(*sync.Mutex)
// 使用互斥锁保护写操作
mutex.Lock()
defer mutex.Unlock()
// 使用 WebSocket 连接向客户端发送消息
fmt.Printf("发送类型[%d]消息\n", messageType)
err := conn.WriteMessage(messageType, data)
if err != nil {
// 如果发送错误,清理锁
connWriteMutexes.Delete(conn)
}
return err
}
// GetWebSocket 获取 Clients 缓存中的 ws 长链接
func (wsmsg *WXSocketMsgTask) GetWebSocket(key string) (conn *websocket.Conn) {
wsmsg.mu.RLock() // 读取 Clients 时使用读锁
defer wsmsg.mu.RUnlock() // 解锁读锁
conn = wsmsg.Clients[key] // 读取 ws 长链接; 若 key 不存在则返回 nil
return conn
}
// UpdateWebSocket 更新 Clients 缓存中的 ws 长链接
func (wsmsg *WXSocketMsgTask) UpdateWebSocket(key string, conn *websocket.Conn) {
wsmsg.mu.Lock() // 写入或删除 Clients 时使用写锁
defer wsmsg.mu.Unlock() // 解锁
wsmsg.Clients[key] = conn // 更新 ws 长链接
wsmsg.wsEnabled = true // 当连接建立时启用WebSocket功能
fmt.Println("WebSocket连接已更新:", key)
}
// DeleteWebSocket 删除 Clients 缓存中的 ws 长链接
func (wsmsg *WXSocketMsgTask) DeleteWebSocket(key string) {
wsmsg.mu.Lock() // 写入或删除 Clients 时使用写锁
defer wsmsg.mu.Unlock() // 解锁
if conn, ok := wsmsg.Clients[key]; ok {
// 清理连接的写锁
connWriteMutexes.Delete(conn)
delete(wsmsg.Clients, key) // 删除 ws 长链接
fmt.Println("WebSocket连接已删除:", key)
// 如果没有连接,禁用WebSocket功能并停止任务
if len(wsmsg.Clients) == 0 {
wsmsg.wsEnabled = false
if wsmsg.isRunning {
wsmsg.Stop()
}
}
}
}
// 获取WebSocket启用状态
func (wsmsg *WXSocketMsgTask) IsWebSocketEnabled() bool {
return wsmsg.wsEnabled
}
// 设置WebSocket启用状态
func (wsmsg *WXSocketMsgTask) SetWebSocketEnabled(enabled bool) {
wsmsg.wsEnabled = enabled
// 如果禁用而且正在运行,停止任务
if !enabled && wsmsg.isRunning {
wsmsg.Stop()
} else if enabled && !wsmsg.isRunning && len(wsmsg.Clients) > 0 {
// 如果启用而且有客户端连接但未运行,启动任务
wsmsg.Start()
}
}
+68
View File
@@ -0,0 +1,68 @@
package wxtask
import (
"sync"
"xiawan/wx/srv/wxface"
)
// WXVerifyTask 加我/拉我进群聊是否需要验证
type WXVerifyTask struct {
wxConn wxface.IWXConnect
// 结束标志
notNeedVerify bool
// 自动入群邀请
autoJoinGroup bool
// 互斥锁
lock sync.Mutex
}
// NewWXVerifyTask 新建朋友圈任务管理器
func NewWXVerifyTask(wxConn wxface.IWXConnect) *WXVerifyTask {
return &WXVerifyTask{
wxConn: wxConn,
notNeedVerify: true,
autoJoinGroup: false,
}
}
// IsNeedVerify 判断是否需要验证
func (wxvt *WXVerifyTask) IsNeedVerify() bool {
wxvt.lock.Lock()
defer wxvt.lock.Unlock()
return wxvt.notNeedVerify
}
// SetNeedVerify 设置被添加,被拉入群聊时是否需要验证
func (wxvt *WXVerifyTask) SetNeedVerify(needVerify bool) {
wxvt.notNeedVerify = needVerify
}
// 设置 autoJoinGroup
func (wxvt *WXVerifyTask) SetAutoJoinGroup(autoJoinGroup bool) {
wxvt.lock.Lock()
defer wxvt.lock.Unlock()
wxvt.autoJoinGroup = autoJoinGroup
}
// 获取 autoJoinGroup
func (wxvt *WXVerifyTask) GetAutoJoinGroup() bool {
wxvt.lock.Lock()
defer wxvt.lock.Unlock()
return wxvt.autoJoinGroup
}
// 判断 autoJoinGroup
func (wxvt *WXVerifyTask) IsAutoJoinGroup() bool {
wxvt.lock.Lock()
defer wxvt.lock.Unlock()
return wxvt.autoJoinGroup
}
// 入群
func (wxvt *WXVerifyTask) AddJoinGroup(url string) error {
wxvt.lock.Lock()
defer wxvt.lock.Unlock()
_, err := wxvt.wxConn.GetWXReqInvoker().ConsentToJoinGroupRequest(url)
return err
}