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 }