1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-05 03:23:50 +08:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
wdvxdr 2020-10-18 00:29:37 +08:00
commit db8e673266
11 changed files with 545 additions and 270 deletions

View File

@ -16,7 +16,6 @@ import (
"github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x352"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "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/oidb"
"github.com/Mrs4s/MiraiGo/client/pb/pttcenter" "github.com/Mrs4s/MiraiGo/client/pb/pttcenter"
"github.com/Mrs4s/MiraiGo/client/pb/structmsg" "github.com/Mrs4s/MiraiGo/client/pb/structmsg"
@ -594,12 +593,20 @@ func (c *QQClient) buildGroupSendingPacket(groupCode int64, r, pkgNum, pkgIndex,
// MessageSvc.PbSendMsg // MessageSvc.PbSendMsg
func (c *QQClient) buildFriendSendingPacket(target int64, msgSeq, r, pkgNum, pkgIndex, pkgDiv int32, time int64, m []message.IMessageElement) (uint16, []byte) { func (c *QQClient) buildFriendSendingPacket(target int64, msgSeq, r, pkgNum, pkgIndex, pkgDiv int32, time int64, m []message.IMessageElement) (uint16, []byte) {
seq := c.nextSeq() 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{ req := &msg.SendMessageRequest{
RoutingHead: &msg.RoutingHead{C2C: &msg.C2C{ToUin: target}}, RoutingHead: &msg.RoutingHead{C2C: &msg.C2C{ToUin: target}},
ContentHead: &msg.ContentHead{PkgNum: pkgNum, PkgIndex: pkgIndex, DivSeq: pkgDiv}, ContentHead: &msg.ContentHead{PkgNum: pkgNum, PkgIndex: pkgIndex, DivSeq: pkgDiv},
MsgBody: &msg.MessageBody{ MsgBody: &msg.MessageBody{
RichText: &msg.RichText{ RichText: &msg.RichText{
Elems: message.ToProtoElems(m, false), Elems: message.ToProtoElems(m, false),
Ptt: ptt,
}, },
}, },
MsgSeq: msgSeq, MsgSeq: msgSeq,
@ -762,37 +769,6 @@ func (c *QQClient) buildImageUploadPacket(data, updKey []byte, commandId int32,
return 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 // ProfileService.Pb.ReqSystemMsgNew.Group
func (c *QQClient) buildSystemMsgNewGroupPacket() (uint16, []byte) { func (c *QQClient) buildSystemMsgNewGroupPacket() (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()
@ -912,30 +888,6 @@ func (c *QQClient) buildSystemMsgFriendActionPacket(reqId, requester int64, acce
return seq, packet 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 // friendlist.ModifyGroupCardReq
func (c *QQClient) buildEditGroupTagPacket(groupCode, memberUin int64, newTag string) (uint16, []byte) { func (c *QQClient) buildEditGroupTagPacket(groupCode, memberUin int64, newTag string) (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()
@ -1169,53 +1121,6 @@ func (c *QQClient) buildGroupInfoRequestPacket(groupCode int64) (uint16, []byte)
return seq, packet 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 // ProfileService.GroupMngReq
func (c *QQClient) buildQuitGroupPacket(groupCode int64) (uint16, []byte) { func (c *QQClient) buildQuitGroupPacket(groupCode int64) (uint16, []byte) {
seq := c.nextSeq() 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) packet := packets.BuildUniPacket(c.Uin, seq, "LightAppSvc.mini_app_info.GetAppInfoById", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet return seq, packet
} }
func (c *QQClient) buildWordSegmentationPacket(data []byte) (uint16, []byte) { func (c *QQClient) buildWordSegmentationPacket(data []byte) (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()
body := &oidb.D79ReqBody{ body := &oidb.D79ReqBody{

View File

@ -39,6 +39,7 @@ type QQClient struct {
FriendList []*FriendInfo FriendList []*FriendInfo
GroupList []*GroupInfo GroupList []*GroupInfo
Online bool Online bool
NetLooping bool
SequenceId int32 SequenceId int32
OutGoingPacketSessionId []byte OutGoingPacketSessionId []byte
@ -158,6 +159,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
"PttCenterSvr.ShortVideoDownReq": decodePttShortVideoDownResponse, "PttCenterSvr.ShortVideoDownReq": decodePttShortVideoDownResponse,
"LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse, "LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse,
"OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_DOWNLOAD-1200": decodeOfflineFileDownloadResponse, "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_DOWNLOAD-1200": decodeOfflineFileDownloadResponse,
"PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500": decodePrivatePttStoreResponse,
}, },
sigInfo: &loginSigInfo{}, sigInfo: &loginSigInfo{},
requestPacketRequestId: 1921334513, requestPacketRequestId: 1921334513,
@ -200,7 +202,6 @@ func (c *QQClient) Login() (*LoginResponse, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.Online = true
go c.netLoop() go c.netLoop()
seq, packet := c.buildLoginPacket() seq, packet := c.buildLoginPacket()
rsp, err := c.sendAndWait(seq, packet) rsp, err := c.sendAndWait(seq, packet)
@ -209,6 +210,7 @@ func (c *QQClient) Login() (*LoginResponse, error) {
} }
l := rsp.(LoginResponse) l := rsp.(LoginResponse)
if l.Success { if l.Success {
c.Online = true
c.lastLostMsg = "" c.lastLostMsg = ""
c.registerClient() c.registerClient()
if !c.heartbeatEnabled { if !c.heartbeatEnabled {
@ -227,6 +229,7 @@ func (c *QQClient) SubmitCaptcha(result string, sign []byte) (*LoginResponse, er
} }
l := rsp.(LoginResponse) l := rsp.(LoginResponse)
if l.Success { if l.Success {
c.Online = true
c.registerClient() c.registerClient()
if !c.heartbeatEnabled { if !c.heartbeatEnabled {
c.startHeartbeat() c.startHeartbeat()
@ -242,6 +245,7 @@ func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) {
} }
l := rsp.(LoginResponse) l := rsp.(LoginResponse)
if l.Success { if l.Success {
c.Online = true
c.registerClient() c.registerClient()
if !c.heartbeatEnabled { if !c.heartbeatEnabled {
c.startHeartbeat() c.startHeartbeat()
@ -620,11 +624,6 @@ func (c *QQClient) sendGroupPoke(groupCode, target int64) {
_, _ = c.sendAndWait(c.buildGroupPokePacket(groupCode, target)) _, _ = 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) { func (c *QQClient) UploadGroupImage(groupCode int64, img []byte) (*message.GroupImageElement, error) {
h := md5.Sum(img) h := md5.Sum(img)
seq, pkt := c.buildGroupImageStorePacket(groupCode, h[:], int32(len(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") 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) { func (c *QQClient) QueryGroupImage(groupCode int64, hash []byte, size int32) (*message.GroupImageElement, error) {
r, err := c.sendAndWait(c.buildGroupImageStorePacket(groupCode, hash, size)) r, err := c.sendAndWait(c.buildGroupImageStorePacket(groupCode, hash, size))
if err != nil { if err != nil {
@ -877,12 +840,16 @@ func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) {
_ = c.send(pkt) _ = c.send(pkt)
} }
func (c *QQClient) getCookies() string { func (c *QQClient) getSKey() string {
if c.sigInfo.sKeyExpiredTime < time.Now().Unix() { if c.sigInfo.sKeyExpiredTime < time.Now().Unix() {
c.Debug("skey expired. refresh...") c.Debug("skey expired. refresh...")
_, _ = c.sendAndWait(c.buildRequestTgtgtNopicsigPacket()) _, _ = 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 { func (c *QQClient) getCookiesWithDomain(domain string) string {
@ -984,9 +951,10 @@ func (c *QQClient) connect() error {
} }
func (c *QQClient) Disconnect() { func (c *QQClient) Disconnect() {
if c.Online { c.NetLooping = false
c.Online = false c.Online = false
c.Conn.Close() 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() { func (c *QQClient) netLoop() {
c.NetLooping = true
reader := binary.NewNetworkReader(c.Conn) reader := binary.NewNetworkReader(c.Conn)
retry := 0 retry := 0
errCount := 0 errCount := 0
for c.Online { for c.NetLooping {
l, err := reader.ReadInt32() l, err := reader.ReadInt32()
if err == io.EOF || err == io.ErrClosedPipe { if err == io.EOF || err == io.ErrClosedPipe {
c.Error("connection dropped by server: %v", err) c.Error("connection dropped by server: %v", err)
@ -1089,7 +1058,7 @@ func (c *QQClient) netLoop() {
retry++ retry++
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
if retry > 10 { if retry > 10 {
c.Online = false break
} }
continue continue
} }
@ -1099,7 +1068,7 @@ func (c *QQClient) netLoop() {
c.Error("parse incoming packet error: %v", err) c.Error("parse incoming packet error: %v", err)
errCount++ errCount++
if errCount > 5 { if errCount > 5 {
c.Online = false break
} }
//log.Println("parse incoming packet error: " + err.Error()) //log.Println("parse incoming packet error: " + err.Error())
continue continue
@ -1141,6 +1110,7 @@ func (c *QQClient) netLoop() {
} }
}() }()
} }
c.NetLooping = false
c.Online = false c.Online = false
_ = c.Conn.Close() _ = c.Conn.Close()
if c.lastLostMsg == "" { if c.lastLostMsg == "" {

View File

@ -19,12 +19,9 @@ import (
"github.com/Mrs4s/MiraiGo/binary/jce" "github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352" "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/msg"
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
"github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/client/pb/structmsg" "github.com/Mrs4s/MiraiGo/client/pb/structmsg"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
) )
@ -88,8 +85,7 @@ func decodeLoginResponse(c *QQClient, _ uint16, payload []byte) (interface{}, er
}, nil }, nil
} }
if t == 160 { if t == 160 || t == 239 {
if t174, ok := m[0x174]; ok { // 短信验证 if t174, ok := m[0x174]; ok { // 短信验证
c.t104 = m[0x104] c.t104 = m[0x104]
c.t174 = t174 c.t174 = t174
@ -427,7 +423,7 @@ func decodeSvcNotify(c *QQClient, _ uint16, _ []byte) (interface{}, error) {
} }
// StatSvc.GetDevLoginInfo // StatSvc.GetDevLoginInfo
func decodeDevListResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeDevListResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{} request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload)) request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{} data := &jce.RequestDataVersion2{}
@ -440,7 +436,7 @@ func decodeDevListResponse(c *QQClient, _ uint16, payload []byte) (interface{},
} }
// SummaryCard.ReqSummaryCard // SummaryCard.ReqSummaryCard
func decodeSummaryCardResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeSummaryCardResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{} request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload)) request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{} data := &jce.RequestDataVersion2{}
@ -648,33 +644,8 @@ func decodeGroupImageStoreResponse(_ *QQClient, _ uint16, payload []byte) (inter
}, nil }, 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 // LongConn.OffPicUp
func decodeOffPicUpResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeOffPicUpResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := cmd0x352.RspBody{} rsp := cmd0x352.RspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil { if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, err return nil, err
@ -1049,84 +1020,20 @@ func decodeForceOfflinePacket(c *QQClient, _ uint16, payload []byte) (interface{
data.ReadFrom(jce.NewJceReader(request.SBuffer)) data.ReadFrom(jce.NewJceReader(request.SBuffer))
r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:]) r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:])
tips := r.ReadString(2) tips := r.ReadString(2)
if c.Online { c.lastLostMsg = tips
c.lastLostMsg = tips c.NetLooping = false
c.Online = false c.Online = false
}
return nil, nil return nil, nil
} }
// StatSvc.ReqMSFOffline // StatSvc.ReqMSFOffline
func decodeMSFOfflinePacket(c *QQClient, _ uint16, _ []byte) (interface{}, error) { func decodeMSFOfflinePacket(c *QQClient, _ uint16, _ []byte) (interface{}, error) {
if c.Online { c.lastLostMsg = "服务器端强制下线."
c.lastLostMsg = "服务器端强制下线." c.NetLooping = false
c.Online = false c.Online = false
}
return nil, nil 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 // OidbSvc.0xd79
func decodeWordSegmentation(_ *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeWordSegmentation(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
pkg := oidb.OIDBSSOPkg{} pkg := oidb.OIDBSSOPkg{}
@ -1144,7 +1051,7 @@ func decodeWordSegmentation(_ *QQClient, _ uint16, payload []byte) (interface{},
} }
// OidbSvc.0x6d6_2 // OidbSvc.0x6d6_2
func decodeOIDB6d6Response(c *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeOIDB6d6Response(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
pkg := oidb.OIDBSSOPkg{} pkg := oidb.OIDBSSOPkg{}
rsp := oidb.D6D6RspBody{} rsp := oidb.D6D6RspBody{}
if err := proto.Unmarshal(payload, &pkg); err != nil { if err := proto.Unmarshal(payload, &pkg); err != nil {
@ -1193,7 +1100,7 @@ func decodeImageOcrResponse(_ *QQClient, _ uint16, payload []byte) (interface{},
} }
// PttCenterSvr.ShortVideoDownReq // PttCenterSvr.ShortVideoDownReq
func decodePttShortVideoDownResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodePttShortVideoDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{} rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil { if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, err return nil, err
@ -1205,7 +1112,7 @@ func decodePttShortVideoDownResponse(c *QQClient, _ uint16, payload []byte) (int
} }
// LightAppSvc.mini_app_info.GetAppInfoById // 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{} pkg := qweb.QWebRsp{}
rsp := qweb.GetAppInfoByIdRsp{} rsp := qweb.GetAppInfoByIdRsp{}
if err := proto.Unmarshal(payload, &pkg); err != nil { if err := proto.Unmarshal(payload, &pkg); err != nil {

View File

@ -245,7 +245,7 @@ type (
ResourceId string ResourceId string
UploadKey []byte UploadKey []byte
UploadIp []int32 UploadIp []string
UploadPort []int32 UploadPort []int32
FileKey []byte FileKey []byte
} }

View File

@ -57,10 +57,10 @@ func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdId
} }
// 只是为了写的跟上面一样长(bushi当然也应该是最快的玩法 // 只是为了写的跟上面一样长(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 := make([]byte, 512)[:0]
url = append(url, "http://"...) url = append(url, "http://"...)
url = append(url, binary.UInt32ToIPV4Address(uint32(ip))...) url = append(url, ip...)
url = append(url, ':') url = append(url, ':')
url = strconv.AppendInt(url, int64(port), 10) url = strconv.AppendInt(url, int64(port), 10)
url = append(url, "/?ver=4679&ukey="...) 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 { func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
url := fmt.Sprintf( 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", "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, c.Uin,
groupCode, groupCode,
len(img), len(img),

122
client/multimsg.go Normal file
View 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
}

View File

@ -6238,6 +6238,69 @@ func (x *AnimationImageShow) GetAnimationParam() []byte {
return nil 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 protoreflect.FileDescriptor
var file_msg_proto_rawDesc = []byte{ 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, 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, 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, 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, 0x6d, 0x22, 0x74, 0x0a, 0x0e, 0x55, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x55, 0x73, 0x65, 0x72,
0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x54, 0x44, 0x65, 0x66, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x55, 0x69, 0x6e, 0x54, 0x79,
0x49, 0x4e, 0x55, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x55, 0x69,
0x02, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x3b, 0x6d, 0x73, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f,
0x6f, 0x33, 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 ( var (
@ -7238,7 +7308,7 @@ func file_msg_proto_rawDescGZIP() []byte {
} }
var file_msg_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 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{}{ var file_msg_proto_goTypes = []interface{}{
(SyncFlag)(0), // 0: SyncFlag (SyncFlag)(0), // 0: SyncFlag
(*GetMessageRequest)(nil), // 1: GetMessageRequest (*GetMessageRequest)(nil), // 1: GetMessageRequest
@ -7301,6 +7371,7 @@ var file_msg_proto_goTypes = []interface{}{
(*SubMsgType0X4Body)(nil), // 58: SubMsgType0x4Body (*SubMsgType0X4Body)(nil), // 58: SubMsgType0x4Body
(*ResvAttr)(nil), // 59: ResvAttr (*ResvAttr)(nil), // 59: ResvAttr
(*AnimationImageShow)(nil), // 60: AnimationImageShow (*AnimationImageShow)(nil), // 60: AnimationImageShow
(*UinTypeUserDef)(nil), // 61: UinTypeUserDef
} }
var file_msg_proto_depIdxs = []int32{ var file_msg_proto_depIdxs = []int32{
0, // 0: GetMessageRequest.syncFlag:type_name -> SyncFlag 0, // 0: GetMessageRequest.syncFlag:type_name -> SyncFlag
@ -8099,6 +8170,18 @@ func file_msg_proto_init() {
return nil 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{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
@ -8106,7 +8189,7 @@ func file_msg_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_msg_proto_rawDesc, RawDescriptor: file_msg_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 60, NumMessages: 61,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },

View File

@ -724,3 +724,9 @@ message AnimationImageShow {
int32 effect_id = 1; int32 effect_id = 1;
bytes animation_param = 2; bytes animation_param = 2;
} }
message UinTypeUserDef {
int32 fromUinType = 1;
int64 fromGroupCode = 2;
string fileUuid = 3;
}

206
client/ptt.go Normal file
View 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
View 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
}

View File

@ -47,6 +47,11 @@ type GroupVoiceElement struct {
Ptt *msg.Ptt Ptt *msg.Ptt
} }
type PrivateVoiceElement struct {
Data []byte
Ptt *msg.Ptt
}
type FriendImageElement struct { type FriendImageElement struct {
ImageId string ImageId string
Md5 []byte Md5 []byte
@ -267,6 +272,10 @@ func (e *GroupVoiceElement) Type() ElementType {
return Voice return Voice
} }
func (e *PrivateVoiceElement) Type() ElementType {
return Voice
}
func (e *VoiceElement) Type() ElementType { func (e *VoiceElement) Type() ElementType {
return Voice return Voice
} }