1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 19:17:38 +08:00
MiraiGo/client/ptt.go
2021-01-07 23:57:45 +08:00

353 lines
11 KiB
Go

package client
import (
"crypto/md5"
"encoding/hex"
"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/client/pb/pttcenter"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"io"
)
func init() {
decoders["PttCenterSvr.ShortVideoDownReq"] = decodePttShortVideoDownResponse
decoders["PttCenterSvr.GroupShortVideoUpReq"] = decodeGroupShortVideoUploadResponse
}
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
h := md5.New()
length, _ := io.Copy(h, voice)
fh := h.Sum(nil)
_, _ = voice.Seek(0, io.SeekStart)
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
rsp, err := c.highwayUploadByBDH(voice, 29, c.highwaySession.SigSession, ext, false)
if err != nil {
return nil, err
}
if len(rsp) == 0 {
return nil, errors.New("miss rsp")
}
pkt := pb.D388RespBody{}
if err = proto.Unmarshal(rsp, &pkt); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if len(pkt.MsgTryUpPttRsp) == 0 {
return nil, errors.New("miss try up rsp")
}
return &message.GroupVoiceElement{
Ptt: &msg.Ptt{
FileType: proto.Int32(4),
SrcUin: &c.Uin,
FileMd5: fh[:],
FileName: proto.String(hex.EncodeToString(fh[:]) + ".amr"),
FileSize: proto.Int32(int32(length)),
GroupFileKey: pkt.MsgTryUpPttRsp[0].FileKey,
BoolValid: proto.Bool(true),
PbReserve: []byte{8, 0, 40, 0, 56, 0},
}}, nil
}
// UploadPrivatePtt 将语音数据使用好友语音通道上传到服务器, 返回 message.PrivateVoiceElement 可直接发送
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: proto.Int32(4),
SrcUin: &c.Uin,
FileMd5: h[:],
FileName: proto.String(hex.EncodeToString(h[:]) + ".amr"),
FileSize: proto.Int32(int32(len(voice))),
FileKey: rsp.FileKey,
BoolValid: proto.Bool(true),
PbReserve: []byte{8, 0, 40, 0, 56, 0},
}}, nil
}
func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSeeker) (*message.ShortVideoElement, error) {
videoHash, videoLen := utils.GetMd5AndLength(video)
thumbHash, thumbLen := utils.GetMd5AndLength(thumb)
i, err := c.sendAndWait(c.buildPttGroupShortVideoUploadReqPacket(videoHash, thumbHash, groupCode, videoLen, thumbLen))
if err != nil {
return nil, errors.Wrap(err, "upload req error")
}
rsp := i.(*pttcenter.ShortVideoUploadRsp)
if rsp.FileExists == 1 {
return &message.ShortVideoElement{
Uuid: []byte(rsp.FileId),
Size: int32(videoLen),
ThumbSize: int32(thumbLen),
Md5: videoHash,
ThumbMd5: thumbHash,
}, nil
}
ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen).PttShortVideoUploadReq)
hwRsp, err := c.highwayUploadByBDH(utils.MultiReadSeeker(thumb, video), 25, c.highwaySession.SigSession, ext, true)
if err != nil {
return nil, errors.Wrap(err, "upload video file error")
}
if len(hwRsp) == 0 {
return nil, errors.New("resp is empty")
}
rsp = &pttcenter.ShortVideoUploadRsp{}
if err = proto.Unmarshal(hwRsp, rsp); err != nil {
return nil, errors.Wrap(err, "decode error")
}
return &message.ShortVideoElement{
Uuid: []byte(rsp.FileId),
Size: int32(videoLen),
ThumbSize: int32(thumbLen),
Md5: videoHash,
ThumbMd5: thumbHash,
}, nil
}
func (c *QQClient) GetShortVideoUrl(uuid, md5 []byte) string {
i, err := c.sendAndWait(c.buildPttShortVideoDownReqPacket(uuid, md5))
if err != nil {
return ""
}
return i.(string)
}
// 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,
MsgTryUpPttReq: []*pb.TryUpPttReq{
{
GroupCode: groupCode,
SrcUin: c.Uin,
FileMd5: md5,
FileSize: int64(size),
FileName: md5,
SrcTerm: 5,
PlatformType: 9,
BuType: 4,
InnerIp: 0,
BuildVer: "6.5.5.663",
VoiceLength: voiceLength,
Codec: codec,
VoiceType: 1,
BoolNewUpChan: true,
},
},
}
payload, _ := proto.Marshal(req)
return payload
}
// PttCenterSvr.ShortVideoDownReq
func (c *QQClient) buildPttShortVideoDownReqPacket(uuid, md5 []byte) (uint16, []byte) {
seq := c.nextSeq()
body := &pttcenter.ShortVideoReqBody{
Cmd: 400,
Seq: int32(seq),
PttShortVideoDownloadReq: &pttcenter.ShortVideoDownloadReq{
FromUin: c.Uin,
ToUin: c.Uin,
ChatType: 1,
ClientType: 7,
FileId: string(uuid),
GroupCode: 1,
FileMd5: md5,
BusinessType: 1,
FileType: 2,
DownType: 2,
SceneType: 2,
},
}
payload, _ := proto.Marshal(body)
packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.ShortVideoDownReq", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildPttGroupShortVideoProto(videoHash, thumbHash []byte, toUin, videoSize, thumbSize int64) *pttcenter.ShortVideoReqBody {
seq := c.nextSeq()
return &pttcenter.ShortVideoReqBody{
Cmd: 300,
Seq: int32(seq),
PttShortVideoUploadReq: &pttcenter.ShortVideoUploadReq{
FromUin: c.Uin,
ToUin: toUin,
ChatType: 1,
ClientType: 2,
Info: &pttcenter.ShortVideoFileInfo{
FileName: hex.EncodeToString(videoHash) + ".mp4",
FileMd5: videoHash,
ThumbFileMd5: thumbHash,
FileSize: videoSize,
FileResLength: 1280,
FileResWidth: 720,
FileFormat: 3,
FileTime: 120,
ThumbFileSize: thumbSize,
},
GroupCode: toUin,
SupportLargeSize: 1,
},
ExtensionReq: []*pttcenter.ShortVideoExtensionReq{
{
SubBusiType: 0,
UserCnt: 1,
},
},
}
}
// PttCenterSvr.GroupShortVideoUpReq
func (c *QQClient) buildPttGroupShortVideoUploadReqPacket(videoHash, thumbHash []byte, toUin, videoSize, thumbSize int64) (uint16, []byte) {
seq := c.nextSeq()
payload, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, toUin, videoSize, thumbSize))
packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.GroupShortVideoUpReq", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
// PttStore.GroupPttUp
func decodeGroupPttStoreResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
pkt := pb.D388RespBody{}
err := proto.Unmarshal(payload, &pkt)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
rsp := pkt.MsgTryUpPttRsp[0]
if rsp.Result != 0 {
return pttUploadResponse{
ResultCode: rsp.Result,
Message: rsp.FailMsg,
}, nil
}
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: ip,
UploadPort: rsp.Uint32UpPort,
FileKey: rsp.FileKey,
FileId: rsp.FileId2,
}, 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, errors.Wrap(err, "unmarshal cmd0x346 rsp body error")
}
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.Errorf("apply upload rsp error: %d", 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
}
// PttCenterSvr.ShortVideoDownReq
func decodePttShortVideoDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.PttShortVideoDownloadRsp == nil || rsp.PttShortVideoDownloadRsp.DownloadAddr == nil {
return nil, errors.New("resp error")
}
return rsp.PttShortVideoDownloadRsp.DownloadAddr.Host[0] + rsp.PttShortVideoDownloadRsp.DownloadAddr.UrlArgs, nil
}
// PttCenterSvr.GroupShortVideoUpReq
func decodeGroupShortVideoUploadResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.PttShortVideoUploadRsp == nil {
return nil, errors.New("resp error")
}
if rsp.PttShortVideoUploadRsp.RetCode != 0 {
return nil, errors.Errorf("ret code error: %v", rsp.PttShortVideoUploadRsp.RetCode)
}
return rsp.PttShortVideoUploadRsp, nil
}