diff --git a/client/global.go b/client/global.go index 7cbefa250..4390bf365 100644 --- a/client/global.go +++ b/client/global.go @@ -195,16 +195,16 @@ func (c *QQClient) parsePrivateMessage(msg *msg.Message) *message.PrivateMessage Sender: sender, Self: c.Uin, Elements: func() []message.IMessageElement { - if msg.Body.RichText.Ptt != nil { - return []message.IMessageElement{ - &message.VoiceElement{ - Name: msg.Body.RichText.Ptt.FileName.Unwrap(), - Md5: msg.Body.RichText.Ptt.FileMd5, - Size: msg.Body.RichText.Ptt.FileSize.Unwrap(), - Url: string(msg.Body.RichText.Ptt.DownPara), - }, - } - } + // if msg.Body.RichText.Ptt != nil { + // return []message.IMessageElement{ + // &message.VoiceElement{ + // Name: msg.Body.RichText.Ptt.FileName.Unwrap(), + // Md5: msg.Body.RichText.Ptt.FileMd5, + // Size: msg.Body.RichText.Ptt.FileSize.Unwrap(), + // Url: string(msg.Body.RichText.Ptt.DownPara), + // }, + // } + // } return message.ParseMessageElems(msg.Body.RichText.Elems) }(), } diff --git a/client/pb/msg/msg.pb.go b/client/pb/msg/msg.pb.go index 8e569df9b..6e5f1f36c 100644 --- a/client/pb/msg/msg.pb.go +++ b/client/pb/msg/msg.pb.go @@ -881,6 +881,12 @@ type MsgElemInfoServtype37 struct { Randomtype proto.Option[uint32] `protobuf:"varint,9,opt"` } +type PbMultiMediaElement struct { + Elem1 *PbMultiMediaElement_Elem1 `protobuf:"bytes,1,opt"` + Elem2 *PbMultiMediaElement_Elem2 `protobuf:"bytes,2,opt"` + _ [0]func() +} + type ElemFlags2_Inst struct { AppId proto.Option[uint32] `protobuf:"varint,1,opt"` InstId proto.Option[uint32] `protobuf:"varint,2,opt"` @@ -893,30 +899,47 @@ type NotOnlineImage_PbReserve struct { _ [0]func() } -type PbMultiMediaElement struct { - Elem1 *struct { - Meta *struct { - Data *struct { - FileLen proto.Option[int32] `protobuf:"varint,1,opt"` - PicMd5 []byte `protobuf:"bytes,2,opt"` - } `protobuf:"bytes,1,opt"` - FilePath proto.Option[string] `protobuf:"bytes,2,opt"` - } `protobuf:"bytes,1,opt"` - - Data *struct { - ImgURL proto.Option[string] `protobuf:"bytes,1,opt"` - Domain proto.Option[string] `protobuf:"bytes,3,opt"` - } `protobuf:"bytes,2,opt"` - } `protobuf:"bytes,1,opt"` - - Elem2 *struct { - Data *struct { - Friend *struct { - RKey proto.Option[string] `protobuf:"bytes,30,opt"` - } `protobuf:"bytes,11,opt"` - Group *struct { - RKey proto.Option[string] `protobuf:"bytes,30,opt"` - } `protobuf:"bytes,12,opt"` - } `protobuf:"bytes,1,opt"` - } `protobuf:"bytes,2,opt"` +type PbMultiMediaElement_Elem1 struct { + Meta *PbMultiMediaElement_Elem1_Meta `protobuf:"bytes,1,opt"` + Data *PbMultiMediaElement_Elem1_Data `protobuf:"bytes,2,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem2 struct { + Data *PbMultiMediaElement_Elem2_Data `protobuf:"bytes,1,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem1_Meta struct { + Data *PbMultiMediaElement_Elem1_Meta_Data `protobuf:"bytes,1,opt"` + FilePath proto.Option[string] `protobuf:"bytes,2,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem1_Data struct { + ImgURL proto.Option[string] `protobuf:"bytes,1,opt"` + Domain proto.Option[string] `protobuf:"bytes,3,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem1_Meta_Data struct { + FileLen proto.Option[int32] `protobuf:"varint,1,opt"` + FileMd5 []byte `protobuf:"bytes,2,opt"` + FileName proto.Option[string] `protobuf:"bytes,4,opt"` +} + +type PbMultiMediaElement_Elem2_Data struct { + Friend *PbMultiMediaElement_Elem2_Data_Friend `protobuf:"bytes,11,opt"` + Group *PbMultiMediaElement_Elem2_Data_Group `protobuf:"bytes,12,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem2_Data_Friend struct { + RKey proto.Option[string] `protobuf:"bytes,30,opt"` + _ [0]func() +} + +type PbMultiMediaElement_Elem2_Data_Group struct { + RKey proto.Option[string] `protobuf:"bytes,30,opt"` + _ [0]func() } diff --git a/client/pb/msg/msg.proto b/client/pb/msg/msg.proto index a200e89e1..f3bc04183 100644 --- a/client/pb/msg/msg.proto +++ b/client/pb/msg/msg.proto @@ -883,7 +883,8 @@ message PbMultiMediaElement { message Meta { message Data { optional int32 FileLen = 1; - optional bytes PicMd5 = 2; + optional bytes FileMd5 = 2; + optional string FileName = 4; } optional Data data = 1; optional string FilePath = 2; @@ -891,7 +892,7 @@ message PbMultiMediaElement { optional Meta meta = 1; message Data { - optional string ImgURL = 2; + optional string ImgURL = 1; optional string Domain = 3; } optional Data data = 2; diff --git a/client/pb/richmedia/ntv2.pb.go b/client/pb/richmedia/ntv2.pb.go new file mode 100644 index 000000000..7b64913fa --- /dev/null +++ b/client/pb/richmedia/ntv2.pb.go @@ -0,0 +1,94 @@ +// Code generated by protoc-gen-golite. DO NOT EDIT. +// source: pb/richmedia/ntv2.proto + +package richmedia + +type NTV2RichMediaReq struct { + ReqHead *MultiMediaReqHead `protobuf:"bytes,1,opt"` + Download *DownloadReq `protobuf:"bytes,3,opt"` + _ [0]func() +} + +type MultiMediaReqHead struct { + Common *CommonHead `protobuf:"bytes,1,opt"` + Scene *SceneInfo `protobuf:"bytes,2,opt"` + Client *ClientMeta `protobuf:"bytes,3,opt"` + _ [0]func() +} + +type CommonHead struct { + RequestId uint32 `protobuf:"varint,1,opt"` + Command uint32 `protobuf:"varint,2,opt"` + _ [0]func() +} + +type SceneInfo struct { + RequestType uint32 `protobuf:"varint,101,opt"` + BusinessType uint32 `protobuf:"varint,102,opt"` + SceneType uint32 `protobuf:"varint,200,opt"` + C2C *C2CUserInfo `protobuf:"bytes,201,opt"` + Group *NTGroupInfo `protobuf:"bytes,202,opt"` + _ [0]func() +} + +type ClientMeta struct { + AgentType uint32 `protobuf:"varint,1,opt"` + _ [0]func() +} + +type C2CUserInfo struct { + AccountType uint32 `protobuf:"varint,1,opt"` + TargetUid string `protobuf:"bytes,2,opt"` + _ [0]func() +} + +type NTGroupInfo struct { + GroupUin uint32 `protobuf:"varint,1,opt"` + _ [0]func() +} + +type DownloadReq struct { + Node *IndexNode `protobuf:"bytes,1,opt"` + _ [0]func() +} + +type IndexNode struct { + Info *FileInfo `protobuf:"bytes,1,opt"` + FileUuid string `protobuf:"bytes,2,opt"` + StoreId uint32 `protobuf:"varint,3,opt"` + _ [0]func() +} + +type FileInfo struct { + Type *FileType `protobuf:"bytes,5,opt"` + Time uint32 `protobuf:"varint,8,opt"` + _ [0]func() +} + +type FileType struct { + Type uint32 `protobuf:"varint,1,opt"` + VoiceFormat uint32 `protobuf:"varint,4,opt"` + _ [0]func() +} + +type NTV2RichMediaRsp struct { + MediaResp *MediaResp `protobuf:"bytes,4,opt"` + _ [0]func() +} + +type MediaResp struct { + DownloadResp *DownloadResp `protobuf:"bytes,3,opt"` + _ [0]func() +} + +type DownloadResp struct { + Rkey string `protobuf:"bytes,1,opt"` + Info *DownloadInfo `protobuf:"bytes,3,opt"` + _ [0]func() +} + +type DownloadInfo struct { + Domain string `protobuf:"bytes,1,opt"` + UrlPath string `protobuf:"bytes,2,opt"` + _ [0]func() +} diff --git a/client/pb/richmedia/ntv2.proto b/client/pb/richmedia/ntv2.proto new file mode 100644 index 000000000..3fc0906d6 --- /dev/null +++ b/client/pb/richmedia/ntv2.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +option go_package = "github.com/Mrs4s/MiraiGo/client/pb/richmedia"; + +message NTV2RichMediaReq { + MultiMediaReqHead ReqHead = 1; + DownloadReq Download = 3; +} + +message MultiMediaReqHead { + CommonHead Common = 1; + SceneInfo Scene = 2; + ClientMeta Client = 3; +} + +message CommonHead { + uint32 RequestId = 1; + uint32 Command = 2; +} + +message SceneInfo { + uint32 RequestType = 101; + uint32 BusinessType = 102; + uint32 SceneType = 200; + optional C2CUserInfo C2C = 201; + optional NTGroupInfo Group = 202; +} + +message ClientMeta { + uint32 AgentType = 1; +} + +message C2CUserInfo { + uint32 AccountType = 1; + string TargetUid = 2; +} + +message NTGroupInfo { + uint32 GroupUin = 1; +} + +message DownloadReq { + IndexNode Node = 1; +} + +message IndexNode { + FileInfo Info = 1; + string FileUuid = 2; + uint32 StoreId = 3; +} + +message FileInfo { + FileType Type = 5; + uint32 Time = 8; +} + +message FileType { + uint32 Type = 1; + uint32 VoiceFormat = 4; +} + +message NTV2RichMediaRsp { + MediaResp MediaResp = 4; +} + +message MediaResp { + DownloadResp DownloadResp = 3; +} + +message DownloadResp { + string Rkey = 1; + DownloadInfo Info = 3; +} + +message DownloadInfo { + string Domain = 1; + string UrlPath = 2; +} diff --git a/client/richmedia.go b/client/richmedia.go new file mode 100644 index 000000000..1a17e2eec --- /dev/null +++ b/client/richmedia.go @@ -0,0 +1,74 @@ +package client + +import ( + "fmt" + + "github.com/Mrs4s/MiraiGo/client/pb/richmedia" + "github.com/Mrs4s/MiraiGo/internal/proto" +) + +// OidbSvcTrpcTcp.0x126d_200 +func (c *QQClient) buildRecordDownloadReqPacket(Uid string, FileId string, groupUin int64, isGroup bool) (uint16, []byte) { + scene := &richmedia.SceneInfo{ + RequestType: 2, + BusinessType: 3, + SceneType: 1, + C2C: &richmedia.C2CUserInfo{ + AccountType: 2, + TargetUid: Uid, + }, + } + if isGroup { + scene.RequestType = 1 + scene.SceneType = 2 + scene.Group = &richmedia.NTGroupInfo{ + GroupUin: uint32(groupUin), + } + } + body := &richmedia.NTV2RichMediaReq{ + ReqHead: &richmedia.MultiMediaReqHead{ + Common: &richmedia.CommonHead{ + RequestId: 3, + Command: 200, + }, + Scene: scene, + Client: &richmedia.ClientMeta{ + AgentType: 2, + }, + }, + Download: &richmedia.DownloadReq{ + Node: &richmedia.IndexNode{ + Info: &richmedia.FileInfo{ + Type: &richmedia.FileType{ + Type: 3, + VoiceFormat: 1, + }, + Time: 1, + }, + FileUuid: FileId, + StoreId: 1, + }, + }, + } + b, err := proto.Marshal(body) + if err != nil { + fmt.Println(err) + } + payload := c.packOIDBPackage(4717, 200, b) + return c.uniPacket("OidbSvcTrpcTcp.0x126d_200", payload) +} + +func (c *QQClient) ParseRecordDownloadRspPacket(body []byte) string { + rp := &richmedia.NTV2RichMediaRsp{} + if err := proto.Unmarshal(body, rp); err != nil && rp.MediaResp.DownloadResp.Info != nil { + c.error("parse RecordDownloadRspPacket error: %v", err) + return "" + } + return fmt.Sprintf("https://%s%s%s", rp.MediaResp.DownloadResp.Info.Domain, rp.MediaResp.DownloadResp.Info.UrlPath, rp.MediaResp.DownloadResp.Rkey) +} + +// GetRecordDownloadUrl 获取语音文件下载地址 +func (c *QQClient) GetRecordDownloadUrl(selfUid string, FileId string, groupUin int64, isGroup bool) string { + body, _ := c.sendAndWaitDynamic(c.buildRecordDownloadReqPacket(selfUid, FileId, groupUin, isGroup)) + return c.ParseRecordDownloadRspPacket(body) +} diff --git a/message/elements.go b/message/elements.go index c90e6015e..a89e2d874 100644 --- a/message/elements.go +++ b/message/elements.go @@ -14,10 +14,12 @@ type TextElement struct { } type VoiceElement struct { - Name string - Md5 []byte - Size int32 - Url string + Name string + Md5 []byte + Size int32 + Url string + FileId string + IsGroup bool // --- sending --- Data []byte diff --git a/message/image.go b/message/image.go index f4cd2d824..0bcab8fde 100644 --- a/message/image.go +++ b/message/image.go @@ -19,7 +19,7 @@ type GroupImageElement struct { Height int32 Md5 []byte Url string - + Name string // EffectID show pic effect id. EffectID int32 Flash bool @@ -32,8 +32,8 @@ type FriendImageElement struct { Width int32 Height int32 Url string - - Flash bool + Name string + Flash bool } type GuildImageElement struct { diff --git a/message/message.go b/message/message.go index fb983ac80..52c697b9d 100644 --- a/message/message.go +++ b/message/message.go @@ -671,36 +671,53 @@ func ParseMessageElems(elems []*msg.Elem) []IMessageElement { Name: strings.TrimPrefix(string(animatedStickerMsg.Text), "/"), } return []IMessageElement{sticker} // sticker 永远为单独消息 - case 48: + } + bt := elem.CommonElem.BusinessType.Unwrap() + switch bt { + case 10, 20: img := &msg.PbMultiMediaElement{} _ = proto.Unmarshal(elem.CommonElem.PbElem, img) domain := img.Elem1.Data.Domain.Unwrap() imgURL := img.Elem1.Data.ImgURL.Unwrap() - if img.Elem2.Data.Friend != nil { rKey := img.Elem2.Data.Friend.RKey.Unwrap() - url := fmt.Sprintf("https://%s%s%s&spec=0&rf=naio", domain, imgURL, rKey) + url := fmt.Sprintf("https://%s%s%s", domain, imgURL, rKey) res = append(res, &FriendImageElement{ ImageId: img.Elem1.Meta.FilePath.Unwrap(), Size: img.Elem1.Meta.Data.FileLen.Unwrap(), Url: url, - Md5: img.Elem1.Meta.Data.PicMd5, + Md5: img.Elem1.Meta.Data.FileMd5, + Name: img.Elem1.Meta.Data.FileName.Unwrap(), }) newImg = true } if img.Elem2.Data.Group != nil { rKey := img.Elem2.Data.Group.RKey.Unwrap() - url := fmt.Sprintf("https://%s%s%s&spec=0&rf=naio", domain, imgURL, rKey) + url := fmt.Sprintf("https://%s%s%s", domain, imgURL, rKey) res = append(res, &GroupImageElement{ ImageId: img.Elem1.Meta.FilePath.Unwrap(), Size: img.Elem1.Meta.Data.FileLen.Unwrap(), Url: url, - Md5: img.Elem1.Meta.Data.PicMd5, + Md5: img.Elem1.Meta.Data.FileMd5, + Name: img.Elem1.Meta.Data.FileName.Unwrap(), }) newImg = true } + case 12, 22: + audio := &msg.PbMultiMediaElement{} + _ = proto.Unmarshal(elem.CommonElem.PbElem, audio) + ve := &VoiceElement{ + Name: audio.Elem1.Meta.Data.FileName.Unwrap(), + Md5: audio.Elem1.Meta.Data.FileMd5, + Size: audio.Elem1.Meta.Data.FileLen.Unwrap(), + FileId: audio.Elem1.Meta.FilePath.Unwrap(), + IsGroup: false, + } + if bt == 22 { + ve.IsGroup = true + } + res = append(res, ve) } - } } return res