From 68f964f81018523f2d31e6b5369e4ae3342f7e86 Mon Sep 17 00:00:00 2001 From: Fdvkey Date: Wed, 15 Jul 2026 18:02:47 +0700 Subject: [PATCH] feat: implement pure-go RIFF EXIF injector --- helper/exif.go | 185 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 65 deletions(-) diff --git a/helper/exif.go b/helper/exif.go index 164ea10..54d7636 100644 --- a/helper/exif.go +++ b/helper/exif.go @@ -1,15 +1,11 @@ package helper import ( + "bytes" + "encoding/binary" "encoding/json" "fmt" - "log" "math/big" - "os" - "path/filepath" - "strings" - - "github.com/google/uuid" ) type StickerMetadata struct { @@ -19,7 +15,6 @@ type StickerMetadata struct { Emojis []string `json:"emojis,omitempty"` } -// Added matching writeUIntLE from F-BOT_GO reference func writeUIntLE(buffer []byte, value, offset, byteLength int64) { slice := make([]byte, byteLength) val := new(big.Int) @@ -36,7 +31,7 @@ func writeUIntLE(buffer []byte, value, offset, byteLength int64) { func createMetadataBytes(packName, publisher string) []byte { exifObj := StickerMetadata{ - PackId: "com.wa.bot.sticker", // or generic ID + PackId: "com.wa.bot.sticker", Name: packName, Publisher: publisher, Emojis: []string{"😀"}, @@ -44,83 +39,143 @@ func createMetadataBytes(packName, publisher string) []byte { exifJson, err := json.Marshal(exifObj) if err != nil { - log.Printf("Failed to marshal EXIF: %v", err) return nil } - // WebP EXIF JSON string representation inside the file bit := []byte{0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00} bit = append(bit, exifJson...) - - // Crucial fix: Inject JSON string length at offset 14 (4 bytes) writeUIntLE(bit, int64(len(exifJson)), 14, 4) return bit } -func getOrCreateDefaultExif() string { - defaultExifPath := "temp/default.exif" - - if err := os.MkdirAll("temp", 0755); err != nil { - log.Printf("Failed to create temp directory: %v", err) - return "" +// InjectExif takes raw WebP bytes and constructs a new WebP RIFF byte slice containing the EXIF metadata +func InjectExif(webpData []byte, packName, publisher string) ([]byte, error) { + if len(webpData) < 12 || string(webpData[0:4]) != "RIFF" || string(webpData[8:12]) != "WEBP" { + return nil, fmt.Errorf("not a valid WEBP file") } - // ALWAYS recreate temp/default.exif just in case previous boots were corrupted - // This corrects broken instances locally instantly - packName := os.Getenv("STICKER_NAME") - if packName == "" { - packName = "Bot" + var width, height uint32 + hasVP8X := false + var vp8xFlags byte + var vp8xBytes []byte + + var chunks [][]byte + + // Default defaults for VP8X canvas sizes + width = 512 + height = 512 + + offset := 12 + for offset < len(webpData) { + if offset+8 > len(webpData) { + break + } + chunkID := string(webpData[offset : offset+4]) + chunkSize := binary.LittleEndian.Uint32(webpData[offset+4 : offset+8]) + + paddedSize := chunkSize + if paddedSize%2 != 0 { + paddedSize++ + } + + if offset+8+int(paddedSize) > len(webpData) { + break + } + + chunkData := webpData[offset+8 : offset+8+int(paddedSize)] + + switch chunkID { + case "VP8X": + hasVP8X = true + vp8xFlags = chunkData[0] + vp8xBytes = chunkData + // extract dimensions + wBytes := []byte{chunkData[4], chunkData[5], chunkData[6], 0} + hBytes := []byte{chunkData[7], chunkData[8], chunkData[9], 0} + width = binary.LittleEndian.Uint32(wBytes) + 1 + height = binary.LittleEndian.Uint32(hBytes) + 1 + case "VP8 ": + if !hasVP8X { + // extract 14 bit scale block + w := uint32(chunkData[6]) | (uint32(chunkData[7]) << 8) + width = w & 0x3FFF + h := uint32(chunkData[8]) | (uint32(chunkData[9]) << 8) + height = h & 0x3FFF + } + chunks = append(chunks, webpData[offset:offset+8+int(paddedSize)]) + case "VP8L": + if !hasVP8X { + // Parse bits 1-28 for w/h + b0 := uint32(chunkData[1]) + b1 := uint32(chunkData[2]) + b2 := uint32(chunkData[3]) + b3 := uint32(chunkData[4]) + width = 1 + (((b1 & 0x3F) << 8) | b0) + height = 1 + (((b3 & 0xF) << 10) | (b2 << 2) | ((b1 & 0xC0) >> 6)) + } + chunks = append(chunks, webpData[offset:offset+8+int(paddedSize)]) + default: + if chunkID != "EXIF" { + chunks = append(chunks, webpData[offset:offset+8+int(paddedSize)]) + } + } + offset += 8 + int(paddedSize) } - publisher := os.Getenv("STICKER_PUBLISHER") - if publisher == "" { - publisher = "Bot" + exifMeta := createMetadataBytes(packName, publisher) + if exifMeta == nil { + return nil, fmt.Errorf("failed to create exif byte payload") } - exifContent := createMetadataBytes(packName, publisher) - if exifContent == nil { - return "" + // Turn on EXIF flag (bit 3) in VP8X + if !hasVP8X { + vp8xBytes = make([]byte, 10) + vp8xFlags = 0x08 // EXIF flag is bit 3 + vp8xBytes[0] = vp8xFlags + w := width - 1 + h := height - 1 + vp8xBytes[4] = byte(w) + vp8xBytes[5] = byte(w >> 8) + vp8xBytes[6] = byte(w >> 16) + vp8xBytes[7] = byte(h) + vp8xBytes[8] = byte(h >> 8) + vp8xBytes[9] = byte(h >> 16) + } else { + vp8xBytes[0] = vp8xFlags | 0x08 } - if err := os.WriteFile(defaultExifPath, exifContent, 0644); err != nil { - log.Printf("Failed to write default EXIF: %v", err) - return "" + var buf bytes.Buffer + buf.WriteString("RIFF") + buf.Write([]byte{0, 0, 0, 0}) // Total size placeholder + buf.WriteString("WEBP") + + // Write VP8X + buf.WriteString("VP8X") + vp8xSize := make([]byte, 4) + binary.LittleEndian.PutUint32(vp8xSize, 10) + buf.Write(vp8xSize) + buf.Write(vp8xBytes) + + // Write existing chunks + for _, chunk := range chunks { + buf.Write(chunk) } - return defaultExifPath -} - -func ResolveExif(args []string) (string, bool) { - if len(args) == 0 { - return getOrCreateDefaultExif(), false + // Write EXIF + buf.WriteString("EXIF") + exifSize := uint32(len(exifMeta)) + exifSizeSlice := make([]byte, 4) + binary.LittleEndian.PutUint32(exifSizeSlice, exifSize) + buf.Write(exifSizeSlice) + buf.Write(exifMeta) + if exifSize%2 != 0 { + buf.WriteByte(0) } - argStr := strings.Join(args, " ") - parts := strings.Split(argStr, "|") + result := buf.Bytes() + totalSize := uint32(len(result) - 8) + binary.LittleEndian.PutUint32(result[4:8], totalSize) - packName := strings.TrimSpace(parts[0]) - publisher := "Bot" - if len(parts) > 1 { - publisher = strings.TrimSpace(parts[1]) - } - - if packName == "" && publisher == "Bot" { - return getOrCreateDefaultExif(), false - } - - exifContent := createMetadataBytes(packName, publisher) - if exifContent == nil { - return getOrCreateDefaultExif(), false - } - - id := uuid.New().String() - tempPath := filepath.Join("temp", fmt.Sprintf("%s.exif", id)) - - if err := os.WriteFile(tempPath, exifContent, 0644); err != nil { - log.Printf("Failed to write dynamic EXIF: %v", err) - return getOrCreateDefaultExif(), false - } - - return tempPath, true -} + return result, nil +} \ No newline at end of file