diff --git a/packets/oidb/GroupImageUpload.go b/packets/oidb/GroupImageUpload.go new file mode 100644 index 00000000..7771a663 --- /dev/null +++ b/packets/oidb/GroupImageUpload.go @@ -0,0 +1,108 @@ +package oidb + +import ( + "encoding/hex" + "errors" + "fmt" + "github.com/LagrangeDev/LagrangeGo/packets/pb/service/oidb" + "github.com/LagrangeDev/LagrangeGo/utils" + "io" + "math/rand" +) + +func BuildGroupImageUploadReq(groupUin uint32, stream io.Reader) (*OidbPacket, error) { + // OidbSvcTrpcTcp.0x11c4_100 + data, err := io.ReadAll(stream) + if err != nil { + return nil, err + } + md5Hash := utils.Md5Digest(data) + sha1Hash := utils.Sha1Digest(data) + format, size, err := utils.ImageResolve(data) + if err != nil { + return nil, err + } + imageExt := utils.GetImageExt(format) + + hexString := "0800180020004a00500062009201009a0100aa010c080012001800200028003a00" + bytesPbReserveTroop, err := hex.DecodeString(hexString) + if err != nil { + return nil, err + } + + body := &oidb.NTV2RichMediaReq{ + ReqHead: &oidb.MultiMediaReqHead{ + Common: &oidb.CommonHead{ + RequestId: 1, + Command: 100, + }, + Scene: &oidb.SceneInfo{ + RequestType: 2, + BusinessType: 1, + SceneType: 2, + Group: &oidb.NTGroupInfo{ + GroupUin: groupUin, + }, + }, + Client: &oidb.ClientMeta{ + AgentType: 2, + }, + }, + Upload: &oidb.UploadReq{ + UploadInfo: []*oidb.UploadInfo{ + { + FileInfo: &oidb.FileInfo{ + FileSize: uint32(len(data)), + FileHash: fmt.Sprintf("%x", md5Hash), + FileSha1: fmt.Sprintf("%x", sha1Hash), + FileName: fmt.Sprintf("%x.%s", md5Hash, imageExt), + Type: &oidb.FileType{ + Type: 1, + PicFormat: uint32(format), + VideoFormat: 0, + VoiceFormat: 0, + }, + Width: size.X, + Height: size.Y, + Time: 0, + Original: 1, + }, + SubFileType: 0, + }, + }, + TryFastUploadCompleted: true, + SrvSendMsg: false, + ClientRandomId: uint64(rand.Int63()), + CompatQMsgSceneType: 2, + ExtBizInfo: &oidb.ExtBizInfo{ + Pic: &oidb.PicExtBizInfo{ + BytesPbReserveTroop: bytesPbReserveTroop, + }, + Video: &oidb.VideoExtBizInfo{ + BytesPbReserve: []byte{}, + }, + Ptt: &oidb.PttExtBizInfo{ + BytesReserve: []byte{}, + BytesPbReserve: []byte{}, + BytesGeneralFlags: []byte{}, + }, + }, + ClientSeq: 0, + NoNeedCompatMsg: false, + }, + } + return BuildOidbPacket(0x11c4, 100, body, false, true) +} + +func ParseGroupImageUploadResp(data []byte) (*oidb.NTV2RichMediaResp, error) { // TODO: return proper response + var resp oidb.NTV2RichMediaResp + baseResp, err := ParseOidbPacket(data, &resp) + if err != nil { + return nil, err + } + if baseResp.ErrorCode != 0 { + return nil, errors.New(baseResp.ErrorMsg) + } + + return &resp, nil +} diff --git a/utils/hash.go b/utils/hash.go index 1c7f3f29..4b3342b9 100644 --- a/utils/hash.go +++ b/utils/hash.go @@ -2,6 +2,7 @@ package utils import ( "crypto/md5" + "crypto/sha1" "crypto/sha256" ) @@ -16,3 +17,9 @@ func Sha256Digest(v []byte) []byte { sha256er.Write(v) return sha256er.Sum(nil) } + +func Sha1Digest(v []byte) []byte { + sha1er := sha1.New() + sha1er.Write(v) + return sha1er.Sum(nil) +} diff --git a/utils/image_resolver.go b/utils/image_resolver.go new file mode 100644 index 00000000..bba522d6 --- /dev/null +++ b/utils/image_resolver.go @@ -0,0 +1,95 @@ +package utils + +import ( + "bytes" + "encoding/binary" + "errors" +) + +// Vector2 二维向量 +type Vector2 struct { + X uint32 + Y uint32 +} + +type ImageFormat uint32 + +const ( + Unknown ImageFormat = iota + Jpeg ImageFormat = 1000 + Png ImageFormat = 1001 + Gif ImageFormat = 2000 + Webp ImageFormat = 1002 + Bmp ImageFormat = 1005 + Tiff ImageFormat = 1006 +) + +func GetImageExt(format ImageFormat) string { + switch format { + case Jpeg: + return "jpg" + case Png: + return "png" + case Gif: + return "gif" + case Webp: + return "webp" + case Bmp: + return "bmp" + case Tiff: + return "tiff" + default: + return "unknown" + } +} + +func ImageResolve(image []byte) (ImageFormat, Vector2, error) { + if len(image) < 10 { // 最小长度检查 + return Unknown, Vector2{}, errors.New("image data is too short") + } + + size := Vector2{} + var format ImageFormat + + switch { + case bytes.Equal(image[:6], []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61}) || bytes.Equal(image[:6], []byte{0x47, 0x49, 0x46, 0x38, 0x37, 0x61}): // GIF + size = Vector2{X: uint32(binary.LittleEndian.Uint16(image[6:8])), Y: uint32(binary.LittleEndian.Uint16(image[8:10]))} + format = Gif + + case bytes.Equal(image[:2], []byte{0xFF, 0xD8}): // JPEG + for i := 2; i < len(image)-10; i++ { + if binary.LittleEndian.Uint16(image[i:i+2])&0xFCFF == 0xC0FF { // SOF0 ~ SOF3 + size = Vector2{X: uint32(binary.BigEndian.Uint16(image[i+7 : i+9])), Y: uint32(binary.BigEndian.Uint16(image[i+5 : i+7]))} + break + } + } + format = Jpeg + + case bytes.Equal(image[:8], []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}): // PNG + size = Vector2{X: binary.BigEndian.Uint32(image[16:20]), Y: binary.BigEndian.Uint32(image[20:24])} + format = Png + + case bytes.Equal(image[:4], []byte{0x52, 0x49, 0x46, 0x46}) && bytes.Equal(image[8:12], []byte{0x57, 0x45, 0x42, 0x50}): // WEBP + if bytes.Equal(image[12:16], []byte{0x56, 0x50, 0x38, 0x58}) { // VP8X + size = Vector2{X: uint32(binary.LittleEndian.Uint16(image[24:27]) + 1), Y: uint32(binary.LittleEndian.Uint16(image[27:30]) + 1)} + } else if bytes.Equal(image[12:16], []byte{0x56, 0x50, 0x38, 0x4C}) { // VP8L + size = Vector2{X: uint32(int32(binary.LittleEndian.Uint32(image[21:25]))&0x3FFF) + 1, Y: uint32(int32(binary.LittleEndian.Uint32(image[20:22])&0xFFFC000)>>0x0E) + 1} + } else { + size = Vector2{X: uint32(binary.LittleEndian.Uint16(image[26:28])), Y: uint32(binary.LittleEndian.Uint16(image[28:30]))} + } + format = Webp + + case bytes.Equal(image[:2], []byte{0x42, 0x4D}): // BMP + size = Vector2{X: binary.LittleEndian.Uint32(image[18:22]), Y: binary.LittleEndian.Uint32(image[22:26])} + format = Bmp + + case bytes.Equal(image[:2], []byte{0x49, 0x49}) || bytes.Equal(image[:2], []byte{0x4D, 0x4D}): // TIFF + size = Vector2{X: uint32(binary.LittleEndian.Uint16(image[18:20])), Y: uint32(binary.LittleEndian.Uint16(image[30:32]))} + format = Tiff + + default: + return Unknown, Vector2{}, nil + } + + return format, size, nil +}