fixed stickers and emojies creation packs
This commit is contained in:
parent
43894f2fc9
commit
139967399d
16 changed files with 771 additions and 22 deletions
185
internal/app/files/sticker_admin.go
Normal file
185
internal/app/files/sticker_admin.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// ValidateStickerMaterialUpload is a pure check (no store writes), used by a
|
||||
// dry-run preview before AdminUploadStickerMaterial actually materializes the
|
||||
// document into a blob + row.
|
||||
func (s *Service) ValidateStickerMaterialUpload(fileName string, data []byte) (string, bool) {
|
||||
if len(data) == 0 || int64(len(data)) > domain.MaxStickerMaterialDocumentSize {
|
||||
return "", false
|
||||
}
|
||||
return detectStickerMaterialUploadMime(fileName, data)
|
||||
}
|
||||
|
||||
// AdminUploadStickerMaterial turns a raw uploaded file (TGS, plain Lottie
|
||||
// JSON, or static WebP) into a loose Document not yet attached to any set,
|
||||
// the same shape stickers.createStickerSet/addStickerToSet expect as input.
|
||||
// Regular users get this document via messages.uploadMedia before referencing
|
||||
// it; the admin console has no such upload step, so this does the equivalent
|
||||
// materialization directly from the uploaded bytes.
|
||||
func (s *Service) AdminUploadStickerMaterial(ctx context.Context, fileName string, data []byte) (domain.Document, error) {
|
||||
if len(data) == 0 || int64(len(data)) > domain.MaxStickerMaterialDocumentSize {
|
||||
return domain.Document{}, domain.ErrStickerSetFileInvalid
|
||||
}
|
||||
mimeType, ok := detectStickerMaterialUploadMime(fileName, data)
|
||||
if !ok {
|
||||
return domain.Document{}, domain.ErrStickerSetFileInvalid
|
||||
}
|
||||
objectKey, err := s.blobs.Put(ctx, data)
|
||||
if err != nil {
|
||||
return domain.Document{}, err
|
||||
}
|
||||
sum := sha256.Sum256(data)
|
||||
docID := randomID()
|
||||
if err := s.media.PutFileBlob(ctx, domain.FileBlob{
|
||||
LocationKey: fmt.Sprintf("doc:%d", docID),
|
||||
Backend: domain.MediaBackend(s.blobs.Name()),
|
||||
ObjectKey: objectKey,
|
||||
Size: int64(len(data)),
|
||||
SHA256: append([]byte(nil), sum[:]...),
|
||||
MimeType: mimeType,
|
||||
}); err != nil {
|
||||
return domain.Document{}, err
|
||||
}
|
||||
doc := domain.Document{
|
||||
ID: docID,
|
||||
AccessHash: randomID(),
|
||||
FileReference: randomFileReference(),
|
||||
Date: int(time.Now().Unix()),
|
||||
MimeType: mimeType,
|
||||
Size: int64(len(data)),
|
||||
DCID: s.dc,
|
||||
Attributes: []domain.DocumentAttribute{
|
||||
{Kind: domain.DocAttrFilename, FileName: strings.TrimSpace(fileName)},
|
||||
},
|
||||
}
|
||||
if err := s.media.PutDocument(ctx, doc); err != nil {
|
||||
return domain.Document{}, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// detectStickerMaterialUploadMime accepts exactly the raw upload shapes
|
||||
// ensureStickerMaterialShape (sticker_creator.go) already knows how to turn
|
||||
// into a real sticker document: gzip'd TGS, plain Lottie JSON (gzip'd on
|
||||
// attach), or a static WebP image. Anything else (raster PNG, raw video,
|
||||
// unrecognized) is rejected rather than guessed at.
|
||||
func detectStickerMaterialUploadMime(fileName string, data []byte) (string, bool) {
|
||||
ext := strings.ToLower(filepath.Ext(strings.TrimSpace(fileName)))
|
||||
webp := isWebPData(data)
|
||||
switch {
|
||||
case ext == ".tgs" || (len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b):
|
||||
return stickerMaterialMimeTGS, validTGSStickerData(data)
|
||||
case ext == ".webp" || webp:
|
||||
return stickerMaterialMimeWebP, webp
|
||||
case ext == ".json":
|
||||
_, _, ok := lottieStickerDimensions(normalizeLottieStickerJSON(data))
|
||||
return stickerMaterialMimeJSON, ok
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func isWebPData(data []byte) bool {
|
||||
return len(data) >= 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WEBP"
|
||||
}
|
||||
|
||||
// AdminAddStickerToSet appends an already-materialized document (from
|
||||
// AdminUploadStickerMaterial) to an existing pack with no ownership check —
|
||||
// same convention as AdminSetStickerSetArchived and friends.
|
||||
func (s *Service) AdminAddStickerToSet(ctx context.Context, setID int64, item domain.StickerSetItemInput) (domain.StickerSet, []domain.Document, error) {
|
||||
set, docs, found, err := s.ResolveStickerSet(ctx, domain.StickerSetRef{Kind: domain.StickerSetRefByID, ID: setID})
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
if !found || set.ID == 0 || set.Deleted {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetInvalid
|
||||
}
|
||||
if len(set.DocumentIDs) >= domain.MaxStickerSetItems {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetTooMuch
|
||||
}
|
||||
doc, err := s.loadStickerMaterialDocument(ctx, item.DocumentID, item.DocumentAccessHash)
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
doc, err = s.materialDocumentForStickerSet(ctx, doc, set.ID)
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
if containsInt64(set.DocumentIDs, doc.ID) {
|
||||
return set, docs, nil
|
||||
}
|
||||
emoji := strings.TrimSpace(item.Emoji)
|
||||
if err := validateStickerEmoji(emoji); err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
doc, err = s.prepareStickerSetDocument(ctx, doc, set, emoji)
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
set.DocumentIDs = append(set.DocumentIDs, doc.ID)
|
||||
set.Count = len(set.DocumentIDs)
|
||||
set.Packs = addDocumentToStickerPacks(set.Packs, emoji, doc.ID)
|
||||
set.Keywords = upsertStickerKeywords(set.Keywords, parseStickerKeywords(doc.ID, item.Keywords))
|
||||
if set.ThumbDocumentID == 0 {
|
||||
setStickerSetThumbFromDocument(&set, doc)
|
||||
}
|
||||
set.Hash = stickerSetHash(set)
|
||||
docs = append(docs, doc)
|
||||
return s.persistStickerSetMutation(ctx, set, docs, []domain.Document{doc})
|
||||
}
|
||||
|
||||
// AdminRemoveStickerFromSet detaches one document from a pack with no
|
||||
// ownership check; see AdminAddStickerToSet for why that's needed here.
|
||||
func (s *Service) AdminRemoveStickerFromSet(ctx context.Context, setID int64, documentID int64) (domain.StickerSet, []domain.Document, error) {
|
||||
set, docs, found, err := s.ResolveStickerSet(ctx, domain.StickerSetRef{Kind: domain.StickerSetRefByID, ID: setID})
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
}
|
||||
if !found || set.ID == 0 || set.Deleted {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetInvalid
|
||||
}
|
||||
if len(set.DocumentIDs) <= 1 {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetEmpty
|
||||
}
|
||||
idx := indexInt64(set.DocumentIDs, documentID)
|
||||
if idx < 0 {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetFileInvalid
|
||||
}
|
||||
var removedDoc domain.Document
|
||||
removedFound := false
|
||||
for _, d := range docs {
|
||||
if d.ID == documentID {
|
||||
removedDoc = d
|
||||
removedFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !removedFound {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetFileInvalid
|
||||
}
|
||||
set.DocumentIDs = removeInt64At(set.DocumentIDs, idx)
|
||||
set.Count = len(set.DocumentIDs)
|
||||
set.Packs = removeDocumentFromStickerPacks(set.Packs, documentID)
|
||||
set.Keywords = removeStickerKeywords(set.Keywords, documentID)
|
||||
removedDoc = detachStickerSetFromDocument(removedDoc)
|
||||
docs = removeDocumentByID(docs, documentID)
|
||||
if set.ThumbDocumentID == documentID {
|
||||
clearStickerSetThumb(&set)
|
||||
if len(docs) > 0 {
|
||||
setStickerSetThumbFromDocument(&set, docs[0])
|
||||
}
|
||||
}
|
||||
set.Hash = stickerSetHash(set)
|
||||
return s.persistStickerSetMutation(ctx, set, docs, []domain.Document{removedDoc})
|
||||
}
|
||||
|
|
@ -62,6 +62,23 @@ func (s *Service) CreateStickerSet(ctx context.Context, req domain.CreateSticker
|
|||
if req.CreatorUserID <= 0 {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetCreatorInvalid
|
||||
}
|
||||
return s.createStickerSet(ctx, req)
|
||||
}
|
||||
|
||||
// AdminCreateStickerSet creates a pack with no owning user (CreatorUserID
|
||||
// forced to 0) so it's editable only through the Admin* bypass methods —
|
||||
// the same convention seed-imported packs already use. Unlike CreateStickerSet,
|
||||
// the caller must supply a non-empty ShortName: short-name suggestion needs a
|
||||
// real user id to build a stable suffix, which an unowned pack doesn't have.
|
||||
func (s *Service) AdminCreateStickerSet(ctx context.Context, req domain.CreateStickerSetRequest) (domain.StickerSet, []domain.Document, error) {
|
||||
req.CreatorUserID = 0
|
||||
if strings.TrimSpace(req.ShortName) == "" {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetShortNameInvalid
|
||||
}
|
||||
return s.createStickerSet(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Service) createStickerSet(ctx context.Context, req domain.CreateStickerSetRequest) (domain.StickerSet, []domain.Document, error) {
|
||||
title := strings.TrimSpace(req.Title)
|
||||
if err := validateStickerSetTitle(title); err != nil {
|
||||
return domain.StickerSet{}, nil, err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue