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  }