feat: add stickers

This commit is contained in:
Krispire Admin
2026-08-30 02:18:53 +05:30
parent cee2d3c874
commit fecb9bc045
15 changed files with 752 additions and 6 deletions
+45 -1
View File
@@ -418,6 +418,9 @@ type SendRequest struct {
PreviewURL *string
// Entity is something else in the app — a post — shared as a card.
Entity *EntityAttachment
// StickerKey sends a piece of reward artwork. Ownership is checked
// server-side; the key alone proves nothing.
StickerKey *string
// Quote is a passage shared from the library, rendered as a quote the
// reader can tap to return to the exact phrase.
Quote *QuoteAttachment
@@ -458,7 +461,8 @@ func (s *ChatService) Send(ctx context.Context, userID, conversationID string, r
body := strings.TrimSpace(req.Body)
// A photo or a shared passage with no caption is an ordinary message; only
// a message with nothing at all in it is empty.
if body == "" && len(req.Attachments) == 0 && req.Quote == nil && req.Entity == nil {
if body == "" && len(req.Attachments) == 0 && req.Quote == nil &&
req.Entity == nil && req.StickerKey == nil {
return nil, ErrChatEmptyBody
}
if len([]rune(body)) > maxBodyRunes {
@@ -493,6 +497,12 @@ func (s *ChatService) Send(ctx context.Context, userID, conversationID string, r
return s.hydrateSent(ctx, userID, conversationID, msg), nil
}
if req.StickerKey != nil {
if err := s.attachSticker(ctx, conversationID, userID, msg, *req.StickerKey); err != nil {
s.logger.Warn("chat: sticker refused",
zap.String("user_id", userID), zap.Error(err))
}
}
if err := s.attachEntity(ctx, conversationID, userID, msg, req.Entity); err != nil {
s.logger.Warn("chat: entity share refused", zap.String("user_id", userID), zap.Error(err))
}
@@ -1624,6 +1634,40 @@ func (s *ChatService) Message(ctx context.Context, userID, conversationID, messa
return s.withAttachments(ctx, userID, msg)
}
// Stickers lists what the caller can send: sādhana rewards they earned, plus
// stickers they saved from a chat.
func (s *ChatService) Stickers(ctx context.Context, userID string) ([]model.Sticker, error) {
return s.chat.Stickers(ctx, userID)
}
// SaveSticker keeps one someone sent, after checking it really is on that
// message and the caller is in the thread.
func (s *ChatService) SaveSticker(ctx context.Context, userID, messageID, key string) error {
return s.chat.SaveSticker(ctx, userID, key, messageID)
}
// attachSticker stores the artwork on a message.
//
// Ownership is checked here, not trusted from the client: the key comes over
// the wire, and without this anyone could send any sticker in the catalog by
// guessing its key. The image and name are denormalized because the catalog
// is static art — there is nothing viewer-dependent to re-resolve on read.
func (s *ChatService) attachSticker(ctx context.Context, conversationID, userID string, msg *model.Message, key string) error {
sticker, err := s.chat.Sticker(ctx, userID, key)
if err != nil {
return err
}
entityType := model.EntitySticker
if _, err := s.chat.AddAttachment(ctx, repository.AddAttachmentParams{
MessageID: msg.ID, ConversationID: conversationID, Ord: 0,
Kind: model.AttachmentSticker, EntityType: &entityType,
EntityID: &sticker.Key, ImageURL: &sticker.ImageURL, Title: &sticker.Name,
}); err != nil {
s.logger.Warn("chat: attach sticker failed", zap.Error(err))
}
return nil
}
// Typing broadcasts that someone is composing.
//
// Never persisted and never pushed: a typing indicator is only meaningful to