package client import ( "crypto/md5" "fmt" "math" "math/rand" "net" "sort" "sync" "sync/atomic" "time" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "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/crypto" "github.com/Mrs4s/MiraiGo/protocol/packets" "github.com/Mrs4s/MiraiGo/utils" ) 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 stat Statistics once sync.Once // option AllowSlider bool // account info Nickname string Age uint16 Gender uint16 FriendList []*FriendInfo GroupList []*GroupInfo OnlineClients []*OtherClientInfo Online bool QiDian *QiDianAccountInfo // protocol public field SequenceId int32 OutGoingPacketSessionId []byte RandomKey []byte TCP *utils.TCPListener ConnectTime time.Time // internal state handlers HandlerMap waiters sync.Map servers []*net.TCPAddr currServerIndex int retryTimes int version *versionInfo deviceInfo *DeviceInfo alive bool // tlv cache t104 []byte t174 []byte g []byte t402 []byte t150 []byte t149 []byte t528 []byte t530 []byte randSeed []byte // t403 rollbackSig []byte // sync info syncCookie []byte pubAccountCookie []byte msgCtrlBuf []byte ksid []byte // session info sigInfo *loginSigInfo bigDataSession *bigDataSessionInfo dpwd []byte timeDiff int64 pwdFlag bool // address srvSsoAddrs []string otherSrvAddrs []string fileStorageInfo *jce.FileStoragePushFSSvcList // message state lastMessageSeq int32 msgSvcCache *utils.Cache lastC2CMsgTime int64 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 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 QiDianAccountInfo struct { MasterUin int64 ExtName string CreateTime int64 bigDataReqAddrs []string bigDataReqSession *bigDataSessionInfo } 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, "wtlogin.trans_emp": decodeTransEmpResponse, "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.delFriend": decodeFriendDeleteResponse, "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.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 NewClientEmpty() *QQClient { return NewClient(0, "") } func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { crypto.ECDH.FetchPubKey(uin) cli := &QQClient{ Uin: uin, PasswordMd5: passwordMd5, SequenceId: 0x3635, AllowSlider: true, RandomKey: make([]byte, 16), OutGoingPacketSessionId: []byte{0x02, 0xB0, 0x5B, 0x8B}, TCP: &utils.TCPListener{}, sigInfo: &loginSigInfo{}, requestPacketRequestID: 1921334513, groupSeq: int32(rand.Intn(20000)), friendSeq: 22911, highwayApplyUpSeq: 77918, eventHandlers: &eventHandlers{}, msgSvcCache: utils.NewCache(time.Second * 15), transCache: utils.NewCache(time.Second * 15), onlinePushCache: utils.NewCache(time.Second * 15), servers: []*net.TCPAddr{}, alive: true, } cli.UseDevice(SystemDeviceInfo) 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 } cli.TCP.PlannedDisconnect(cli.plannedDisconnect) cli.TCP.UnexpectedDisconnect(cli.unexpectedDisconnect) rand.Read(cli.RandomKey) go cli.netLoop() return cli } func (c *QQClient) UseDevice(info *DeviceInfo) { c.version = genVersionInfo(info.Protocol) c.ksid = []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", info.IMEI)) c.deviceInfo = info } func (c *QQClient) Release() { if c.Online { c.Disconnect() } c.alive = false } // 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 } rsp, err := c.sendAndWait(c.buildLoginPacket()) if err != nil { c.Disconnect() return nil, err } l := rsp.(LoginResponse) if l.Success { _ = c.init(false) } return &l, nil } func (c *QQClient) TokenLogin(token []byte) error { if c.Online { return ErrAlreadyOnline } err := c.connect() if err != nil { return err } { r := binary.NewReader(token) c.Uin = r.ReadInt64() c.sigInfo.d2 = r.ReadBytesShort() c.sigInfo.d2Key = r.ReadBytesShort() c.sigInfo.tgt = r.ReadBytesShort() c.sigInfo.srmToken = r.ReadBytesShort() c.sigInfo.t133 = r.ReadBytesShort() c.sigInfo.encryptedA1 = r.ReadBytesShort() c.sigInfo.wtSessionTicketKey = r.ReadBytesShort() c.OutGoingPacketSessionId = r.ReadBytesShort() // SystemDeviceInfo.TgtgtKey = r.ReadBytesShort() c.deviceInfo.TgtgtKey = r.ReadBytesShort() } _, err = c.sendAndWait(c.buildRequestChangeSigPacket()) if err != nil { return err } return c.init(true) } func (c *QQClient) FetchQRCode() (*QRCodeLoginResponse, error) { if c.Online { return nil, ErrAlreadyOnline } err := c.connect() if err != nil { return nil, err } i, err := c.sendAndWait(c.buildQRCodeFetchRequestPacket()) if err != nil { return nil, errors.Wrap(err, "fetch qrcode error") } return i.(*QRCodeLoginResponse), nil } func (c *QQClient) QueryQRCodeStatus(sig []byte) (*QRCodeLoginResponse, error) { i, err := c.sendAndWait(c.buildQRCodeResultQueryRequestPacket(sig)) if err != nil { return nil, errors.Wrap(err, "query result error") } return i.(*QRCodeLoginResponse), nil } func (c *QQClient) QRCodeLogin(info *QRCodeLoginInfo) (*LoginResponse, error) { i, err := c.sendAndWait(c.buildQRCodeLoginPacket(info.tmpPwd, info.tmpNoPicSig, info.tgtQR)) if err != nil { return nil, errors.Wrap(err, "qrcode login error") } rsp := i.(LoginResponse) if rsp.Success { _ = c.init(false) } return &rsp, 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(false) } 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(false) } 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(false) } 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(tokenLogin bool) error { if len(c.g) == 0 { c.Warning("device lock is disable. http api may fail.") } if err := c.registerClient(); err != nil { return errors.Wrap(err, "register error") } if tokenLogin { notify := make(chan struct{}) d := c.waitPacket("StatSvc.ReqMSFOffline", func(i interface{}, err error) { notify <- struct{}{} }) d2 := c.waitPacket("MessageSvc.PushForceOffline", func(i interface{}, err error) { notify <- struct{}{} }) select { case <-notify: d() d2() return errors.New("token failed") case <-time.After(time.Second): d() d2() } } c.groupSysMsgCache, _ = c.GetGroupSystemMessages() if !c.heartbeatEnabled { go c.doHeartbeat() } _ = c.RefreshStatus() if c.version.Protocol == QiDian { _, _ = c.sendAndWait(c.buildLoginExtraPacket()) // 小登录 _, _ = c.sendAndWait(c.buildConnKeyRequestPacket()) // big data key 如果等待 config push 的话时间来不及 } seq, pkt := c.buildGetMessageRequestPacket(msg.SyncFlag_START, time.Now().Unix()) _, _ = c.sendAndWait(seq, pkt, requestParams{"used_reg_proxy": true, "init": true}) c.once.Do(func() { c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) { atomic.AddUint64(&c.stat.MessageReceived, 1) atomic.StoreInt64(&c.stat.LastMessageTime, time.Now().Unix()) }) c.OnPrivateMessage(func(_ *QQClient, _ *message.PrivateMessage) { atomic.AddUint64(&c.stat.MessageReceived, 1) atomic.StoreInt64(&c.stat.LastMessageTime, time.Now().Unix()) }) c.OnTempMessage(func(_ *QQClient, _ *TempMessageEvent) { atomic.AddUint64(&c.stat.MessageReceived, 1) atomic.StoreInt64(&c.stat.LastMessageTime, time.Now().Unix()) }) c.onGroupMessageReceipt("internal", func(_ *QQClient, _ *groupMessageReceiptEvent) { atomic.AddUint64(&c.stat.MessageSent, 1) }) }) return nil } func (c *QQClient) GenToken() []byte { return binary.NewWriterF(func(w *binary.Writer) { w.WriteUInt64(uint64(c.Uin)) w.WriteBytesShort(c.sigInfo.d2) w.WriteBytesShort(c.sigInfo.d2Key) w.WriteBytesShort(c.sigInfo.tgt) w.WriteBytesShort(c.sigInfo.srmToken) w.WriteBytesShort(c.sigInfo.t133) w.WriteBytesShort(c.sigInfo.encryptedA1) w.WriteBytesShort(c.sigInfo.wtSessionTicketKey) w.WriteBytesShort(c.OutGoingPacketSessionId) w.WriteBytesShort(c.deviceInfo.TgtgtKey) }) } func (c *QQClient) SetOnlineStatus(s UserOnlineStatus) { if s < 1000 { _, _ = c.sendAndWait(c.buildStatusSetPacket(int32(s), 0)) return } _, _ = c.sendAndWait(c.buildStatusSetPacket(11, int32(s))) } 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 // 当使用普通QQ时: 请求好友列表 // 当使用企点QQ时: 请求外部联系人列表 func (c *QQClient) GetFriendList() (*FriendListResponse, error) { if c.version.Protocol == QiDian { rsp, err := c.getQiDianAddressDetailList() if err != nil { return nil, err } return &FriendListResponse{TotalCount: int32(len(rsp)), List: rsp}, nil } 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(`