diff --git a/client/highway.go b/client/highway.go index ed5130f4..b0c77a6b 100644 --- a/client/highway.go +++ b/client/highway.go @@ -2,6 +2,9 @@ package client import ( "bytes" + binary2 "encoding/binary" + "fmt" + highway2 "github.com/LagrangeDev/LagrangeGo/packets/highway" "github.com/LagrangeDev/LagrangeGo/packets/pb/service/highway" "github.com/LagrangeDev/LagrangeGo/utils" "github.com/LagrangeDev/LagrangeGo/utils/binary" @@ -10,8 +13,12 @@ import ( "net/http" "net/url" "strconv" + "sync/atomic" ) +var highwayUri map[uint32][]string +var sequence atomic.Uint32 + type UpBlock struct { CommandId int Uin uint @@ -25,6 +32,94 @@ type UpBlock struct { Timestamp uint64 } +func (c *QQClient) GetServiceServer() map[uint32][]string { + if highwayUri == nil { + highwayUri = make(map[uint32][]string) + packet, err := highway2.BuildHighWayUrlReq(c.sig.Tgt) + if err != nil { + return nil + } + payload, err := c.SendUniPacketAndAwait("HttpConn.0x6ff_501", packet.Data()) + if err != nil { + networkLogger.Errorf("Failed to get highway server: %v", err) + return nil + } + resp, err := highway2.ParseHighWayUrlReq(payload.Data) + if err != nil { + networkLogger.Errorf("Failed to parse highway server: %v", err) + return nil + } + for _, info := range resp.HttpConn.ServerInfos { + servicetype := info.ServiceType + for _, addr := range info.ServerAddrs { + ip := make([]byte, 4) + binary2.LittleEndian.PutUint32(ip, addr.IP) + service := highwayUri[servicetype] + service = append(service, fmt.Sprintf("http://%x.%x.%x.%x:%d/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%d", ip[0], ip[1], ip[2], ip[3], addr.Port, c.sig.Uin)) + highwayUri[servicetype] = service + } + } + } + return highwayUri +} + +func (c *QQClient) UploadSrcByStreamAsync(commonId int, stream io.ReadSeeker, ticket []byte, md5 []byte, extendInfo []byte) bool { + // Get server URL + server := c.GetServiceServer() + if server == nil { + return false + } + success := true + var upBlocks []UpBlock + data, err := io.ReadAll(stream) + if err != nil { + return false + } + + fileSize := uint64(len(data)) + offset := uint64(0) + _, err = stream.Seek(0, io.SeekStart) + if err != nil { + return false + } + + for offset < fileSize { + var buffersize uint64 + if uint64(1024*1024) > fileSize-offset { + buffersize = fileSize - offset + } else { + buffersize = uint64(1024 * 1024) + } + buffer := make([]byte, buffersize) + payload, err := io.ReadFull(stream, buffer) + if err != nil { + return false + } + reqBody := UpBlock{ + CommandId: commonId, + Uin: uint(c.sig.Uin), + Sequence: uint(sequence.Load()), + FileSize: fileSize, + Offset: offset, + Ticket: ticket, + FileMd5: md5, + Block: buffer, + ExtendInfo: extendInfo, + } + sequence.Add(1) + upBlocks = append(upBlocks, reqBody) + offset += uint64(payload) + // 4 is HighwayConcurrent + if len(upBlocks) >= 4 || offset == fileSize { + for _, block := range upBlocks { + success = success && c.SendUpBlockAsync(block, server[1][0]) + } + upBlocks = nil + } + } + return success +} + func (c *QQClient) SendUpBlockAsync(block UpBlock, server string) bool { head := &highway.DataHighwayHead{ Version: 1, diff --git a/packets/highway/HighWayUrl.go b/packets/highway/HighWayUrl.go new file mode 100644 index 00000000..3fd2f540 --- /dev/null +++ b/packets/highway/HighWayUrl.go @@ -0,0 +1,43 @@ +package highway + +import ( + "encoding/hex" + "github.com/LagrangeDev/LagrangeGo/packets/pb/action" + "github.com/LagrangeDev/LagrangeGo/utils/binary" + "github.com/RomiChan/protobuf/proto" +) + +func BuildHighWayUrlReq(tgt []byte) (*binary.Builder, error) { + tgtHex := hex.EncodeToString(tgt) + body := &action.HttpConn0X6Ff_501{ + HttpConn: &action.HttpConn{ + Field1: 0, + Field2: 0, + Field3: 16, + Field4: 1, + Tgt: tgtHex, + Field6: 3, + ServiceTypes: []int32{1, 5, 10, 21}, + Field9: 2, + Field10: 9, + Field11: 8, + Ver: "1.0.1", + }, + } + packet := binary.NewBuilder(nil) + marshal, err := proto.Marshal(body) + if err != nil { + return nil, err + } + packet.WriteBytes(marshal, false) + return packet, nil +} + +func ParseHighWayUrlReq(data []byte) (*action.HttpConn0X6Ff_501Response, error) { + var req action.HttpConn0X6Ff_501Response + err := proto.Unmarshal(data, &req) + if err != nil { + return nil, err + } + return &req, nil +}