Files
2026-02-17 13:06:23 +08:00

534 lines
17 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package clientsdk
import (
"encoding/xml"
"errors"
"fmt"
"strings"
"time"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/clientsdk/baseutils"
"xiawan/wx/clientsdk/cecdh"
"xiawan/wx/clientsdk/mmtls"
"xiawan/wx/protobuf/wechat"
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
)
// 二维码登录测试
func qrcodeLoginDemo() *baseinfo.UserInfo {
userInfo := NewUserInfo(uuid.New().String(), nil, baseinfo.ClientVersion, nil)
// 长链接初始化MMTLS
dialer := GetDialer(userInfo)
tmpMMInfo, err := mmtls.InitMMTLSInfoLong(dialer, userInfo.LongHost, userInfo.ShortHost, nil)
if err != nil {
fmt.Println(err)
return nil
}
userInfo.MMInfo = tmpMMInfo
// 获取登录二维码
qrcodeResponse, err := getLoginQRCodeDemo(userInfo)
if err != nil {
fmt.Println(err)
return nil
}
// 二维码写入到文件
baseutils.WriteToFile(qrcodeResponse.Qrcode.Src, "./qrcode.png")
// 二维码信息
qrcodeUUID := qrcodeResponse.GetUuid()
qrcodeKey := qrcodeResponse.GetAes().GetKey()
// 检测扫码状态
bRet, err := checkQrcodeAndLoginDemo(userInfo, qrcodeUUID, qrcodeKey)
if !bRet {
fmt.Println("登录失败:", err)
return nil
}
fmt.Println("登录成功")
return userInfo
}
// 获取二维码请求示例,这里分开处理发送请求和解析响应,有利于统一长短连接处理数据的方式
func getLoginQRCodeDemo(userInfo *baseinfo.UserInfo) (*wechat.LoginQRCodeResponse, error) {
// 发送获取二维码请求,得到响应数据
packHeader, err := SendLoginQRCodeRequest(userInfo)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeGetLoginQRCode {
return nil, errors.New("getLoginQRCodeDemo err: URLID != baseinfo.MMRequestTypeGetLoginQRCode")
}
// 解析数据
response := &wechat.LoginQRCodeResponse{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
// checkQrcodeAndLoginDemo 检测扫码状态,然后登录
func checkQrcodeAndLoginDemo(userInfo *baseinfo.UserInfo, qrcodeUUID string, qrcodeKey []byte) (bool, error) {
lgQrNotify, err := checkQrcodeDemo(userInfo, qrcodeUUID, qrcodeKey)
baseutils.ShowObjectValue(lgQrNotify)
if err != nil {
return false, err
}
// 没有检测到扫码状态 继续检测
if lgQrNotify.Wxid == nil && lgQrNotify.Wxnewpass == nil {
// 判断二维码是否过期
qrcodeValidTime := lgQrNotify.GetEffectiveTime()
if qrcodeValidTime <= 0 {
return false, errors.New("checkQrcodeAndLoginDemo err: qrcode invalid now")
}
// 暂停1秒
time.Sleep(time.Duration(1) * time.Second)
return checkQrcodeAndLoginDemo(userInfo, qrcodeUUID, qrcodeKey)
}
// 发送登陆请求
return checkManualAuthDemo(userInfo, lgQrNotify.GetWxnewpass(), lgQrNotify.GetWxid())
}
// 检测扫码状态, 这里要用长链接去检测,因为微信是采用长链接
func checkQrcodeDemo(userInfo *baseinfo.UserInfo, qrcodeUUID string, qrcodeKey []byte) (*wechat.LoginQRCodeNotify, error) {
// 先获取要发送的数据
reqBytes, err := GetCheckLoginQRCodeRequest(userInfo, qrcodeUUID, qrcodeKey)
if err != nil {
return nil, err
}
// 长链接发送
err = mmtls.MMTCPSendReq(userInfo.MMInfo, mmtls.MMLongOperationCheckQrcode, reqBytes)
if err != nil {
return nil, err
}
// 长链接接收数据, 应该用异步去接收这里只是写个demo
recvInfo, err := mmtls.MMTCPRecvData(userInfo.MMInfo)
if err != nil {
return nil, err
}
// 系统推送,
if recvInfo.HeaderInfo.Operation < 1000000000 {
// 收到这个消息要发同步请求,去同步数据
return nil, errors.New("checkQrcodeDemo err: URLID != baseinfo.MMRequestTypeCheckLoginQRCode")
}
// 解析检测二维码状态响应数据
packHeader, err := DecodePackHeader(recvInfo.RespData, nil)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeCheckLoginQRCode {
return nil, errors.New("checkQrcodeDemo err: URLID != baseinfo.MMRequestTypeCheckLoginQRCode")
}
// 解析数据
response := &wechat.CheckLoginQRCodeResponse{}
err = ParseResponseData(userInfo, packHeader, response)
if err != nil {
return nil, err
}
// 解密LoginQRCodeNotify
retBytes, err := baseutils.AesDecryptByteKey(response.LoginQrcodeNotifyPkg.NotifyData.Buffer, qrcodeKey)
if err != nil {
return nil, err
}
lgQrNotify := &wechat.LoginQRCodeNotify{}
err = proto.Unmarshal(retBytes, lgQrNotify)
return lgQrNotify, err
}
// 发送登录请求
func checkManualAuthDemo(userInfo *baseinfo.UserInfo, newpass string, wxid string) (bool, error) {
authResponse, err := manualAuthDemo(userInfo, newpass, wxid)
if err != nil {
return false, err
}
// 重定向
if authResponse.GetBaseResponse().GetRet() == baseinfo.MMErrIdcRedirect {
// 修改服务器地址,然后重新发送登录包
userInfo.ShortHost = authResponse.GetDnsInfo().GetNewHostList().GetList()[1].GetSubstitute()
userInfo.LongHost = authResponse.GetDnsInfo().GetNewHostList().GetList()[0].GetSubstitute()
// 关闭长链接
if userInfo.MMInfo.Conn != nil {
userInfo.MMInfo.Conn.Close()
}
// 重新初始化MMTLS
dialer := GetDialer(userInfo)
tmpMMInfo, err := mmtls.InitMMTLSInfoLong(dialer, userInfo.LongHost, userInfo.ShortHost, nil)
if err != nil {
return false, err
}
userInfo.MMInfo = tmpMMInfo
return checkManualAuthDemo(userInfo, newpass, wxid)
} else if authResponse.GetBaseResponse().GetRet() == baseinfo.MMOk {
// 登录成功处理
myWxid := authResponse.AccountInfo.Wxid
if myWxid != nil {
userInfo.WxId = *myWxid
}
// 获取aesKey
ecServerPubKey := authResponse.AuthParam.EcdhKey.Key.GetBuffer()
userInfo.CheckSumKey = cecdh.ComputerECCKeyMD5(ecServerPubKey, userInfo.EcPrivateKey)
tmpAesKey, err := baseutils.AesDecryptByteKey(authResponse.AuthParam.SessionKey.Key, userInfo.CheckSumKey)
if err != nil {
return false, err
}
userInfo.SessionKey = tmpAesKey
// autoauthKey, token登录需要用到的key
userInfo.AutoAuthKey = authResponse.AuthParam.AutoAuthKey.Buffer
return true, nil
}
return false, errors.New("登录发生未知错误")
}
// 发送登录请求
func manualAuthDemo(userInfo *baseinfo.UserInfo, newpass string, wxid string) (*wechat.ManualAuthResponse, error) {
packHeader, err := SendManualAuth(userInfo, newpass, wxid)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeManualAuth {
return nil, errors.New("manualAuthDemo err: URLID != baseinfo.MMRequestTypeManualAuth")
}
// 解析数据
response := &wechat.ManualAuthResponse{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
// 获取账号信息
func getProfileDemo(userInfo *baseinfo.UserInfo) (*wechat.GetProfileResponse, error) {
packHeader, err := SendGetProfileRequest(userInfo)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeGetProfile {
return nil, errors.New("getProfileDemo err: URLID != baseinfo.MMRequestTypeGetProfile")
}
// 解析数据
response := &wechat.GetProfileResponse{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
// 同步信息,好友,消息,等等,任何信息都能同步到(例如:你在手机上给好友发个信息这里就可以同步到, 添加好友,接收到消息,等等)
func NewSyncDemo(userInfo *baseinfo.UserInfo) {
for {
syncResp, err := newSyncRequestDemo(userInfo, 3, userInfo.SyncKey)
if err != nil {
fmt.Println(err)
return
}
// 跟新同步Key
userInfo.SyncKey = syncResp.GetKeyBuf().GetBuffer()
// 如果没有同步到数据则返回
cmdList := syncResp.GetCmdList()
syncCount := cmdList.GetCount()
fmt.Println("同步到消息条数--->", syncCount)
if syncCount <= 0 {
return
}
// 遍历同步到的每条信息
itemList := cmdList.GetItemList()
for index := uint32(0); index < syncCount; index++ {
item := itemList[index]
itemID := item.GetCmdId()
// 同步到联系人
if itemID == baseinfo.CmdIDModContact {
contact := new(wechat.ModContact)
err := proto.Unmarshal(item.CmdBuf.Data, contact)
if err != nil {
continue
}
fmt.Print(contact)
// 判断contact是否是群 == 0 不是群
if contact.GetChatroomVersion() == 0 {
continue
}
// 被移除群聊
if contact.GetChatRoomNotify() == 0 {
fmt.Println("消息免打扰群, 群wxid = ", contact.GetUserName().GetStr(), " 群昵称:", contact.GetNickName().GetStr())
} else {
fmt.Println("微信群, 群wxid = ", contact.GetUserName().GetStr(), " 群昵称:", contact.GetNickName().GetStr())
}
}
// 同步到消息
if itemID == baseinfo.CmdIDAddMsg {
var addMsg wechat.AddMsg
fmt.Println(addMsg)
// 判断接收人是不是群
userName := addMsg.GetToUserName().GetStr()
if !strings.HasSuffix(userName, "@chatroom") {
continue
}
// 首先类型:要是引用
if addMsg.GetMsgType() != baseinfo.MMAddMsgTypeRefer {
continue
}
// 解析引用的消息
tmpMsg := new(baseinfo.Msg)
err = xml.Unmarshal([]byte(addMsg.GetContent().GetStr()), tmpMsg)
if err != nil {
continue
}
// 判断是否支付类型
if tmpMsg.APPMsg.MsgType != baseinfo.MMAppMsgTypePayInfo {
continue
}
// 判断是否红包类型
if tmpMsg.APPMsg.WCPayInfo.SceneID != baseinfo.MMPayInfoSceneIDHongBao {
continue
}
// // 抢红包操作
// hbItem := new(baseinfo.HongBaoItem)
// hbItem.GroupWxid = userName
// hbItem.NativeURL = tmpMsg.APPMsg.WCPayInfo.NativeURL
// hongBaoURLItem, err := ParseHongBaoURL(hbItem.NativeURL)
// if err != nil {
// continue
// }
// hbItem.URLItem = hongBaoURLItem
// // 这里可以发打开红包请求, 后面添加
}
// 还有很多消息类型, 可以自己打印出来看看
}
}
}
// NewSyncDemo 同步请求
func newSyncRequestDemo(userInfo *baseinfo.UserInfo, scene uint32, syncKey []byte) (*wechat.NewSyncResponse, error) {
// 发送同步消息
packHeader, err := SendNewSyncRequest(userInfo, scene)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeNewSync {
return nil, errors.New("getProfileDemo err: URLID != baseinfo.MMRequestTypeGetProfile")
}
// 解析数据
response := &wechat.NewSyncResponse{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
// initContactListDemo 初始化通讯录列表
func initContactListDemo(userInfo *baseinfo.UserInfo) {
// 第一步先获取所有的好友公众号微信ID列表
userNameList := make([]string, 0)
contactSeq := uint32(0)
for {
// 先获取所有好友公众号的微信ID列表
contactResp, err := initContactReqDemo(userInfo, contactSeq)
if err != nil {
fmt.Println(err)
return
}
contactSeq = contactResp.GetCurrentWxcontactSeq()
// 好友微信id列表
contactUserNameList := contactResp.GetContactUsernameList()
userNameList = append(userNameList, contactUserNameList[0:]...)
if len(contactUserNameList) < 100 {
break
}
}
// 分批获取所有 微信ID 对应的详细信息, 一次最多获取20个
userCount := len(userNameList)
offset := 0
for offset < userCount {
tmpCount := 20
if offset+tmpCount > userCount {
tmpCount = userCount - offset
}
// 批量(一次最多获取20个)根据微信号获取 对应的详细信息(好友、群、公众号)
tmpUserWxidList := userNameList[offset : offset+tmpCount]
briefResp, err := batchGetContactBriefInfoReqDemo(userInfo, tmpUserWxidList)
if err != nil {
fmt.Println(err)
break
}
// 遍历打印结果,
contactList := briefResp.GetContactList()
contactCount := len(contactList)
for tmpIndex := 0; tmpIndex < contactCount; tmpIndex++ {
contactItem := contactList[tmpIndex]
tmpContact := contactItem.GetContact()
tmpUserName := tmpContact.GetUserName().GetStr()
tmpNickName := tmpContact.GetNickName().GetStr()
// 暂时可以这样判断
if tmpContact.CustomizedInfo != nil {
fmt.Println("公众号:", tmpUserName, " ", tmpNickName)
} else {
fmt.Println("好友:", tmpUserName, " ", tmpNickName)
}
}
offset = offset + tmpCount
}
}
// 初始化通讯录
func initContactReqDemo(userInfo *baseinfo.UserInfo, contactSeq uint32) (*wechat.InitContactResp, error) {
// 发送初始化通讯录请求
packHeader, err := SendInitContactReq(userInfo, contactSeq)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeInitContact {
return nil, errors.New("initContactReqDemo err: URLID != baseinfo.MMRequestTypeInitContact")
}
// 解析数据
response := &wechat.InitContactResp{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
func batchGetContactBriefInfoReqDemo(userInfo *baseinfo.UserInfo, userNameList []string) (*wechat.BatchGetContactBriefInfoResp, error) {
// 发送同步消息
packHeader, err := SendBatchGetContactBriefInfoReq(userInfo, userNameList)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeBatchGetContactBriefInfo {
return nil, errors.New("batchGetContactBriefInfoReqDemo err: URLID != baseinfo.MMRequestTypeBatchGetContactBriefInfo")
}
// 解析数据
response := &wechat.BatchGetContactBriefInfoResp{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
// token登录Demo代码
func tokenLoginDemo(userInfo *baseinfo.UserInfo) error {
// 关闭长链接
if userInfo.MMInfo.Conn != nil {
userInfo.MMInfo.Conn.Close()
}
// 重新初始化MMTLS
dialer := GetDialer(userInfo)
tmpMMInfo, err := mmtls.InitMMTLSInfoLong(dialer, userInfo.LongHost, userInfo.ShortHost, nil)
if err != nil {
return err
}
userInfo.MMInfo = tmpMMInfo
authResp, err := autoAuthDemo(userInfo)
if err != nil {
return err
}
if authResp.GetBaseResponse().GetRet() == baseinfo.MMOk {
// 登录成功处理
myWxid := authResp.AccountInfo.Wxid
if myWxid != nil {
userInfo.WxId = *myWxid
}
// 获取aesKey
ecServerPubKey := authResp.AuthParam.EcdhKey.Key.GetBuffer()
userInfo.CheckSumKey = cecdh.ComputerECCKeyMD5(ecServerPubKey, userInfo.EcPrivateKey)
tmpAesKey, err := baseutils.AesDecryptByteKey(authResp.AuthParam.SessionKey.Key, userInfo.CheckSumKey)
if err != nil {
return err
}
userInfo.SessionKey = tmpAesKey
// autoauthKey, token登录需要用到的key
userInfo.AutoAuthKey = authResp.AuthParam.AutoAuthKey.Buffer
return nil
}
return errors.New("Token登录失败")
}
// 发送token登录请求
func autoAuthDemo(userInfo *baseinfo.UserInfo) (*wechat.ManualAuthResponse, error) {
packHeader, err := SendAutoAuthRequest(userInfo)
if err != nil {
return nil, err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeAutoAuth {
return nil, errors.New("batchGetContactBriefInfoReqDemo err: URLID != baseinfo.MMRequestTypeBatchGetContactBriefInfo")
}
// 解析数据
response := &wechat.ManualAuthResponse{}
err = ParseResponseData(userInfo, packHeader, response)
return response, err
}
func testGetCDNDnsInfo(userInfo *baseinfo.UserInfo) error {
packHeader, err := SendGetCDNDnsRequest(userInfo)
if err != nil {
return err
}
// 这里做判断, 这里肯定是相等的,因为知道调用的是这个请求,
// 这是给个示例处理方式以后兼容长链接有用根据URLID来判断是什么请求
if packHeader.URLID != baseinfo.MMRequestTypeGetCdnDNS {
return errors.New("SendGetCDNDnsRequest err: URLID != baseinfo.MMRequestTypeAutoAuth")
}
// 解析数据
response := &wechat.GetCDNDnsResponse{}
err = ParseResponseData(userInfo, packHeader, response)
if err != nil {
return err
}
userInfo.DNSInfo = response.GetDnsInfo()
userInfo.APPDnsInfo = response.GetAppDnsInfo()
userInfo.SNSDnsInfo = response.GetSnsDnsInfo()
userInfo.FAKEDnsInfo = response.GetFakeDnsInfo()
return nil
}