package client import ( "bytes" "crypto/md5" "fmt" "io" "math" "math/rand" "net" "runtime/debug" "sort" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary/jce" "github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/protocol/packets" "github.com/Mrs4s/MiraiGo/utils" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" ) var json = jsoniter.ConfigFastest //go:generate go run github.com/a8m/syncmap -o "handler_map_gen.go" -pkg client -name HandlerMap "map[uint16]*handlerInfo" type QQClient struct { Uin int64 PasswordMd5 [16]byte AllowSlider bool Nickname string Age uint16 Gender uint16 FriendList []*FriendInfo GroupList []*GroupInfo OnlineClients []*OtherClientInfo Online bool NetLooping bool SequenceId int32 OutGoingPacketSessionId []byte RandomKey []byte Conn net.Conn ConnectTime time.Time handlers HandlerMap waiters sync.Map servers []*net.TCPAddr currServerIndex int retryTimes int version *versionInfo dpwd []byte syncCookie []byte pubAccountCookie []byte msgCtrlBuf []byte ksid []byte t104 []byte t174 []byte g []byte t402 []byte t150 []byte t149 []byte t528 []byte t530 []byte rollbackSig []byte randSeed []byte // t403 timeDiff int64 sigInfo *loginSigInfo highwaySession *highwaySessionInfo srvSsoAddrs []string otherSrvAddrs []string fileStorageInfo *jce.FileStoragePushFSSvcList pwdFlag bool lastMessageSeq int32 //lastMessageSeqTmp sync.Map msgSvcCache *utils.Cache transCache *utils.Cache lastLostMsg string groupSysMsgCache *GroupSystemMessages groupMsgBuilders sync.Map onlinePushCache *utils.Cache requestPacketRequestId int32 groupSeq int32 friendSeq int32 heartbeatEnabled bool groupDataTransSeq int32 highwayApplyUpSeq int32 eventHandlers *eventHandlers stat *Statistics groupListLock sync.Mutex } type loginSigInfo struct { loginBitmap uint64 tgt []byte tgtKey []byte srmToken []byte // study room manager | 0x16a t133 []byte encryptedA1 []byte userStKey []byte userStWebSig []byte sKey []byte sKeyExpiredTime int64 d2 []byte d2Key []byte wtSessionTicketKey []byte deviceToken []byte psKeyMap map[string][]byte pt4TokenMap map[string][]byte } type handlerInfo struct { fun func(i interface{}, err error) params requestParams } var decoders = map[string]func(*QQClient, *incomingPacketInfo, []byte) (interface{}, error){ "wtlogin.login": decodeLoginResponse, "wtlogin.exchange_emp": decodeExchangeEmpResponse, "StatSvc.register": decodeClientRegisterResponse, "StatSvc.ReqMSFOffline": decodeMSFOfflinePacket, "MessageSvc.PushNotify": decodeSvcNotify, "OnlinePush.ReqPush": decodeOnlinePushReqPacket, "OnlinePush.PbPushTransMsg": decodeOnlinePushTransPacket, "ConfigPushSvc.PushReq": decodePushReqPacket, "MessageSvc.PbGetMsg": decodeMessageSvcPacket, "MessageSvc.PushForceOffline": decodeForceOfflinePacket, "PbMessageSvc.PbMsgWithDraw": decodeMsgWithDrawResponse, "friendlist.getFriendGroupList": decodeFriendGroupListResponse, "friendlist.GetTroopListReqV2": decodeGroupListResponse, "friendlist.GetTroopMemberListReq": decodeGroupMemberListResponse, "group_member_card.get_group_member_card_info": decodeGroupMemberInfoResponse, "PttStore.GroupPttUp": decodeGroupPttStoreResponse, "LongConn.OffPicUp": decodeOffPicUpResponse, "ProfileService.Pb.ReqSystemMsgNew.Group": decodeSystemMsgGroupPacket, "ProfileService.Pb.ReqSystemMsgNew.Friend": decodeSystemMsgFriendPacket, "OidbSvc.0xe07_0": decodeImageOcrResponse, "OidbSvc.0xd79": decodeWordSegmentation, "OidbSvc.0x990": decodeTranslateResponse, "SummaryCard.ReqSummaryCard": decodeSummaryCardResponse, "LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse, } func init() { rand.Seed(time.Now().UTC().UnixNano()) } // NewClient create new qq client func NewClient(uin int64, password string) *QQClient { return NewClientMd5(uin, md5.Sum([]byte(password))) } func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { cli := &QQClient{ Uin: uin, PasswordMd5: passwordMd5, SequenceId: 0x3635, AllowSlider: true, RandomKey: make([]byte, 16), OutGoingPacketSessionId: []byte{0x02, 0xB0, 0x5B, 0x8B}, sigInfo: &loginSigInfo{}, requestPacketRequestId: 1921334513, groupSeq: int32(rand.Intn(20000)), friendSeq: 22911, highwayApplyUpSeq: 77918, ksid: []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", SystemDeviceInfo.IMEI)), eventHandlers: &eventHandlers{}, msgSvcCache: utils.NewCache(time.Second * 15), transCache: utils.NewCache(time.Second * 15), onlinePushCache: utils.NewCache(time.Second * 15), version: genVersionInfo(SystemDeviceInfo.Protocol), servers: []*net.TCPAddr{}, stat: &Statistics{}, } sso, err := getSSOAddress() if err == nil && len(sso) > 0 { cli.servers = append(sso, cli.servers...) } adds, err := net.LookupIP("msfwifi.3g.qq.com") // host servers if err == nil && len(adds) > 0 { var hostAddrs []*net.TCPAddr for _, addr := range adds { hostAddrs = append(hostAddrs, &net.TCPAddr{ IP: addr, Port: 8080, }) } cli.servers = append(hostAddrs, cli.servers...) } if len(cli.servers) == 0 { cli.servers = []*net.TCPAddr{ // default servers {IP: net.IP{42, 81, 172, 81}, Port: 80}, {IP: net.IP{114, 221, 148, 59}, Port: 14000}, {IP: net.IP{42, 81, 172, 147}, Port: 443}, {IP: net.IP{125, 94, 60, 146}, Port: 80}, {IP: net.IP{114, 221, 144, 215}, Port: 80}, {IP: net.IP{42, 81, 172, 22}, Port: 80}, } } pings := make([]int64, len(cli.servers)) wg := sync.WaitGroup{} wg.Add(len(cli.servers)) for i := range cli.servers { go func(index int) { defer wg.Done() p, err := qualityTest(cli.servers[index]) if err != nil { pings[index] = 9999 return } pings[index] = p }(i) } wg.Wait() sort.Slice(cli.servers, func(i, j int) bool { return pings[i] < pings[j] }) if len(cli.servers) > 3 { cli.servers = cli.servers[0 : len(cli.servers)/2] // 保留ping值中位数以上的server } rand.Read(cli.RandomKey) return cli } // Login send login request func (c *QQClient) Login() (*LoginResponse, error) { if c.Online { return nil, ErrAlreadyOnline } err := c.connect() if err != nil { return nil, err } go c.netLoop() rsp, err := c.sendAndWait(c.buildLoginPacket()) if err != nil { c.Disconnect() return nil, err } l := rsp.(LoginResponse) if l.Success { c.init() } return &l, nil } // SubmitCaptcha send captcha to server func (c *QQClient) SubmitCaptcha(result string, sign []byte) (*LoginResponse, error) { seq, packet := c.buildCaptchaPacket(result, sign) rsp, err := c.sendAndWait(seq, packet) if err != nil { c.Disconnect() return nil, err } l := rsp.(LoginResponse) if l.Success { c.init() } return &l, nil } func (c *QQClient) SubmitTicket(ticket string) (*LoginResponse, error) { seq, packet := c.buildTicketSubmitPacket(ticket) rsp, err := c.sendAndWait(seq, packet) if err != nil { c.Disconnect() return nil, err } l := rsp.(LoginResponse) if l.Success { c.init() } return &l, nil } func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) { rsp, err := c.sendAndWait(c.buildSMSCodeSubmitPacket(code)) if err != nil { c.Disconnect() return nil, err } l := rsp.(LoginResponse) if l.Success { c.init() } return &l, nil } func (c *QQClient) RequestSMS() bool { rsp, err := c.sendAndWait(c.buildSMSRequestPacket()) if err != nil { c.Error("request sms error: %v", err) return false } return rsp.(LoginResponse).Error == SMSNeededError } func (c *QQClient) init() { if len(c.g) == 0 { c.Warning("device lock is disable. http api may fail.") } c.Online = true _ = c.registerClient() c.groupSysMsgCache, _ = c.GetGroupSystemMessages() if !c.heartbeatEnabled { go c.doHeartbeat() } _ = c.RefreshStatus() seq, pkt := c.buildGetMessageRequestPacket(msg.SyncFlag_START, time.Now().Unix()) _, _ = c.sendAndWait(seq, pkt, requestParams{"used_reg_proxy": true, "init": true}) c.stat.once.Do(func() { c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) { c.stat.MessageReceived++ c.stat.LastMessageTime = time.Now().Unix() }) c.OnPrivateMessage(func(_ *QQClient, _ *message.PrivateMessage) { c.stat.MessageReceived++ c.stat.LastMessageTime = time.Now().Unix() }) c.OnTempMessage(func(_ *QQClient, _ *message.TempMessage) { c.stat.MessageReceived++ c.stat.LastMessageTime = time.Now().Unix() }) c.onGroupMessageReceipt("internal", func(_ *QQClient, _ *groupMessageReceiptEvent) { c.stat.MessageSent++ }) }) } func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) { b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com")) if err != nil { return nil, err } ret := VipInfo{Uin: target} b = b[bytes.Index(b, []byte(``))+24:] t := b[:bytes.Index(b, []byte(``))] ret.Name = string(t) b = b[bytes.Index(b, []byte(`LV`))+17:] t = b[:bytes.Index(b, []byte(`

