diff --git a/client/client.go b/client/client.go index edf062f0..7c9153f7 100644 --- a/client/client.go +++ b/client/client.go @@ -70,6 +70,8 @@ type QQClient struct { timeDiff int64 sigInfo *loginSigInfo highwaySession *highwaySessionInfo + srvSsoAddrs []string + otherSrvAddrs []string fileStorageInfo *jce.FileStoragePushFSSvcList pwdFlag bool diff --git a/client/decoders.go b/client/decoders.go index f4dadd15..0ea5eeaf 100644 --- a/client/decoders.go +++ b/client/decoders.go @@ -256,7 +256,18 @@ func decodePushReqPacket(c *QQClient, _ uint16, payload []byte) (interface{}, er SigSession: rsp.RspBody.SigSession, SessionKey: rsp.RspBody.SessionKey, } - c.Debug("highway session updated.") + for _, srv := range rsp.RspBody.Addrs { + if srv.GetServiceType() == 10 { + for _, addr := range srv.Addrs { + c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr.GetIp()), addr.GetPort())) + } + } + if srv.GetServiceType() == 21 { + for _, addr := range srv.Addrs { + c.otherSrvAddrs = append(c.otherSrvAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr.GetIp()), addr.GetPort())) + } + } + } } } } diff --git a/client/highway.go b/client/highway.go index 156f7177..56d9e2ab 100644 --- a/client/highway.go +++ b/client/highway.go @@ -6,15 +6,16 @@ import ( binary2 "encoding/binary" "encoding/hex" "fmt" - "net" - "net/http" - "strconv" - "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/utils" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "io" + "net" + "net/http" + "strconv" + "time" ) func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdId int32) error { @@ -57,6 +58,133 @@ func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdId return nil } +func (c *QQClient) highwayUploadByBDH(stream io.ReadSeeker, cmdId int32, ext []byte) ([]byte, error) { + // TODO: encrypted upload support. + if len(c.srvSsoAddrs) == 0 { + return nil, errors.New("srv addrs not found. maybe miss some packet?") + } + if c.highwaySession == nil { + return nil, errors.New("highway session not found. maybe miss some packet?") + } + h := md5.New() + length, _ := io.Copy(h, stream) + chunkSize := 8192 * 8 + fh := h.Sum(nil) + _, _ = stream.Seek(0, io.SeekStart) + conn, err := net.DialTimeout("tcp", c.srvSsoAddrs[0], time.Second*20) + if err != nil { + return nil, errors.Wrap(err, "connect error") + } + offset := 0 + reader := binary.NewNetworkReader(conn) + ticket := c.highwaySession.SigSession + if err = c.highwaySendHeartbreak(conn); err != nil { + return nil, errors.Wrap(err, "echo error") + } + if _, _, err = highwayReadResponse(reader); err != nil { + return nil, errors.Wrap(err, "echo error") + } + var rspExt []byte + for { + chunk := make([]byte, chunkSize) + rl, err := io.ReadFull(stream, chunk) + if err == io.EOF { + break + } + if err == io.ErrUnexpectedEOF { + chunk = chunk[:rl] + } + ch := md5.Sum(chunk) + head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ + MsgBasehead: &pb.DataHighwayHead{ + Version: 1, + Uin: strconv.FormatInt(c.Uin, 10), + Command: "PicUp.DataUp", + Seq: c.nextGroupDataTransSeq(), + Appid: int32(c.version.AppId), + Dataflag: 4096, + CommandId: cmdId, + LocaleId: 2052, + }, + MsgSeghead: &pb.SegHead{ + Filesize: length, + Dataoffset: int64(offset), + Datalength: int32(rl), + Serviceticket: ticket, + Md5: ch[:], + FileMd5: fh[:], + }, + ReqExtendinfo: ext, + }) + offset += rl + _, err = conn.Write(binary.NewWriterF(func(w *binary.Writer) { + w.WriteByte(40) + w.WriteUInt32(uint32(len(head))) + w.WriteUInt32(uint32(len(chunk))) + w.Write(head) + w.Write(chunk) + w.WriteByte(41) + })) + if err != nil { + return nil, errors.Wrap(err, "write conn error") + } + rspHead, _, err := highwayReadResponse(reader) + if err != nil { + return nil, errors.Wrap(err, "highway upload error") + } + if rspHead.ErrorCode != 0 { + return nil, errors.New("upload failed") + } + if rspHead.RspExtendinfo != nil { + rspExt = rspHead.RspExtendinfo + } + if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil { + ticket = rspHead.MsgSeghead.Serviceticket + } + } + return rspExt, nil +} + +func (c *QQClient) highwaySendHeartbreak(conn net.Conn) error { + head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ + MsgBasehead: &pb.DataHighwayHead{ + Version: 1, + Uin: strconv.FormatInt(c.Uin, 10), + Command: "PicUp.Echo", + Seq: c.nextGroupDataTransSeq(), + Appid: int32(c.version.AppId), + Dataflag: 4096, + CommandId: 0, + LocaleId: 2052, + }, + }) + _, err := conn.Write(binary.NewWriterF(func(w *binary.Writer) { + w.WriteByte(40) + w.WriteUInt32(uint32(len(head))) + w.WriteUInt32(0) + w.Write(head) + w.WriteByte(41) + })) + return err +} + +func highwayReadResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, []byte, error) { + _, err := r.ReadByte() + if err != nil { + return nil, nil, errors.Wrap(err, "failed to read byte") + } + hl, _ := r.ReadInt32() + a2, _ := r.ReadInt32() + head, _ := r.ReadBytes(int(hl)) + payload, _ := r.ReadBytes(int(a2)) + _, _ = r.ReadByte() + rsp := new(pb.RspDataHighwayHead) + if err = proto.Unmarshal(head, rsp); err != nil { + return nil, nil, errors.Wrap(err, "failed to unmarshal protobuf message") + } + return rsp, payload, nil +} + // 只是为了写的跟上面一样长(bushi,当然也应该是最快的玩法 func (c *QQClient) uploadPtt(ip string, port int32, updKey, fileKey, data, md5 []byte) error { url := make([]byte, 512)[:0] diff --git a/client/ptt.go b/client/ptt.go index 61423aee..43c9a22f 100644 --- a/client/ptt.go +++ b/client/ptt.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "crypto/md5" "encoding/hex" @@ -19,27 +20,44 @@ import ( // UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送 func (c *QQClient) UploadGroupPtt(groupCode int64, voice []byte) (*message.GroupVoiceElement, error) { h := md5.Sum(voice) - seq, pkt := c.buildGroupPttStorePacket(groupCode, h[:], int32(len(voice)), 0, int32(len(voice))) - r, err := c.sendAndWait(seq, pkt) + ext := c.buildGroupPttStoreBDHExt(groupCode, h[:], int32(len(voice)), 0, int32(len(voice))) + rsp, err := c.highwayUploadByBDH(bytes.NewReader(voice), 29, ext) if err != nil { return nil, err } - rsp := r.(pttUploadResponse) - if rsp.ResultCode != 0 { - return nil, errors.New(rsp.Message) + if len(rsp) == 0 { + return nil, errors.New("miss rsp") } - if rsp.IsExists { - goto ok + pkt := pb.D388RespBody{} + if err = proto.Unmarshal(rsp, &pkt); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal protobuf message") } - for i, ip := range rsp.UploadIp { - err := c.uploadPtt(ip, rsp.UploadPort[i], rsp.UploadKey, rsp.FileKey, voice, h[:]) - if err != nil { - continue - } - goto ok + if len(pkt.MsgTryUpPttRsp) == 0 { + return nil, errors.New("miss try up rsp") } - return nil, errors.New("upload failed") -ok: + /* + seq, pkt := c.buildGroupPttStorePacket(groupCode, h[:], int32(len(voice)), 0, int32(len(voice))) + r, err := c.sendAndWait(seq, pkt) + if err != nil { + return nil, err + } + rsp := r.(pttUploadResponse) + if rsp.ResultCode != 0 { + return nil, errors.New(rsp.Message) + } + if rsp.IsExists { + goto ok + } + for i, ip := range rsp.UploadIp { + err := c.uploadPtt(ip, rsp.UploadPort[i], rsp.UploadKey, rsp.FileKey, voice, h[:]) + if err != nil { + continue + } + goto ok + } + return nil, errors.New("upload failed") + ok: + */ return &message.GroupVoiceElement{ Ptt: &msg.Ptt{ FileType: proto.Int32(4), @@ -47,7 +65,7 @@ ok: FileMd5: h[:], FileName: proto.String(hex.EncodeToString(h[:]) + ".amr"), FileSize: proto.Int32(int32(len(voice))), - GroupFileKey: rsp.FileKey, + GroupFileKey: pkt.MsgTryUpPttRsp[0].FileKey, BoolValid: proto.Bool(true), PbReserve: []byte{8, 0, 40, 0, 56, 0}, }}, nil @@ -89,6 +107,12 @@ ok: // PttStore.GroupPttUp func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, codec, voiceLength int32) (uint16, []byte) { seq := c.nextSeq() + packet := packets.BuildUniPacket(c.Uin, seq, "PttStore.GroupPttUp", 1, c.OutGoingPacketSessionId, + EmptyBytes, c.sigInfo.d2Key, c.buildGroupPttStoreBDHExt(groupCode, md5, size, codec, voiceLength)) + return seq, packet +} + +func (c *QQClient) buildGroupPttStoreBDHExt(groupCode int64, md5 []byte, size, codec, voiceLength int32) []byte { req := &pb.D388ReqBody{ NetType: 3, Subcmd: 3, @@ -110,11 +134,9 @@ func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, c BoolNewUpChan: true, }, }, - Extension: EmptyBytes, } payload, _ := proto.Marshal(req) - packet := packets.BuildUniPacket(c.Uin, seq, "PttStore.GroupPttUp", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload) - return seq, packet + return payload } // PttStore.GroupPttUp