package client import ( "crypto/md5" "errors" "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/protocol/packets" "github.com/Mrs4s/MiraiGo/utils" "io" "log" "math/rand" "net" "strconv" "sync" "sync/atomic" "time" ) type QQClient struct { Uin int64 PasswordMd5 [16]byte Nickname string Age uint16 Gender uint16 FriendList []*FriendInfo GroupList []*GroupInfo Online bool SequenceId uint16 OutGoingPacketSessionId []byte RandomKey []byte Conn net.Conn decoders map[string]func(*QQClient, uint16, []byte) (interface{}, error) handlers map[uint16]func(interface{}, error) syncCookie []byte pubAccountCookie []byte msgCtrlBuf []byte ksid []byte t104 []byte t150 []byte t149 []byte t528 []byte t530 []byte rollbackSig []byte timeDiff int64 sigInfo *loginSigInfo pwdFlag bool lastMessageSeq int32 lastMessageSeqTmp sync.Map groupMsgBuilders sync.Map onlinePushCache []int16 // reset on reconnect requestPacketRequestId int32 groupSeq int32 friendSeq int32 groupDataTransSeq int32 eventHandlers *eventHandlers groupListLock *sync.Mutex } type loginSigInfo struct { loginBitmap uint64 tgt []byte tgtKey []byte userStKey []byte userStWebSig []byte sKey []byte d2 []byte d2Key []byte wtSessionTicketKey []byte deviceToken []byte } 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, RandomKey: make([]byte, 16), OutGoingPacketSessionId: []byte{0x02, 0xB0, 0x5B, 0x8B}, decoders: map[string]func(*QQClient, uint16, []byte) (interface{}, error){ "wtlogin.login": decodeLoginResponse, "StatSvc.register": decodeClientRegisterResponse, "MessageSvc.PushNotify": decodeSvcNotify, "OnlinePush.PbPushGroupMsg": decodeGroupMessagePacket, "OnlinePush.ReqPush": decodeOnlinePushReqPacket, "OnlinePush.PbPushTransMsg": decodeOnlinePushTransPacket, "ConfigPushSvc.PushReq": decodePushReqPacket, "MessageSvc.PbGetMsg": decodeMessageSvcPacket, "friendlist.getFriendGroupList": decodeFriendGroupListResponse, "friendlist.GetTroopListReqV2": decodeGroupListResponse, "friendlist.GetTroopMemberListReq": decodeGroupMemberListResponse, "ImgStore.GroupPicUp": decodeGroupImageStoreResponse, "LongConn.OffPicUp": decodeOffPicUpResponse, "ProfileService.Pb.ReqSystemMsgNew.Group": decodeSystemMsgGroupPacket, "ProfileService.Pb.ReqSystemMsgNew.Friend": decodeSystemMsgFriendPacket, //"MultiMsg.ApplyDown": decodeMultiMsgDownPacket, }, handlers: map[uint16]func(interface{}, error){}, sigInfo: &loginSigInfo{}, requestPacketRequestId: 1921334513, groupSeq: 22911, friendSeq: 22911, ksid: []byte("|454001228437590|A8.2.7.27f6ea96"), eventHandlers: &eventHandlers{}, groupListLock: new(sync.Mutex), } 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 } c.Online = true go c.loop() seq, packet := c.buildLoginPacket() rsp, err := c.sendAndWait(seq, packet) if err != nil { return nil, err } l := rsp.(LoginResponse) if l.Success { c.registerClient() go c.heartbeat() } 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 { return nil, err } l := rsp.(LoginResponse) if l.Success { c.registerClient() go c.heartbeat() } return &l, 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(curFriendCount) >= r.TotalCount { break } } return r, nil } func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage) *message.GroupMessage { eid := utils.RandomString(6) mr := int32(rand.Uint32()) ch := make(chan int32) c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) { if e.Rand == mr { ch <- e.Seq } }) defer c.onGroupMessageReceipt(eid) _, pkt := c.buildGroupSendingPacket(groupCode, mr, m) _ = c.send(pkt) var mid int32 ret := &message.GroupMessage{ Id: -1, InternalId: mr, GroupCode: groupCode, Sender: &message.Sender{ Uin: c.Uin, Nickname: c.Nickname, IsFriend: true, }, Time: int32(time.Now().Unix()), Elements: m.Elements, } select { case mid = <-ch: case <-time.After(time.Second * 5): return ret } ret.Id = mid return ret } func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) { mr := int32(rand.Uint32()) _, pkt := c.buildFriendSendingPacket(target, mr, m) _ = c.send(pkt) } func (c *QQClient) RecallGroupMessage(groupCode int64, msgId, msgInternalId int32) { _, pkt := c.buildGroupRecallPacket(groupCode, msgId, msgInternalId) _ = c.send(pkt) } func (c *QQClient) UploadGroupImage(groupCode int64, img []byte) (*message.GroupImageElement, error) { h := md5.Sum(img) seq, pkt := c.buildGroupImageStorePacket(groupCode, h[:], int32(len(img))) r, err := c.sendAndWait(seq, pkt) if err != nil { return nil, err } rsp := r.(imageUploadResponse) if rsp.ResultCode != 0 { return nil, errors.New(rsp.Message) } if rsp.IsExists { return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil } for i, ip := range rsp.UploadIp { updServer := binary.UInt32ToIPV4Address(uint32(ip)) err := c.highwayUploadImage(updServer+":"+strconv.FormatInt(int64(rsp.UploadPort[i]), 10), rsp.UploadKey, img) if err != nil { return nil, err } return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil } return nil, errors.New("upload failed") } func (c *QQClient) UploadPrivateImage(target int64, img []byte) (*message.FriendImageElement, error) { return c.uploadPrivateImage(target, img, 0) } func (c *QQClient) uploadPrivateImage(target int64, img []byte, count int) (*message.FriendImageElement, error) { count++ h := md5.Sum(img) i, err := c.sendAndWait(c.buildOffPicUpPacket(target, h[:], int32(len(img)))) if err != nil { return nil, err } rsp := i.(imageUploadResponse) if rsp.ResultCode != 0 { return nil, errors.New(rsp.Message) } if !rsp.IsExists { if _, err = c.UploadGroupImage(0, img); err != nil { return nil, err } // safe if count >= 5 { return nil, errors.New("upload failed") } return c.uploadPrivateImage(target, img, count) } return &message.FriendImageElement{ ImageId: rsp.ResourceId, Md5: h[:], }, nil } func (c *QQClient) QueryGroupImage(groupCode int64, hash []byte, size int32) (*message.GroupImageElement, error) { r, err := c.sendAndWait(c.buildGroupImageStorePacket(groupCode, hash, size)) if err != nil { return nil, err } rsp := r.(imageUploadResponse) if rsp.ResultCode != 0 { return nil, errors.New(rsp.Message) } if rsp.IsExists { return message.NewGroupImage(binary.CalculateImageResourceId(hash), hash), nil } return nil, errors.New("image not exists") } 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()) if err != nil { return nil, err } r := rsp.([]*GroupInfo) for _, group := range r { m, err := c.GetGroupMembers(group) if err != nil { continue } group.Members = m } 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 } rsp := data.(groupMemberListResponse) nextUin = rsp.NextUin for _, m := range rsp.list { if m.Uin == group.OwnerUin { m.Permission = Owner break } } 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 bool) { switch req := i.(type) { case *UserJoinGroupRequest: _, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.RequesterUin, req.GroupCode, false, accept, false) _ = c.send(pkt) case *GroupInvitedRequest: _, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.InvitorUin, req.GroupCode, true, accept, false) _ = c.send(pkt) } } func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) { _, pkt := c.buildSystemMsgFriendActionPacket(req.RequestId, req.RequesterUin, accept) _ = c.send(pkt) } func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo { for _, m := range g.Members { f := m if f.Uin == uin { return f } } return nil } func (g *GroupInfo) removeMember(uin int64) { if g.memLock == nil { g.memLock = new(sync.Mutex) } g.memLock.Lock() defer g.memLock.Unlock() for i, m := range g.Members { if m.Uin == uin { g.Members = append(g.Members[:i], g.Members[i+1:]...) break } } } func (c *QQClient) connect() error { conn, err := net.Dial("tcp", "125.94.60.146:80") //TODO: more servers if err != nil { return err } c.Conn = conn c.onlinePushCache = []int16{} return nil } func (c *QQClient) registerClient() { _, packet := c.buildClientRegisterPacket() _ = c.send(packet) } func (c *QQClient) nextSeq() uint16 { c.SequenceId++ c.SequenceId &= 0x7FFF if c.SequenceId == 0 { c.SequenceId++ } return c.SequenceId } func (c *QQClient) nextPacketSeq() int32 { s := atomic.LoadInt32(&c.requestPacketRequestId) atomic.AddInt32(&c.requestPacketRequestId, 2) return s } func (c *QQClient) nextGroupSeq() int32 { s := atomic.LoadInt32(&c.groupSeq) atomic.AddInt32(&c.groupSeq, 2) return s } func (c *QQClient) nextFriendSeq() int32 { s := atomic.LoadInt32(&c.friendSeq) atomic.AddInt32(&c.friendSeq, 2) return s } func (c *QQClient) nextGroupDataTransSeq() int32 { s := atomic.LoadInt32(&c.groupDataTransSeq) atomic.AddInt32(&c.groupDataTransSeq, 2) return s } func (c *QQClient) send(pkt []byte) error { _, err := c.Conn.Write(pkt) return err } func (c *QQClient) sendAndWait(seq uint16, pkt []byte) (interface{}, error) { type T struct { Response interface{} Error error } _, err := c.Conn.Write(pkt) if err != nil { return nil, err } ch := make(chan T) c.handlers[seq] = func(i interface{}, err error) { ch <- T{ Response: i, Error: err, } } rsp := <-ch return rsp.Response, rsp.Error } func (c *QQClient) loop() { reader := binary.NewNetworkReader(c.Conn) for c.Online { l, err := reader.ReadInt32() if err == io.EOF || err == io.ErrClosedPipe { err = c.connect() if err != nil { c.Online = false return } reader = binary.NewNetworkReader(c.Conn) c.registerClient() } if l <= 0 { continue } data, err := reader.ReadBytes(int(l) - 4) pkt, err := packets.ParseIncomingPacket(data, c.sigInfo.d2Key) if err != nil { log.Println("parse incoming packet error: " + err.Error()) continue } payload := pkt.Payload if pkt.Flag2 == 2 { payload, err = pkt.DecryptPayload(c.RandomKey) if err != nil { continue } } //fmt.Println(pkt.CommandName) go func() { decoder, ok := c.decoders[pkt.CommandName] if !ok { if f, ok := c.handlers[pkt.SequenceId]; ok { delete(c.handlers, pkt.SequenceId) f(nil, nil) } return } rsp, err := decoder(c, pkt.SequenceId, payload) if err != nil { log.Println("decode", pkt.CommandName, "error:", err) } if f, ok := c.handlers[pkt.SequenceId]; ok { delete(c.handlers, pkt.SequenceId) f(rsp, err) } }() } } func (c *QQClient) heartbeat() { for c.Online { time.Sleep(time.Second * 30) seq := c.nextSeq() sso := packets.BuildSsoPacket(seq, "Heartbeat.Alive", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, []byte{}, c.ksid) packet := packets.BuildLoginPacket(c.Uin, 0, []byte{}, sso, []byte{}) _, _ = c.sendAndWait(seq, packet) } }