mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 19:17:38 +08:00
373 lines
12 KiB
Go
373 lines
12 KiB
Go
package client
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/Mrs4s/MiraiGo/binary"
|
|
"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"
|
|
)
|
|
|
|
func init() {
|
|
decoders["PttCenterSvr.ShortVideoDownReq"] = decodePttShortVideoDownResponse
|
|
decoders["PttCenterSvr.GroupShortVideoUpReq"] = decodeGroupShortVideoUploadResponse
|
|
}
|
|
|
|
var pttWaiter = utils.NewUploadWaiter()
|
|
|
|
// 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)
|
|
|
|
key := hex.EncodeToString(fh)
|
|
pttWaiter.Wait(key)
|
|
defer pttWaiter.Done(key)
|
|
|
|
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
|
|
rsp, err := c.highwayUploadByBDH(voice, length, 29, c.bigDataSession.SigSession, fh, ext, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rsp) == 0 {
|
|
return nil, errors.New("miss rsp")
|
|
}
|
|
pkt := cmd0x388.D388RspBody{}
|
|
if err = proto.Unmarshal(rsp, &pkt); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
|
}
|
|
if len(pkt.TryupPttRsp) == 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.TryupPttRsp[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 io.ReadSeeker) (*message.PrivateVoiceElement, error) {
|
|
h := md5.New()
|
|
length, _ := io.Copy(h, voice)
|
|
fh := h.Sum(nil)
|
|
_, _ = voice.Seek(0, io.SeekStart)
|
|
|
|
key := hex.EncodeToString(fh)
|
|
pttWaiter.Wait(key)
|
|
defer pttWaiter.Done(key)
|
|
|
|
ext := c.buildC2CPttStoreBDHExt(target, fh[:], int32(length), int32(length))
|
|
rsp, err := c.highwayUploadByBDH(voice, length, 26, c.bigDataSession.SigSession, fh, ext, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rsp) == 0 {
|
|
return nil, errors.New("miss rsp")
|
|
}
|
|
pkt := cmd0x346.C346RspBody{}
|
|
if err = proto.Unmarshal(rsp, &pkt); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
|
}
|
|
if pkt.ApplyUploadRsp == nil {
|
|
return nil, errors.New("miss apply upload rsp")
|
|
}
|
|
return &message.PrivateVoiceElement{
|
|
Ptt: &msg.Ptt{
|
|
FileType: proto.Int32(4),
|
|
SrcUin: &c.Uin,
|
|
FileUuid: pkt.ApplyUploadRsp.Uuid,
|
|
FileMd5: fh[:],
|
|
FileName: proto.String(hex.EncodeToString(fh[:]) + ".amr"),
|
|
FileSize: proto.Int32(int32(length)),
|
|
// Reserve: constructPTTExtraInfo(1, int32(len(voice))), // todo length
|
|
BoolValid: proto.Bool(true),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// UploadGroupShortVideo 将视频和封面上传到服务器, 返回 message.ShortVideoElement 可直接发送
|
|
// combinedCache 本地文件缓存, 设置后可多线程上传
|
|
func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSeeker, combinedCache ...string) (*message.ShortVideoElement, error) {
|
|
videoHash, videoLen := utils.ComputeMd5AndLength(video)
|
|
thumbHash, thumbLen := utils.ComputeMd5AndLength(thumb)
|
|
cache := ""
|
|
if len(combinedCache) > 0 {
|
|
cache = combinedCache[0]
|
|
}
|
|
|
|
key := string(videoHash) + string(thumbHash)
|
|
pttWaiter.Wait(key)
|
|
defer pttWaiter.Done(key)
|
|
|
|
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)
|
|
var hwRsp []byte
|
|
multi := utils.MultiReadSeeker(thumb, video)
|
|
h := md5.New()
|
|
length, _ := io.Copy(h, multi)
|
|
fh := h.Sum(nil)
|
|
_, _ = multi.Seek(0, io.SeekStart)
|
|
if cache != "" {
|
|
var file *os.File
|
|
file, err = os.OpenFile(cache, os.O_WRONLY|os.O_CREATE, 0o666)
|
|
cp := func() error {
|
|
_, err := io.Copy(file, utils.MultiReadSeeker(thumb, video))
|
|
return err
|
|
}
|
|
if err != nil || cp() != nil {
|
|
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, ext, true)
|
|
} else {
|
|
_ = file.Close()
|
|
hwRsp, err = c.highwayUploadFileMultiThreadingByBDH(cache, 25, 8, c.bigDataSession.SigSession, ext, true)
|
|
_ = os.Remove(cache)
|
|
}
|
|
} else {
|
|
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, 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 := &cmd0x388.D388ReqBody{
|
|
NetType: proto.Uint32(3),
|
|
Subcmd: proto.Uint32(3),
|
|
TryupPttReq: []*cmd0x388.TryUpPttReq{
|
|
{
|
|
GroupCode: proto.Uint64(uint64(groupCode)),
|
|
SrcUin: proto.Uint64(uint64(c.Uin)),
|
|
FileMd5: md5,
|
|
FileSize: proto.Uint64(uint64(size)),
|
|
FileName: md5,
|
|
SrcTerm: proto.Uint32(5),
|
|
PlatformType: proto.Uint32(9),
|
|
BuType: proto.Uint32(4),
|
|
InnerIp: proto.Uint32(0),
|
|
BuildVer: utils.S2B("6.5.5.663"),
|
|
VoiceLength: proto.Uint32(uint32(voiceLength)),
|
|
Codec: proto.Uint32(uint32(codec)),
|
|
VoiceType: proto.Uint32(1),
|
|
NewUpChan: proto.Bool(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, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
|
|
pkt := cmd0x388.D388RspBody{}
|
|
err := proto.Unmarshal(payload, &pkt)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
|
}
|
|
rsp := pkt.TryupPttRsp[0]
|
|
if rsp.GetResult() != 0 {
|
|
return pttUploadResponse{
|
|
ResultCode: int32(rsp.GetResult()),
|
|
Message: utils.B2S(rsp.GetFailMsg()),
|
|
}, nil
|
|
}
|
|
if rsp.GetFileExit() {
|
|
return pttUploadResponse{IsExists: true}, nil
|
|
}
|
|
ip := make([]string, 0, len(rsp.GetUpIp()))
|
|
for _, i := range rsp.GetUpIp() {
|
|
ip = append(ip, binary.UInt32ToIPV4Address(i))
|
|
}
|
|
return pttUploadResponse{
|
|
UploadKey: rsp.UpUkey,
|
|
UploadIp: ip,
|
|
UploadPort: rsp.GetUpPort(),
|
|
FileKey: rsp.FileKey,
|
|
FileId: int64(rsp.GetFileid()),
|
|
}, nil
|
|
}
|
|
|
|
// PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500
|
|
func (c *QQClient) buildC2CPttStoreBDHExt(target int64, md5 []byte, size, voiceLength int32) []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)
|
|
return payload
|
|
}
|
|
|
|
// PttCenterSvr.ShortVideoDownReq
|
|
func decodePttShortVideoDownResponse(_ *QQClient, _ *incomingPacketInfo, 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, _ *incomingPacketInfo, 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
|
|
}
|