mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 11:07:40 +08:00
207 lines
6.0 KiB
Go
207 lines
6.0 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/message"
|
|
"github.com/Mrs4s/MiraiGo/protocol/packets"
|
|
"github.com/pkg/errors"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// 语音相关处理逻辑
|
|
|
|
// 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)
|
|
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),
|
|
SrcUin: &c.Uin,
|
|
FileMd5: h[:],
|
|
FileName: proto.String(hex.EncodeToString(h[:]) + ".amr"),
|
|
FileSize: proto.Int32(int32(len(voice))),
|
|
GroupFileKey: rsp.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
|
|
}
|
|
|
|
// PttStore.GroupPttUp
|
|
func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, codec, voiceLength int32) (uint16, []byte) {
|
|
seq := c.nextSeq()
|
|
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,
|
|
},
|
|
},
|
|
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
|
|
}
|
|
|
|
// 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,
|
|
}, 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
|
|
}
|