mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 19:17:38 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
db8e673266
@ -16,7 +16,6 @@ import (
|
||||
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/pttcenter"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/structmsg"
|
||||
@ -594,12 +593,20 @@ func (c *QQClient) buildGroupSendingPacket(groupCode int64, r, pkgNum, pkgIndex,
|
||||
// MessageSvc.PbSendMsg
|
||||
func (c *QQClient) buildFriendSendingPacket(target int64, msgSeq, r, pkgNum, pkgIndex, pkgDiv int32, time int64, m []message.IMessageElement) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
var ptt *msg.Ptt
|
||||
if len(m) > 0 {
|
||||
if p, ok := m[0].(*message.PrivateVoiceElement); ok {
|
||||
ptt = p.Ptt
|
||||
m = []message.IMessageElement{}
|
||||
}
|
||||
}
|
||||
req := &msg.SendMessageRequest{
|
||||
RoutingHead: &msg.RoutingHead{C2C: &msg.C2C{ToUin: target}},
|
||||
ContentHead: &msg.ContentHead{PkgNum: pkgNum, PkgIndex: pkgIndex, DivSeq: pkgDiv},
|
||||
MsgBody: &msg.MessageBody{
|
||||
RichText: &msg.RichText{
|
||||
Elems: message.ToProtoElems(m, false),
|
||||
Ptt: ptt,
|
||||
},
|
||||
},
|
||||
MsgSeq: msgSeq,
|
||||
@ -762,37 +769,6 @@ func (c *QQClient) buildImageUploadPacket(data, updKey []byte, commandId int32,
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ProfileService.Pb.ReqSystemMsgNew.Group
|
||||
func (c *QQClient) buildSystemMsgNewGroupPacket() (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
@ -912,30 +888,6 @@ func (c *QQClient) buildSystemMsgFriendActionPacket(reqId, requester int64, acce
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// PbMessageSvc.PbMsgWithDraw
|
||||
func (c *QQClient) buildGroupRecallPacket(groupCode int64, msgSeq, msgRan int32) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &msg.MsgWithDrawReq{
|
||||
GroupWithDraw: []*msg.GroupMsgWithDrawReq{
|
||||
{
|
||||
SubCmd: 1,
|
||||
GroupCode: groupCode,
|
||||
MsgList: []*msg.GroupMsgInfo{
|
||||
{
|
||||
MsgSeq: msgSeq,
|
||||
MsgRandom: msgRan,
|
||||
MsgType: 0,
|
||||
},
|
||||
},
|
||||
UserDef: []byte{0x08, 0x00},
|
||||
},
|
||||
},
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "PbMessageSvc.PbMsgWithDraw", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// friendlist.ModifyGroupCardReq
|
||||
func (c *QQClient) buildEditGroupTagPacket(groupCode, memberUin int64, newTag string) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
@ -1169,53 +1121,6 @@ func (c *QQClient) buildGroupInfoRequestPacket(groupCode int64) (uint16, []byte)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyUp
|
||||
func (c *QQClient) buildMultiApplyUpPacket(data, hash []byte, buType int32, groupUin int64) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &multimsg.MultiReqBody{
|
||||
Subcmd: 1,
|
||||
TermType: 5,
|
||||
PlatformType: 9,
|
||||
NetType: 3,
|
||||
BuildVer: "8.2.0.1296",
|
||||
MultimsgApplyupReq: []*multimsg.MultiMsgApplyUpReq{
|
||||
{
|
||||
DstUin: groupUin,
|
||||
MsgSize: int64(len(data)),
|
||||
MsgMd5: hash,
|
||||
MsgType: 3,
|
||||
},
|
||||
},
|
||||
BuType: buType,
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "MultiMsg.ApplyUp", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyDown
|
||||
func (c *QQClient) buildMultiApplyDownPacket(resId string) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &multimsg.MultiReqBody{
|
||||
Subcmd: 2,
|
||||
TermType: 5,
|
||||
PlatformType: 9,
|
||||
NetType: 3,
|
||||
BuildVer: "8.2.0.1296",
|
||||
MultimsgApplydownReq: []*multimsg.MultiMsgApplyDownReq{
|
||||
{
|
||||
MsgResid: []byte(resId),
|
||||
MsgType: 3,
|
||||
},
|
||||
},
|
||||
BuType: 2,
|
||||
ReqChannelType: 2,
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "MultiMsg.ApplyDown", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// ProfileService.GroupMngReq
|
||||
func (c *QQClient) buildQuitGroupPacket(groupCode int64) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
@ -1332,6 +1237,7 @@ func (c *QQClient) buildAppInfoRequestPacket(id string) (uint16, []byte) {
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "LightAppSvc.mini_app_info.GetAppInfoById", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
func (c *QQClient) buildWordSegmentationPacket(data []byte) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
body := &oidb.D79ReqBody{
|
||||
|
@ -39,6 +39,7 @@ type QQClient struct {
|
||||
FriendList []*FriendInfo
|
||||
GroupList []*GroupInfo
|
||||
Online bool
|
||||
NetLooping bool
|
||||
|
||||
SequenceId int32
|
||||
OutGoingPacketSessionId []byte
|
||||
@ -158,6 +159,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,
|
||||
@ -200,7 +202,6 @@ func (c *QQClient) Login() (*LoginResponse, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Online = true
|
||||
go c.netLoop()
|
||||
seq, packet := c.buildLoginPacket()
|
||||
rsp, err := c.sendAndWait(seq, packet)
|
||||
@ -209,6 +210,7 @@ func (c *QQClient) Login() (*LoginResponse, error) {
|
||||
}
|
||||
l := rsp.(LoginResponse)
|
||||
if l.Success {
|
||||
c.Online = true
|
||||
c.lastLostMsg = ""
|
||||
c.registerClient()
|
||||
if !c.heartbeatEnabled {
|
||||
@ -227,6 +229,7 @@ func (c *QQClient) SubmitCaptcha(result string, sign []byte) (*LoginResponse, er
|
||||
}
|
||||
l := rsp.(LoginResponse)
|
||||
if l.Success {
|
||||
c.Online = true
|
||||
c.registerClient()
|
||||
if !c.heartbeatEnabled {
|
||||
c.startHeartbeat()
|
||||
@ -242,6 +245,7 @@ func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) {
|
||||
}
|
||||
l := rsp.(LoginResponse)
|
||||
if l.Success {
|
||||
c.Online = true
|
||||
c.registerClient()
|
||||
if !c.heartbeatEnabled {
|
||||
c.startHeartbeat()
|
||||
@ -620,11 +624,6 @@ func (c *QQClient) sendGroupPoke(groupCode, target int64) {
|
||||
_, _ = c.sendAndWait(c.buildGroupPokePacket(groupCode, target))
|
||||
}
|
||||
|
||||
func (c *QQClient) RecallGroupMessage(groupCode int64, msgId, msgInternalId int32) {
|
||||
_, pkt := c.buildGroupRecallPacket(groupCode, msgId, msgInternalId)
|
||||
_ = c.send(pkt)
|
||||
}
|
||||
|
||||
func (c *QQClient) UploadGroupImage(groupCode int64, img []byte) (*message.GroupImageElement, error) {
|
||||
h := md5.Sum(img)
|
||||
seq, pkt := c.buildGroupImageStorePacket(groupCode, h[:], int32(len(img)))
|
||||
@ -691,42 +690,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[:], 2)
|
||||
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 {
|
||||
@ -877,12 +840,16 @@ func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) {
|
||||
_ = c.send(pkt)
|
||||
}
|
||||
|
||||
func (c *QQClient) getCookies() string {
|
||||
func (c *QQClient) getSKey() string {
|
||||
if c.sigInfo.sKeyExpiredTime < time.Now().Unix() {
|
||||
c.Debug("skey expired. refresh...")
|
||||
_, _ = c.sendAndWait(c.buildRequestTgtgtNopicsigPacket())
|
||||
}
|
||||
return fmt.Sprintf("uin=o%d; skey=%s;", c.Uin, c.sigInfo.sKey)
|
||||
return string(c.sigInfo.sKey)
|
||||
}
|
||||
|
||||
func (c *QQClient) getCookies() string {
|
||||
return fmt.Sprintf("uin=o%d; skey=%s;", c.Uin, c.getSKey())
|
||||
}
|
||||
|
||||
func (c *QQClient) getCookiesWithDomain(domain string) string {
|
||||
@ -984,9 +951,10 @@ func (c *QQClient) connect() error {
|
||||
}
|
||||
|
||||
func (c *QQClient) Disconnect() {
|
||||
if c.Online {
|
||||
c.Online = false
|
||||
c.Conn.Close()
|
||||
c.NetLooping = false
|
||||
c.Online = false
|
||||
if c.Conn != nil {
|
||||
_ = c.Conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1071,10 +1039,11 @@ func (c *QQClient) sendAndWait(seq uint16, pkt []byte) (interface{}, error) {
|
||||
}
|
||||
|
||||
func (c *QQClient) netLoop() {
|
||||
c.NetLooping = true
|
||||
reader := binary.NewNetworkReader(c.Conn)
|
||||
retry := 0
|
||||
errCount := 0
|
||||
for c.Online {
|
||||
for c.NetLooping {
|
||||
l, err := reader.ReadInt32()
|
||||
if err == io.EOF || err == io.ErrClosedPipe {
|
||||
c.Error("connection dropped by server: %v", err)
|
||||
@ -1089,7 +1058,7 @@ func (c *QQClient) netLoop() {
|
||||
retry++
|
||||
time.Sleep(time.Second * 3)
|
||||
if retry > 10 {
|
||||
c.Online = false
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -1099,7 +1068,7 @@ func (c *QQClient) netLoop() {
|
||||
c.Error("parse incoming packet error: %v", err)
|
||||
errCount++
|
||||
if errCount > 5 {
|
||||
c.Online = false
|
||||
break
|
||||
}
|
||||
//log.Println("parse incoming packet error: " + err.Error())
|
||||
continue
|
||||
@ -1141,6 +1110,7 @@ func (c *QQClient) netLoop() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
c.NetLooping = false
|
||||
c.Online = false
|
||||
_ = c.Conn.Close()
|
||||
if c.lastLostMsg == "" {
|
||||
|
@ -19,12 +19,9 @@ import (
|
||||
"github.com/Mrs4s/MiraiGo/binary/jce"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/structmsg"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
@ -88,8 +85,7 @@ func decodeLoginResponse(c *QQClient, _ uint16, payload []byte) (interface{}, er
|
||||
}, nil
|
||||
}
|
||||
|
||||
if t == 160 {
|
||||
|
||||
if t == 160 || t == 239 {
|
||||
if t174, ok := m[0x174]; ok { // 短信验证
|
||||
c.t104 = m[0x104]
|
||||
c.t174 = t174
|
||||
@ -427,7 +423,7 @@ func decodeSvcNotify(c *QQClient, _ uint16, _ []byte) (interface{}, error) {
|
||||
}
|
||||
|
||||
// StatSvc.GetDevLoginInfo
|
||||
func decodeDevListResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodeDevListResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
request := &jce.RequestPacket{}
|
||||
request.ReadFrom(jce.NewJceReader(payload))
|
||||
data := &jce.RequestDataVersion2{}
|
||||
@ -440,7 +436,7 @@ func decodeDevListResponse(c *QQClient, _ uint16, payload []byte) (interface{},
|
||||
}
|
||||
|
||||
// SummaryCard.ReqSummaryCard
|
||||
func decodeSummaryCardResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodeSummaryCardResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
request := &jce.RequestPacket{}
|
||||
request.ReadFrom(jce.NewJceReader(payload))
|
||||
data := &jce.RequestDataVersion2{}
|
||||
@ -648,33 +644,8 @@ func decodeGroupImageStoreResponse(_ *QQClient, _ uint16, payload []byte) (inter
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PttStore.GroupPttUp
|
||||
func decodeGroupPttStoreResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
pkt := pb.D388RespBody{}
|
||||
err := proto.Unmarshal(payload, &pkt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
return pttUploadResponse{
|
||||
UploadKey: rsp.UpUkey,
|
||||
UploadIp: rsp.Uint32UpIp,
|
||||
UploadPort: rsp.Uint32UpPort,
|
||||
FileKey: rsp.FileKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LongConn.OffPicUp
|
||||
func decodeOffPicUpResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodeOffPicUpResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
rsp := cmd0x352.RspBody{}
|
||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||
return nil, err
|
||||
@ -1049,84 +1020,20 @@ func decodeForceOfflinePacket(c *QQClient, _ uint16, payload []byte) (interface{
|
||||
data.ReadFrom(jce.NewJceReader(request.SBuffer))
|
||||
r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:])
|
||||
tips := r.ReadString(2)
|
||||
if c.Online {
|
||||
c.lastLostMsg = tips
|
||||
c.Online = false
|
||||
}
|
||||
c.lastLostMsg = tips
|
||||
c.NetLooping = false
|
||||
c.Online = false
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// StatSvc.ReqMSFOffline
|
||||
func decodeMSFOfflinePacket(c *QQClient, _ uint16, _ []byte) (interface{}, error) {
|
||||
if c.Online {
|
||||
c.lastLostMsg = "服务器端强制下线."
|
||||
c.Online = false
|
||||
}
|
||||
c.lastLostMsg = "服务器端强制下线."
|
||||
c.NetLooping = false
|
||||
c.Online = false
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyUp
|
||||
func decodeMultiApplyUpResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
body := multimsg.MultiRspBody{}
|
||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body.MultimsgApplyupRsp) == 0 {
|
||||
return nil, errors.New("rsp is empty")
|
||||
}
|
||||
rsp := body.MultimsgApplyupRsp[0]
|
||||
switch rsp.Result {
|
||||
case 0:
|
||||
return rsp, nil
|
||||
case 193:
|
||||
return nil, errors.New("too large")
|
||||
}
|
||||
return nil, errors.New("failed")
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyDown
|
||||
func decodeMultiApplyDownResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
body := multimsg.MultiRspBody{}
|
||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body.MultimsgApplydownRsp) == 0 {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
rsp := body.MultimsgApplydownRsp[0]
|
||||
prefix := func() string {
|
||||
if rsp.MsgExternInfo != nil && rsp.MsgExternInfo.ChannelType == 2 {
|
||||
return "https://ssl.htdata.qq.com"
|
||||
}
|
||||
return fmt.Sprintf("http://%s:%d", binary.UInt32ToIPV4Address(uint32(rsp.Uint32DownIp[0])), body.MultimsgApplydownRsp[0].Uint32DownPort[0])
|
||||
}()
|
||||
b, err := utils.HttpGetBytes(fmt.Sprintf("%s%s", prefix, string(rsp.ThumbDownPara)), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b[0] != 40 {
|
||||
return nil, errors.New("unexpected body data")
|
||||
}
|
||||
tea := binary.NewTeaCipher(body.MultimsgApplydownRsp[0].MsgKey)
|
||||
r := binary.NewReader(b[1:])
|
||||
i1 := r.ReadInt32()
|
||||
i2 := r.ReadInt32()
|
||||
if i1 > 0 {
|
||||
r.ReadBytes(int(i1)) // im msg head
|
||||
}
|
||||
data := tea.Decrypt(r.ReadBytes(int(i2)))
|
||||
lb := longmsg.LongRspBody{}
|
||||
if err = proto.Unmarshal(data, &lb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uc := binary.GZipUncompress(lb.MsgDownRsp[0].MsgContent)
|
||||
mt := msg.PbMultiMsgTransmit{}
|
||||
if err = proto.Unmarshal(uc, &mt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mt, nil
|
||||
}
|
||||
|
||||
// OidbSvc.0xd79
|
||||
func decodeWordSegmentation(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
pkg := oidb.OIDBSSOPkg{}
|
||||
@ -1144,7 +1051,7 @@ func decodeWordSegmentation(_ *QQClient, _ uint16, payload []byte) (interface{},
|
||||
}
|
||||
|
||||
// OidbSvc.0x6d6_2
|
||||
func decodeOIDB6d6Response(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodeOIDB6d6Response(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
pkg := oidb.OIDBSSOPkg{}
|
||||
rsp := oidb.D6D6RspBody{}
|
||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
||||
@ -1193,7 +1100,7 @@ func decodeImageOcrResponse(_ *QQClient, _ uint16, payload []byte) (interface{},
|
||||
}
|
||||
|
||||
// PttCenterSvr.ShortVideoDownReq
|
||||
func decodePttShortVideoDownResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodePttShortVideoDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
rsp := pttcenter.ShortVideoRspBody{}
|
||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||
return nil, err
|
||||
@ -1205,7 +1112,7 @@ func decodePttShortVideoDownResponse(c *QQClient, _ uint16, payload []byte) (int
|
||||
}
|
||||
|
||||
// LightAppSvc.mini_app_info.GetAppInfoById
|
||||
func decodeAppInfoResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
func decodeAppInfoResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
pkg := qweb.QWebRsp{}
|
||||
rsp := qweb.GetAppInfoByIdRsp{}
|
||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
||||
|
@ -245,7 +245,7 @@ type (
|
||||
|
||||
ResourceId string
|
||||
UploadKey []byte
|
||||
UploadIp []int32
|
||||
UploadIp []string
|
||||
UploadPort []int32
|
||||
FileKey []byte
|
||||
}
|
||||
|
@ -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, codec int64) 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="...)
|
||||
@ -85,7 +85,7 @@ func (c *QQClient) uploadGroupPtt(ip, port int32, updKey, fileKey, data, md5 []b
|
||||
func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
|
||||
url := fmt.Sprintf(
|
||||
"http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
|
||||
string(c.sigInfo.sKey),
|
||||
c.getSKey(),
|
||||
c.Uin,
|
||||
groupCode,
|
||||
len(img),
|
||||
|
122
client/multimsg.go
Normal file
122
client/multimsg.go
Normal file
@ -0,0 +1,122 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
|
||||
"github.com/Mrs4s/MiraiGo/protocol/packets"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// MultiMsg.ApplyUp
|
||||
func (c *QQClient) buildMultiApplyUpPacket(data, hash []byte, buType int32, groupUin int64) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &multimsg.MultiReqBody{
|
||||
Subcmd: 1,
|
||||
TermType: 5,
|
||||
PlatformType: 9,
|
||||
NetType: 3,
|
||||
BuildVer: "8.2.0.1296",
|
||||
MultimsgApplyupReq: []*multimsg.MultiMsgApplyUpReq{
|
||||
{
|
||||
DstUin: groupUin,
|
||||
MsgSize: int64(len(data)),
|
||||
MsgMd5: hash,
|
||||
MsgType: 3,
|
||||
},
|
||||
},
|
||||
BuType: buType,
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "MultiMsg.ApplyUp", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyUp
|
||||
func decodeMultiApplyUpResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
body := multimsg.MultiRspBody{}
|
||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body.MultimsgApplyupRsp) == 0 {
|
||||
return nil, errors.New("rsp is empty")
|
||||
}
|
||||
rsp := body.MultimsgApplyupRsp[0]
|
||||
switch rsp.Result {
|
||||
case 0:
|
||||
return rsp, nil
|
||||
case 193:
|
||||
return nil, errors.New("too large")
|
||||
}
|
||||
return nil, errors.New("failed")
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyDown
|
||||
func (c *QQClient) buildMultiApplyDownPacket(resId string) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &multimsg.MultiReqBody{
|
||||
Subcmd: 2,
|
||||
TermType: 5,
|
||||
PlatformType: 9,
|
||||
NetType: 3,
|
||||
BuildVer: "8.2.0.1296",
|
||||
MultimsgApplydownReq: []*multimsg.MultiMsgApplyDownReq{
|
||||
{
|
||||
MsgResid: []byte(resId),
|
||||
MsgType: 3,
|
||||
},
|
||||
},
|
||||
BuType: 2,
|
||||
ReqChannelType: 2,
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "MultiMsg.ApplyDown", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
// MultiMsg.ApplyDown
|
||||
func decodeMultiApplyDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
|
||||
body := multimsg.MultiRspBody{}
|
||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body.MultimsgApplydownRsp) == 0 {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
rsp := body.MultimsgApplydownRsp[0]
|
||||
prefix := func() string {
|
||||
if rsp.MsgExternInfo != nil && rsp.MsgExternInfo.ChannelType == 2 {
|
||||
return "https://ssl.htdata.qq.com"
|
||||
}
|
||||
return fmt.Sprintf("http://%s:%d", binary.UInt32ToIPV4Address(uint32(rsp.Uint32DownIp[0])), body.MultimsgApplydownRsp[0].Uint32DownPort[0])
|
||||
}()
|
||||
b, err := utils.HttpGetBytes(fmt.Sprintf("%s%s", prefix, string(rsp.ThumbDownPara)), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b[0] != 40 {
|
||||
return nil, errors.New("unexpected body data")
|
||||
}
|
||||
tea := binary.NewTeaCipher(body.MultimsgApplydownRsp[0].MsgKey)
|
||||
r := binary.NewReader(b[1:])
|
||||
i1 := r.ReadInt32()
|
||||
i2 := r.ReadInt32()
|
||||
if i1 > 0 {
|
||||
r.ReadBytes(int(i1)) // im msg head
|
||||
}
|
||||
data := tea.Decrypt(r.ReadBytes(int(i2)))
|
||||
lb := longmsg.LongRspBody{}
|
||||
if err = proto.Unmarshal(data, &lb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uc := binary.GZipUncompress(lb.MsgDownRsp[0].MsgContent)
|
||||
mt := msg.PbMultiMsgTransmit{}
|
||||
if err = proto.Unmarshal(uc, &mt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mt, nil
|
||||
}
|
@ -6238,6 +6238,69 @@ func (x *AnimationImageShow) GetAnimationParam() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UinTypeUserDef struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
FromUinType int32 `protobuf:"varint,1,opt,name=fromUinType,proto3" json:"fromUinType,omitempty"`
|
||||
FromGroupCode int64 `protobuf:"varint,2,opt,name=fromGroupCode,proto3" json:"fromGroupCode,omitempty"`
|
||||
FileUuid string `protobuf:"bytes,3,opt,name=fileUuid,proto3" json:"fileUuid,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UinTypeUserDef) Reset() {
|
||||
*x = UinTypeUserDef{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_msg_proto_msgTypes[60]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UinTypeUserDef) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UinTypeUserDef) ProtoMessage() {}
|
||||
|
||||
func (x *UinTypeUserDef) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_msg_proto_msgTypes[60]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UinTypeUserDef.ProtoReflect.Descriptor instead.
|
||||
func (*UinTypeUserDef) Descriptor() ([]byte, []int) {
|
||||
return file_msg_proto_rawDescGZIP(), []int{60}
|
||||
}
|
||||
|
||||
func (x *UinTypeUserDef) GetFromUinType() int32 {
|
||||
if x != nil {
|
||||
return x.FromUinType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *UinTypeUserDef) GetFromGroupCode() int64 {
|
||||
if x != nil {
|
||||
return x.FromGroupCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *UinTypeUserDef) GetFileUuid() string {
|
||||
if x != nil {
|
||||
return x.FileUuid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_msg_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_msg_proto_rawDesc = []byte{
|
||||
@ -7218,11 +7281,18 @@ var file_msg_proto_rawDesc = []byte{
|
||||
0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6e, 0x69, 0x6d,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x0e, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x72, 0x61,
|
||||
0x6d, 0x2a, 0x2e, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x09, 0x0a,
|
||||
0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x54,
|
||||
0x49, 0x4e, 0x55, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10,
|
||||
0x02, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x3b, 0x6d, 0x73, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
0x6d, 0x22, 0x74, 0x0a, 0x0e, 0x55, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x55, 0x73, 0x65, 0x72,
|
||||
0x44, 0x65, 0x66, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x55, 0x69, 0x6e, 0x54, 0x79,
|
||||
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x55, 0x69,
|
||||
0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f,
|
||||
0x75, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x55, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x55, 0x75, 0x69, 0x64, 0x2a, 0x2e, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x46,
|
||||
0x6c, 0x61, 0x67, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x0d,
|
||||
0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x54, 0x49, 0x4e, 0x55, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x3b, 0x6d, 0x73, 0x67,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -7238,7 +7308,7 @@ func file_msg_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_msg_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 60)
|
||||
var file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 61)
|
||||
var file_msg_proto_goTypes = []interface{}{
|
||||
(SyncFlag)(0), // 0: SyncFlag
|
||||
(*GetMessageRequest)(nil), // 1: GetMessageRequest
|
||||
@ -7301,6 +7371,7 @@ var file_msg_proto_goTypes = []interface{}{
|
||||
(*SubMsgType0X4Body)(nil), // 58: SubMsgType0x4Body
|
||||
(*ResvAttr)(nil), // 59: ResvAttr
|
||||
(*AnimationImageShow)(nil), // 60: AnimationImageShow
|
||||
(*UinTypeUserDef)(nil), // 61: UinTypeUserDef
|
||||
}
|
||||
var file_msg_proto_depIdxs = []int32{
|
||||
0, // 0: GetMessageRequest.syncFlag:type_name -> SyncFlag
|
||||
@ -8099,6 +8170,18 @@ func file_msg_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_msg_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UinTypeUserDef); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@ -8106,7 +8189,7 @@ func file_msg_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_msg_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 60,
|
||||
NumMessages: 61,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
@ -723,4 +723,10 @@ message ResvAttr {
|
||||
message AnimationImageShow {
|
||||
int32 effect_id = 1;
|
||||
bytes animation_param = 2;
|
||||
}
|
||||
|
||||
message UinTypeUserDef {
|
||||
int32 fromUinType = 1;
|
||||
int64 fromGroupCode = 2;
|
||||
string fileUuid = 3;
|
||||
}
|
206
client/ptt.go
Normal file
206
client/ptt.go
Normal file
@ -0,0 +1,206 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// 语音相关处理逻辑
|
||||
|
||||
// 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: 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
|
||||
}
|
||||
|
||||
// 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: 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()
|
||||
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, err
|
||||
}
|
||||
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, 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
|
||||
}
|
66
client/recall.go
Normal file
66
client/recall.go
Normal file
@ -0,0 +1,66 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||
"github.com/Mrs4s/MiraiGo/protocol/packets"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// 撤回相关处理逻辑
|
||||
|
||||
func (c *QQClient) RecallGroupMessage(groupCode int64, msgId, msgInternalId int32) {
|
||||
_, pkt := c.buildGroupRecallPacket(groupCode, msgId, msgInternalId)
|
||||
_ = c.send(pkt)
|
||||
}
|
||||
|
||||
func (c *QQClient) RecallPrivateMessage(uin, ts int64, msgId, msgInternalId int32) {
|
||||
_, pkt := c.buildPrivateRecallPacket(uin, ts, msgId, msgInternalId)
|
||||
_ = c.send(pkt)
|
||||
}
|
||||
|
||||
// PbMessageSvc.PbMsgWithDraw
|
||||
func (c *QQClient) buildGroupRecallPacket(groupCode int64, msgSeq, msgRan int32) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &msg.MsgWithDrawReq{
|
||||
GroupWithDraw: []*msg.GroupMsgWithDrawReq{
|
||||
{
|
||||
SubCmd: 1,
|
||||
GroupCode: groupCode,
|
||||
MsgList: []*msg.GroupMsgInfo{
|
||||
{
|
||||
MsgSeq: msgSeq,
|
||||
MsgRandom: msgRan,
|
||||
MsgType: 0,
|
||||
},
|
||||
},
|
||||
UserDef: []byte{0x08, 0x00},
|
||||
},
|
||||
},
|
||||
}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "PbMessageSvc.PbMsgWithDraw", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
||||
|
||||
func (c *QQClient) buildPrivateRecallPacket(uin, ts int64, msgSeq, random int32) (uint16, []byte) {
|
||||
seq := c.nextSeq()
|
||||
req := &msg.MsgWithDrawReq{C2CWithDraw: []*msg.C2CMsgWithDrawReq{
|
||||
{
|
||||
MsgInfo: []*msg.C2CMsgInfo{
|
||||
{
|
||||
FromUin: c.Uin,
|
||||
ToUin: uin,
|
||||
MsgTime: ts,
|
||||
MsgUid: int64(random),
|
||||
MsgSeq: msgSeq,
|
||||
MsgRandom: random,
|
||||
},
|
||||
},
|
||||
Reserved: []byte{0x08, 0x00},
|
||||
SubCmd: 1,
|
||||
},
|
||||
}}
|
||||
payload, _ := proto.Marshal(req)
|
||||
packet := packets.BuildUniPacket(c.Uin, seq, "PbMessageSvc.PbMsgWithDraw", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
|
||||
return seq, packet
|
||||
}
|
@ -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
|
||||
@ -267,6 +272,10 @@ func (e *GroupVoiceElement) Type() ElementType {
|
||||
return Voice
|
||||
}
|
||||
|
||||
func (e *PrivateVoiceElement) Type() ElementType {
|
||||
return Voice
|
||||
}
|
||||
|
||||
func (e *VoiceElement) Type() ElementType {
|
||||
return Voice
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user