diff --git a/client/client.go b/client/client.go index 66b0a1a5..47f77368 100644 --- a/client/client.go +++ b/client/client.go @@ -158,6 +158,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { "PttCenterSvr.ShortVideoDownReq": decodePttShortVideoDownResponse, "LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse, "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_DOWNLOAD-1200": decodeOfflineFileDownloadResponse, + "PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500": decodePrivatePttStoreResponse, }, sigInfo: &loginSigInfo{}, requestPacketRequestId: 1921334513, @@ -691,42 +692,6 @@ func (c *QQClient) ImageOcr(img interface{}) (*OcrResponse, error) { return nil, errors.New("image error") } -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) - 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.uploadGroupPtt(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: 4, - SrcUin: c.Uin, - FileMd5: h[:], - FileName: hex.EncodeToString(h[:]) + ".amr", - FileSize: int32(len(voice)), - GroupFileKey: rsp.FileKey, - BoolValid: true, - PbReserve: []byte{8, 0, 40, 0, 56, 0}, - }}, 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 { diff --git a/client/entities.go b/client/entities.go index 7aad2c85..9687d744 100644 --- a/client/entities.go +++ b/client/entities.go @@ -245,7 +245,7 @@ type ( ResourceId string UploadKey []byte - UploadIp []int32 + UploadIp []string UploadPort []int32 FileKey []byte } diff --git a/client/highway.go b/client/highway.go index 5da91be8..89ee0db1 100644 --- a/client/highway.go +++ b/client/highway.go @@ -57,10 +57,10 @@ func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdId } // 只是为了写的跟上面一样长(bushi,当然也应该是最快的玩法 -func (c *QQClient) uploadGroupPtt(ip, port int32, updKey, fileKey, data, md5 []byte) error { +func (c *QQClient) uploadPtt(ip string, port int32, updKey, fileKey, data, md5 []byte) error { url := make([]byte, 512)[:0] url = append(url, "http://"...) - url = append(url, binary.UInt32ToIPV4Address(uint32(ip))...) + url = append(url, ip...) url = append(url, ':') url = strconv.AppendInt(url, int64(port), 10) url = append(url, "/?ver=4679&ukey="...) diff --git a/client/ptt.go b/client/ptt.go index b16984e0..14cad165 100644 --- a/client/ptt.go +++ b/client/ptt.go @@ -1,11 +1,87 @@ package client import ( + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client/pb" + "github.com/Mrs4s/MiraiGo/client/pb/cmd0x346" + "github.com/Mrs4s/MiraiGo/client/pb/msg" + "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/protocol/packets" "google.golang.org/protobuf/proto" ) +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) + 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: 4, + SrcUin: c.Uin, + FileMd5: h[:], + FileName: hex.EncodeToString(h[:]) + ".amr", + FileSize: int32(len(voice)), + GroupFileKey: rsp.FileKey, + BoolValid: true, + PbReserve: []byte{8, 0, 40, 0, 56, 0}, + }}, nil +} + +func (c *QQClient) UploadPrivatePtt(target int64, voice []byte) (*message.PrivateVoiceElement, error) { + h := md5.Sum(voice) + i, err := c.sendAndWait(c.buildPrivatePttStorePacket(target, h[:], int32(len(voice)), int32(len(voice)))) + if err != nil { + return nil, err + } + rsp := i.(pttUploadResponse) + 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.PrivateVoiceElement{ + Ptt: &msg.Ptt{ + FileType: 4, + SrcUin: c.Uin, + FileMd5: h[:], + FileName: hex.EncodeToString(h[:]) + ".amr", + FileSize: int32(len(voice)), + FileKey: rsp.FileKey, + BoolValid: true, + PbReserve: []byte{8, 0, 40, 0, 56, 0}, + }}, nil +} + // PttStore.GroupPttUp func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, codec, voiceLength int32) (uint16, []byte) { seq := c.nextSeq() @@ -54,10 +130,73 @@ func decodeGroupPttStoreResponse(_ *QQClient, _ uint16, payload []byte) (interfa if rsp.BoolFileExit { return pttUploadResponse{IsExists: true}, nil } + var ip []string + for _, i := range rsp.Uint32UpIp { + ip = append(ip, binary.UInt32ToIPV4Address(uint32(i))) + } return pttUploadResponse{ UploadKey: rsp.UpUkey, - UploadIp: rsp.Uint32UpIp, + UploadIp: ip, UploadPort: rsp.Uint32UpPort, FileKey: rsp.FileKey, }, nil } + +// PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500 +func (c *QQClient) buildPrivatePttStorePacket(target int64, md5 []byte, size, voiceLength int32) (uint16, []byte) { + seq := c.nextSeq() + req := &cmd0x346.C346ReqBody{ + Cmd: 500, + Seq: int32(seq), + ApplyUploadReq: &cmd0x346.ApplyUploadReq{ + SenderUin: c.Uin, + RecverUin: target, + FileType: 2, + FileSize: int64(size), + FileName: hex.EncodeToString(md5), + Bytes_10MMd5: md5, // 超过10M可能会炸 + }, + BusinessId: 17, + ClientType: 104, + ExtensionReq: &cmd0x346.ExtensionReq{ + Id: 3, + PttFormat: 1, + NetType: 3, + VoiceType: 2, + PttTime: voiceLength, + }, + } + payload, _ := proto.Marshal(req) + packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload) + return seq, packet +} + +// PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500 +func decodePrivatePttStoreResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) { + rsp := cmd0x346.C346RspBody{} + if err := proto.Unmarshal(payload, &rsp); err != nil { + c.Error("unmarshal cmd0x346 rsp body error: %v", err) + return nil, err + } + if rsp.ApplyUploadRsp == nil { + c.Error("decode apply upload 500 error: apply rsp is nil.") + return nil, errors.New("apply rsp is nil") + } + if rsp.ApplyUploadRsp.RetCode != 0 { + c.Error("decode apply upload 500 error: %v", rsp.ApplyUploadRsp.RetCode) + return nil, errors.New(fmt.Sprint(rsp.ApplyUploadRsp.RetCode)) + } + if rsp.ApplyUploadRsp.BoolFileExist { + return pttUploadResponse{IsExists: true}, nil + } + var port []int32 + for range rsp.ApplyUploadRsp.UploadipList { + port = append(port, rsp.ApplyUploadRsp.UploadPort) + } + return pttUploadResponse{ + UploadKey: rsp.ApplyUploadRsp.UploadKey, + UploadIp: rsp.ApplyUploadRsp.UploadipList, + UploadPort: port, + FileKey: rsp.ApplyUploadRsp.Uuid, + }, nil +} diff --git a/message/elements.go b/message/elements.go index 90ba660e..93331519 100644 --- a/message/elements.go +++ b/message/elements.go @@ -47,6 +47,11 @@ type GroupVoiceElement struct { Ptt *msg.Ptt } +type PrivateVoiceElement struct { + Data []byte + Ptt *msg.Ptt +} + type FriendImageElement struct { ImageId string Md5 []byte @@ -116,7 +121,7 @@ type GroupShowPicElement struct { EffectId int32 } -type FriendFlashPicElement struct{ +type FriendFlashPicElement struct { FriendImageElement }