`))] ret.Level, _ = strconv.Atoi(string(t)) b = b[bytes.Index(b, []byte(`
`))+35:] b = b[bytes.Index(b, []byte(`

`))+3:] t = b[:bytes.Index(b, []byte(`倍`))] ret.LevelSpeed, _ = strconv.ParseFloat(string(t), 64) b = b[bytes.Index(b, []byte(`

`))+35:] b = b[bytes.Index(b, []byte(`

`))+3:] st := string(b[:bytes.Index(b, []byte(`

`))]) st = strings.Replace(st, "", "", 1) st = strings.Replace(st, "", "", 1) ret.VipLevel = st b = b[bytes.Index(b, []byte(`
`))+35:] b = b[bytes.Index(b, []byte(`

`))+3:] t = b[:bytes.Index(b, []byte(`

`))] ret.VipGrowthSpeed, _ = strconv.Atoi(string(t)) b = b[bytes.Index(b, []byte(`
`))+35:] b = b[bytes.Index(b, []byte(`

`))+3:] t = b[:bytes.Index(b, []byte(`

`))] ret.VipGrowthTotal, _ = strconv.Atoi(string(t)) return &ret, nil } func (c *QQClient) GetGroupHonorInfo(groupCode int64, honorType HonorType) (*GroupHonorInfo, error) { b, err := utils.HttpGetBytes(fmt.Sprintf("https://qun.qq.com/interactive/honorlist?gc=%d&type=%d", groupCode, honorType), c.getCookiesWithDomain("qun.qq.com")) if err != nil { return nil, err } b = b[bytes.Index(b, []byte(`window.__INITIAL_STATE__=`))+25:] b = b[:bytes.Index(b, []byte(""))] ret := GroupHonorInfo{} err = json.Unmarshal(b, &ret) if err != nil { return nil, err } return &ret, nil } func (c *QQClient) GetWordSegmentation(text string) ([]string, error) { rsp, err := c.sendAndWait(c.buildWordSegmentationPacket([]byte(text))) if err != nil { return nil, err } if data, ok := rsp.([][]byte); ok { var ret []string for _, val := range data { ret = append(ret, string(val)) } return ret, nil } return nil, errors.New("decode error") } func (c *QQClient) GetSummaryInfo(target int64) (*SummaryCardInfo, error) { rsp, err := c.sendAndWait(c.buildSummaryCardRequestPacket(target)) if err != nil { return nil, err } return rsp.(*SummaryCardInfo), nil } // ReloadFriendList refresh QQClient.FriendList field via GetFriendList() func (c *QQClient) ReloadFriendList() error { rsp, err := c.GetFriendList() if err != nil { return err } c.FriendList = rsp.List return nil } // GetFriendList request friend list func (c *QQClient) GetFriendList() (*FriendListResponse, error) { var curFriendCount = 0 r := &FriendListResponse{} for { rsp, err := c.sendAndWait(c.buildFriendGroupListRequestPacket(int16(curFriendCount), 150, 0, 0)) if err != nil { return nil, err } list := rsp.(FriendListResponse) r.TotalCount = list.TotalCount r.List = append(r.List, list.List...) curFriendCount += len(list.List) if int32(len(r.List)) >= r.TotalCount { break } } return r, nil } func (c *QQClient) GetForwardMessage(resId string) *message.ForwardMessage { m := c.DownloadForwardMessage(resId) if m == nil { return nil } var ( item *msg.PbMultiMsgItem ret = &message.ForwardMessage{Nodes: []*message.ForwardNode{}} ) for _, iter := range m.Items { if iter.GetFileName() == m.FileName { item = iter } } if item == nil { return nil } for _, m := range item.GetBuffer().GetMsg() { ret.Nodes = append(ret.Nodes, &message.ForwardNode{ SenderId: m.Head.GetFromUin(), SenderName: func() string { if m.Head.GetMsgType() == 82 && m.Head.GroupInfo != nil { return m.Head.GroupInfo.GetGroupCard() } return m.Head.GetFromNick() }(), Time: m.Head.GetMsgTime(), Message: message.ParseMessageElems(m.Body.RichText.Elems), }) } return ret } func (c *QQClient) DownloadForwardMessage(resId string) *message.ForwardElement { i, err := c.sendAndWait(c.buildMultiApplyDownPacket(resId)) if err != nil { return nil } multiMsg := i.(*msg.PbMultiMsgTransmit) if multiMsg.GetPbItemList() == nil { return nil } var pv string for i := 0; i < int(math.Min(4, float64(len(multiMsg.GetMsg())))); i++ { m := multiMsg.Msg[i] pv += fmt.Sprintf(`%s: %s`, func() string { if m.Head.GetMsgType() == 82 && m.Head.GroupInfo != nil { return m.Head.GroupInfo.GetGroupCard() } return m.Head.GetFromNick() }(), message.ToReadableString( message.ParseMessageElems(multiMsg.Msg[i].GetBody().GetRichText().Elems), ), ) } return genForwardTemplate( resId, pv, "群聊的聊天记录", "[聊天记录]", "聊天记录", fmt.Sprintf("查看 %d 条转发消息", len(multiMsg.GetMsg())), time.Now().UnixNano(), multiMsg.GetPbItemList(), ) } func (c *QQClient) SendGroupPoke(groupCode, target int64) { _, _ = c.sendAndWait(c.buildGroupPokePacket(groupCode, target)) } func (c *QQClient) SendFriendPoke(target int64) { _, _ = c.sendAndWait(c.buildFriendPokePacket(target)) } func (c *QQClient) ReloadGroupList() error { c.groupListLock.Lock() defer c.groupListLock.Unlock() list, err := c.GetGroupList() if err != nil { return err } c.GroupList = list return nil } func (c *QQClient) GetGroupList() ([]*GroupInfo, error) { rsp, err := c.sendAndWait(c.buildGroupListRequestPacket(EmptyBytes)) if err != nil { return nil, err } r := rsp.([]*GroupInfo) wg := sync.WaitGroup{} batch := 50 for i := 0; i < len(r); i += batch { k := i + batch if k > len(r) { k = len(r) } wg.Add(k - i) for j := i; j < k; j++ { go func(g *GroupInfo, wg *sync.WaitGroup) { defer wg.Done() m, err := c.GetGroupMembers(g) if err != nil { return } g.Members = m }(r[j], &wg) } wg.Wait() } return r, nil } func (c *QQClient) GetGroupMembers(group *GroupInfo) ([]*GroupMemberInfo, error) { var nextUin int64 var list []*GroupMemberInfo for { data, err := c.sendAndWait(c.buildGroupMemberListRequestPacket(group.Uin, group.Code, nextUin)) if err != nil { return nil, err } if data == nil { return nil, errors.New("group member list unavailable: rsp is nil") } rsp := data.(groupMemberListResponse) nextUin = rsp.NextUin for _, m := range rsp.list { m.Group = group if m.Uin == group.OwnerUin { m.Permission = Owner } } list = append(list, rsp.list...) if nextUin == 0 { return list, nil } } } func (c *QQClient) FindFriend(uin int64) *FriendInfo { for _, t := range c.FriendList { f := t if f.Uin == uin { return f } } return nil } func (c *QQClient) FindGroupByUin(uin int64) *GroupInfo { for _, g := range c.GroupList { f := g if f.Uin == uin { return f } } return nil } func (c *QQClient) FindGroup(code int64) *GroupInfo { for _, g := range c.GroupList { f := g if f.Code == code { return f } } return nil } func (c *QQClient) SolveGroupJoinRequest(i interface{}, accept, block bool, reason string) { if accept { block = false reason = "" } switch req := i.(type) { case *UserJoinGroupRequest: _, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.RequesterUin, req.GroupCode, func() int32 { if req.Suspicious { return 2 } else { return 1 } }(), false, accept, block, reason) _ = c.send(pkt) case *GroupInvitedRequest: _, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.InvitorUin, req.GroupCode, 1, true, accept, block, reason) _ = c.send(pkt) } } func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) { _, pkt := c.buildSystemMsgFriendActionPacket(req.RequestId, req.RequesterUin, accept) _ = c.send(pkt) } func (c *QQClient) getSKey() string { if c.sigInfo.sKeyExpiredTime < time.Now().Unix() && len(c.g) > 0 { c.Debug("skey expired. refresh...") _, _ = c.sendAndWait(c.buildRequestTgtgtNopicsigPacket()) } return string(c.sigInfo.sKey) } func (c *QQClient) getCookies() string { return fmt.Sprintf("uin=o%d; skey=%s;", c.Uin, c.getSKey()) } func (c *QQClient) getCookiesWithDomain(domain string) string { cookie := c.getCookies() if psKey, ok := c.sigInfo.psKeyMap[domain]; ok { return fmt.Sprintf("%s p_uin=o%d; p_skey=%s;", cookie, c.Uin, psKey) } else { return cookie } } func (c *QQClient) getCSRFToken() int { accu := 5381 for _, b := range c.sigInfo.sKey { accu = accu + (accu << 5) + int(b) } return 2147483647 & accu } func (c *QQClient) getMemberInfo(groupCode, memberUin int64) (*GroupMemberInfo, error) { info, err := c.sendAndWait(c.buildGroupMemberInfoRequestPacket(groupCode, memberUin)) if err != nil { return nil, err } return info.(*GroupMemberInfo), nil } func (c *QQClient) editMemberCard(groupCode, memberUin int64, card string) { _, _ = c.sendAndWait(c.buildEditGroupTagPacket(groupCode, memberUin, card)) } func (c *QQClient) editMemberSpecialTitle(groupCode, memberUin int64, title string) { _, _ = c.sendAndWait(c.buildEditSpecialTitlePacket(groupCode, memberUin, title)) } func (c *QQClient) setGroupAdmin(groupCode, memberUin int64, flag bool) { _, _ = c.sendAndWait(c.buildGroupAdminSetPacket(groupCode, memberUin, flag)) } func (c *QQClient) updateGroupName(groupCode int64, newName string) { _, _ = c.sendAndWait(c.buildGroupNameUpdatePacket(groupCode, newName)) } func (c *QQClient) updateGroupMemo(groupCode int64, newMemo string) { _, _ = c.sendAndWait(c.buildGroupMemoUpdatePacket(groupCode, newMemo)) } func (c *QQClient) groupMuteAll(groupCode int64, mute bool) { _, _ = c.sendAndWait(c.buildGroupMuteAllPacket(groupCode, mute)) } func (c *QQClient) groupMute(groupCode, memberUin int64, time uint32) { _, _ = c.sendAndWait(c.buildGroupMutePacket(groupCode, memberUin, time)) } func (c *QQClient) quitGroup(groupCode int64) { _, _ = c.sendAndWait(c.buildQuitGroupPacket(groupCode)) } func (c *QQClient) kickGroupMember(groupCode, memberUin int64, msg string, block bool) { _, _ = c.sendAndWait(c.buildGroupKickPacket(groupCode, memberUin, msg, block)) } func (g *GroupInfo) removeMember(uin int64) { g.Update(func(info *GroupInfo) { for i, m := range info.Members { if m.Uin == uin { info.Members = append(info.Members[:i], info.Members[i+1:]...) break } } }) } func (c *QQClient) connect() error { c.Info("connect to server: %v", c.servers[c.currServerIndex].String()) conn, err := net.DialTCP("tcp", nil, c.servers[c.currServerIndex]) c.currServerIndex++ if c.currServerIndex == len(c.servers) { c.currServerIndex = 0 } if err != nil || conn == nil { c.retryTimes++ if c.retryTimes > len(c.servers) { return errors.New("All servers are unreachable") } c.Error("connect server error: %v", err) if err = c.connect(); err != nil { return err } return nil } c.retryTimes = 0 c.ConnectTime = time.Now() c.Conn = conn return nil } func (c *QQClient) Disconnect() { c.NetLooping = false c.Online = false if c.Conn != nil { _ = c.Conn.Close() } } func (c *QQClient) SetCustomServer(servers []*net.TCPAddr) { c.servers = append(servers, c.servers...) } func (c *QQClient) SendGroupGift(groupCode, uin uint64, gift message.GroupGift) { _, packet := c.sendGroupGiftPacket(groupCode, uin, gift) _ = c.send(packet) } func (c *QQClient) registerClient() error { _, err := c.sendAndWait(c.buildClientRegisterPacket()) return err } func (c *QQClient) nextSeq() uint16 { return uint16(atomic.AddInt32(&c.SequenceId, 1) & 0x7FFF) } func (c *QQClient) nextPacketSeq() int32 { return atomic.AddInt32(&c.requestPacketRequestId, 2) } func (c *QQClient) nextGroupSeq() int32 { return atomic.AddInt32(&c.groupSeq, 2) } func (c *QQClient) nextFriendSeq() int32 { return atomic.AddInt32(&c.friendSeq, 1) } func (c *QQClient) nextGroupDataTransSeq() int32 { return atomic.AddInt32(&c.groupDataTransSeq, 2) } func (c *QQClient) nextHighwayApplySeq() int32 { return atomic.AddInt32(&c.highwayApplyUpSeq, 2) } func (c *QQClient) send(pkt []byte) error { _, err := c.Conn.Write(pkt) if err != nil { c.stat.PacketLost++ } else { c.stat.PacketSent++ } return errors.Wrap(err, "Packet failed to send") } func (c *QQClient) sendAndWait(seq uint16, pkt []byte, params ...requestParams) (interface{}, error) { type T struct { Response interface{} Error error } err := c.send(pkt) if err != nil { return nil, err } ch := make(chan T) defer close(ch) p := func() requestParams { if len(params) == 0 { return nil } return params[0] }() c.handlers.Store(seq, &handlerInfo{fun: func(i interface{}, err error) { ch <- T{ Response: i, Error: err, } }, params: p}) retry := 0 for true { select { case rsp := <-ch: return rsp.Response, rsp.Error case <-time.After(time.Second * 15): retry++ if retry < 2 { _ = c.send(pkt) continue } c.handlers.Delete(seq) //c.Error("packet timed out, seq: %v", seq) //println("Packet Timed out") return nil, errors.New("Packet timed out") } } return nil, nil } // 等待一个或多个数据包解析, 优先级低于 sendAndWait // 返回终止解析函数 func (c *QQClient) waitPacket(cmd string, f func(interface{}, error)) func() { c.waiters.Store(cmd, f) return func() { c.waiters.Delete(cmd) } } func (c *QQClient) netLoop() { if c.NetLooping { return } c.NetLooping = true reader := binary.NewNetworkReader(c.Conn) retry := 0 errCount := 0 for c.NetLooping { l, err := reader.ReadInt32() if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) { c.Error("connection dropped by server: %v", err) c.stat.DisconnectTimes++ err = c.connect() if err != nil { c.Error("connect server error: %v", err) break } reader = binary.NewNetworkReader(c.Conn) go func() { if e := c.registerClient(); e != nil && e.Error() != "Packet timed out" { // 掉线在心跳已经有判断了, 只需要处理返回值 c.lastLostMsg = "register client failed: " + e.Error() c.Disconnect() c.Error("reconnect failed: " + e.Error()) //break } }() } if l <= 0 { retry++ time.Sleep(time.Second * 3) if retry > 10 { break } continue } data, err := reader.ReadBytes(int(l) - 4) pkt, err := packets.ParseIncomingPacket(data, c.sigInfo.d2Key) if err != nil { c.Error("parse incoming packet error: %v", err) if errors.Is(err, packets.ErrSessionExpired) || errors.Is(err, packets.ErrPacketDropped) { break } errCount++ if errCount > 2 { break } //log.Println("parse incoming packet error: " + err.Error()) continue } payload := pkt.Payload if pkt.Flag2 == 2 { payload, err = pkt.DecryptPayload(c.RandomKey, c.sigInfo.wtSessionTicketKey) if err != nil { c.Error("decrypt payload error: %v", err) continue } } errCount = 0 retry = 0 c.Debug("rev pkt: %v seq: %v", pkt.CommandName, pkt.SequenceId) c.stat.PacketReceived++ go func() { defer func() { if pan := recover(); pan != nil { c.Error("panic on decoder %v : %v\n%s", pkt.CommandName, pan, debug.Stack()) } }() if decoder, ok := decoders[pkt.CommandName]; ok { // found predefined decoder info, ok := c.handlers.LoadAndDelete(pkt.SequenceId) rsp, err := decoder(c, &incomingPacketInfo{ SequenceId: pkt.SequenceId, CommandName: pkt.CommandName, Params: func() requestParams { if !ok { return nil } return info.params }(), }, payload) if err != nil { c.Debug("decode pkt %v error: %+v", pkt.CommandName, err) } if ok { info.fun(rsp, err) } else if f, ok := c.waiters.Load(pkt.CommandName); ok { // 在不存在handler的情况下触发wait f.(func(interface{}, error))(rsp, err) } } else if f, ok := c.handlers.LoadAndDelete(pkt.SequenceId); ok { // does not need decoder f.fun(nil, nil) } else { c.Debug("Unhandled Command: %s\nSeq: %d\nThis message can be ignored.", pkt.CommandName, pkt.SequenceId) } }() } c.NetLooping = false c.Online = false _ = c.Conn.Close() if c.lastLostMsg == "" { c.lastLostMsg = "Connection lost." } c.stat.LostTimes++ c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: c.lastLostMsg}) } func (c *QQClient) doHeartbeat() { c.heartbeatEnabled = true times := 0 for c.Online { seq := c.nextSeq() sso := packets.BuildSsoPacket(seq, c.version.AppId, "Heartbeat.Alive", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, []byte{}, c.ksid) packet := packets.BuildLoginPacket(c.Uin, 0, []byte{}, sso, []byte{}) _, err := c.sendAndWait(seq, packet) if err != nil { c.lastLostMsg = "Heartbeat failed: " + err.Error() c.Disconnect() break } times++ if times >= 7 { _ = c.registerClient() times = 0 } time.Sleep(time.Second * 30) } c.heartbeatEnabled = false }