mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 19:17:38 +08:00
feature upload ptt by highway.
This commit is contained in:
parent
f5aa81ba50
commit
dd94cc86c5
@ -70,6 +70,8 @@ type QQClient struct {
|
||||
timeDiff int64
|
||||
sigInfo *loginSigInfo
|
||||
highwaySession *highwaySessionInfo
|
||||
srvSsoAddrs []string
|
||||
otherSrvAddrs []string
|
||||
fileStorageInfo *jce.FileStoragePushFSSvcList
|
||||
pwdFlag bool
|
||||
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user