github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/client/upload_file.go (about) 1 package client 2 3 import ( 4 "crypto/md5" 5 "crypto/sha1" 6 "fmt" 7 "io" 8 "math/rand" 9 "time" 10 11 "github.com/pkg/errors" 12 13 "github.com/Mrs4s/MiraiGo/client/internal/highway" 14 "github.com/Mrs4s/MiraiGo/client/internal/network" 15 "github.com/Mrs4s/MiraiGo/client/pb/cmd0x346" 16 "github.com/Mrs4s/MiraiGo/client/pb/exciting" 17 "github.com/Mrs4s/MiraiGo/client/pb/msg" 18 "github.com/Mrs4s/MiraiGo/internal/proto" 19 "github.com/Mrs4s/MiraiGo/message" 20 "github.com/Mrs4s/MiraiGo/utils" 21 ) 22 23 func init() { 24 decoders["OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_UPLOAD_V3-1700"] = decodePrivateFileUploadReq 25 } 26 27 type LocalFile struct { 28 FileName string 29 Body io.ReadSeeker // LocalFile content body 30 RemoteFolder string 31 size int64 32 md5 []byte 33 sha1 []byte 34 } 35 36 type fileUploadRsp struct { 37 Existed bool 38 BusID int32 39 Uuid []byte 40 UploadKey []byte 41 42 // upload group file need 43 UploadIpLanV4 []string 44 UploadPort int32 45 } 46 47 func (f *LocalFile) init() { 48 md5H := md5.New() 49 sha1H := sha1.New() 50 51 whence, _ := f.Body.Seek(0, io.SeekCurrent) 52 f.size, _ = io.Copy(io.MultiWriter(md5H, sha1H), f.Body) 53 _, _ = f.Body.Seek(whence, io.SeekStart) // restore 54 55 // calculate md5&sha1 hash 56 f.md5 = md5H.Sum(nil) 57 f.sha1 = sha1H.Sum(nil) 58 } 59 60 var fsWaiter = utils.NewUploadWaiter() 61 62 func (c *QQClient) UploadFile(target message.Source, file *LocalFile) error { 63 switch target.SourceType { 64 case message.SourceGroup, message.SourcePrivate: // ok 65 default: 66 return errors.New("not implemented") 67 } 68 69 file.init() 70 // 同文件等待其他线程上传 71 fkey := string(file.sha1) 72 fsWaiter.Wait(fkey) 73 defer fsWaiter.Done(fkey) 74 75 var seq uint16 76 var pkt []byte 77 if target.SourceType == message.SourcePrivate { 78 seq, pkt = c.buildPrivateFileUploadReqPacket(target, file) 79 } else { 80 seq, pkt = c.buildGroupFileUploadReqPacket(target.PrimaryID, file) 81 } 82 i, err := c.sendAndWait(seq, pkt) 83 if err != nil { 84 return errors.Wrap(err, "query upload failed") 85 } 86 rsp := i.(*fileUploadRsp) 87 88 if !rsp.Existed { 89 ext := &exciting.FileUploadExt{ 90 Unknown1: proto.Int32(100), 91 Unknown2: proto.Int32(2), 92 Entry: &exciting.FileUploadEntry{ 93 BusiBuff: &exciting.ExcitingBusiInfo{ 94 BusId: proto.Int32(rsp.BusID), 95 SenderUin: proto.Some(c.Uin), 96 ReceiverUin: proto.Some(target.PrimaryID), 97 GroupCode: proto.Int64(0), 98 }, 99 FileEntry: &exciting.ExcitingFileEntry{ 100 FileSize: proto.Some(file.size), 101 Md5: file.md5, 102 Sha1: file.sha1, 103 FileId: rsp.Uuid, 104 UploadKey: rsp.UploadKey, 105 }, 106 ClientInfo: &exciting.ExcitingClientInfo{ 107 ClientType: proto.Int32(2), 108 AppId: proto.String(fmt.Sprint(c.version().AppId)), 109 TerminalType: proto.Int32(2), 110 ClientVer: proto.String("d92615c5"), 111 Unknown: proto.Int32(4), 112 }, 113 FileNameInfo: &exciting.ExcitingFileNameInfo{ 114 FileName: proto.Some(file.FileName), 115 }, 116 }, 117 Unknown200: proto.Int32(1), 118 } 119 if target.SourceType == message.SourceGroup { 120 if len(rsp.UploadIpLanV4) == 0 { 121 return errors.New("server requires unsupported ftn upload") 122 } 123 ext.Unknown3 = proto.Int32(0) 124 ext.Unknown200 = proto.None[int32]() 125 ext.Entry.BusiBuff.GroupCode = proto.Int64(target.PrimaryID) 126 ext.Entry.Host = &exciting.ExcitingHostConfig{ 127 Hosts: []*exciting.ExcitingHostInfo{ 128 { 129 Url: &exciting.ExcitingUrlInfo{ 130 Unknown: proto.Int32(1), 131 Host: proto.Some(rsp.UploadIpLanV4[0]), 132 }, 133 Port: proto.Some(rsp.UploadPort), 134 }, 135 }, 136 } 137 } 138 extPkt, _ := proto.Marshal(ext) 139 input := highway.Transaction{ 140 CommandID: 71, 141 Body: file.Body, 142 Size: file.size, 143 Sum: file.md5, 144 Ticket: c.highwaySession.SigSession, 145 Ext: extPkt, 146 } 147 if target.SourceType == message.SourcePrivate { 148 input.CommandID = 69 149 } 150 if _, err := c.highwaySession.Upload(input); err != nil { 151 return errors.Wrap(err, "upload failed") 152 } 153 } 154 if target.SourceType == message.SourceGroup { 155 _, pkt := c.buildGroupFileFeedsRequest(target.PrimaryID, string(rsp.Uuid), rsp.BusID, rand.Int31()) 156 return c.sendPacket(pkt) 157 } 158 // 私聊文件 159 _, pkt = c.buildPrivateFileUploadSuccReq(target, rsp) 160 err = c.sendPacket(pkt) 161 if err != nil { 162 return err 163 } 164 uid := target.PrimaryID 165 msgSeq := c.nextFriendSeq() 166 content, _ := proto.Marshal(&msg.SubMsgType0X4Body{ 167 NotOnlineFile: &msg.NotOnlineFile{ 168 FileType: proto.Int32(0), 169 FileUuid: rsp.Uuid, 170 FileMd5: file.md5, 171 FileName: []byte(file.FileName), 172 FileSize: proto.Int64(file.size), 173 Subcmd: proto.Int32(1), 174 }, 175 }) 176 req := &msg.SendMessageRequest{ 177 RoutingHead: &msg.RoutingHead{ 178 Trans_0X211: &msg.Trans0X211{ 179 ToUin: proto.Uint64(uint64(uid)), 180 CcCmd: proto.Uint32(4), 181 }, 182 }, 183 ContentHead: &msg.ContentHead{ 184 PkgNum: proto.Int32(1), 185 PkgIndex: proto.Int32(0), 186 DivSeq: proto.Int32(0), 187 }, 188 MsgBody: &msg.MessageBody{ 189 MsgContent: content, 190 }, 191 MsgSeq: proto.Some(msgSeq), 192 MsgRand: proto.Some(int32(rand.Uint32())), 193 SyncCookie: syncCookie(time.Now().Unix()), 194 } 195 payload, _ := proto.Marshal(req) 196 _, p := c.uniPacket("MessageSvc.PbSendMsg", payload) 197 return c.sendPacket(p) 198 } 199 200 func (c *QQClient) buildPrivateFileUploadReqPacket(target message.Source, file *LocalFile) (uint16, []byte) { 201 req := cmd0x346.C346ReqBody{ 202 Cmd: 1700, 203 Seq: c.nextFriendSeq(), 204 ApplyUploadReqV3: &cmd0x346.ApplyUploadReqV3{ 205 SenderUin: c.Uin, 206 RecverUin: target.PrimaryID, 207 FileSize: file.size, 208 FileName: file.FileName, 209 Bytes_10MMd5: file.md5, // TODO: investigate this 210 Sha: file.sha1, 211 LocalFilepath: "/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/" + file.FileName, 212 Md5: file.md5, 213 }, 214 BusinessId: 3, 215 ClientType: 104, 216 FlagSupportMediaplatform: 1, 217 } 218 pkg, _ := proto.Marshal(&req) 219 return c.uniPacket("OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_UPLOAD_V3-1700", pkg) 220 } 221 222 // OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_UPLOAD_V3-1700 223 func decodePrivateFileUploadReq(_ *QQClient, pkt *network.Packet) (any, error) { 224 var rsp cmd0x346.C346RspBody 225 err := proto.Unmarshal(pkt.Payload, &rsp) 226 if err != nil { 227 return nil, err 228 } 229 v3 := rsp.ApplyUploadRspV3 230 r := &fileUploadRsp{ 231 Existed: v3.BoolFileExist, 232 BusID: 3, 233 Uuid: v3.Uuid, 234 UploadKey: v3.MediaPlateformUploadKey, 235 } 236 return r, nil 237 } 238 239 func (c *QQClient) buildPrivateFileUploadSuccReq(target message.Source, rsp *fileUploadRsp) (uint16, []byte) { 240 req := &cmd0x346.C346ReqBody{ 241 Cmd: 800, 242 Seq: 7, 243 UploadSuccReq: &cmd0x346.UploadSuccReq{ 244 SenderUin: c.Uin, 245 RecverUin: target.PrimaryID, 246 Uuid: rsp.Uuid, 247 }, 248 BusinessId: 3, 249 ClientType: 104, 250 } 251 pkt, _ := proto.Marshal(req) 252 return c.uniPacket("OfflineFilleHandleSvr.pb_ftn_CMD_REQ_UPLOAD_SUCC-800", pkt) 253 }