package logic import ( "context" "errors" "time" deliverentity "spark-server/internal/module/deliver/entity" "spark-server/internal/module/message/entity" "spark-server/internal/pkg/errs" ) const cmdRecall uint16 = 9 func (s *Service) RecallMessage(ctx context.Context, userID int64, deviceID uint64, msgID int64) error { if !s.ready() || userID <= 0 { return errs.ErrUnauthorized } if msgID <= 0 { return errs.ErrInvalidArgument } msg, err := s.repo.GetByID(ctx, msgID) if err != nil { return err } if msg.SenderID != userID || msg.SenderID == 0 { return errs.ErrForbidden } if _, err := s.chats.GetMember(ctx, msg.ConversationID, userID); err != nil { if errors.Is(err, errs.ErrNotFound) { return errs.ErrForbidden } return err } if msg.Status == entity.StatusRecalled { return s.enqueueRecall(ctx, msg, userID, deviceID) } if msg.Status != entity.StatusNormal { return errs.ErrForbidden } if time.Since(time.UnixMilli(msg.CreatedAtMS)) > recallWindow { return errs.ErrForbidden } if err := s.repo.Recall(ctx, msg.ID); err != nil { return err } msg.Status = entity.StatusRecalled return s.enqueueRecall(ctx, msg, userID, deviceID) } func (s *Service) enqueueRecall(ctx context.Context, msg *entity.Message, userID int64, deviceID uint64) error { if s.queue == nil { return nil } jobID, err := s.ids.NextID() if err != nil { return err } members, err := s.chats.ListMemberIDs(ctx, msg.ConversationID) if err != nil { return err } return s.queue.Push(ctx, &deliverentity.Job{ MsgID: jobID, Cmd: cmdRecall, ConversationID: msg.ConversationID, FromUserID: userID, FromDeviceID: deviceID, ExcludeDevice: deviceID, ToUserIDs: members, Payload: controlBytes(msg.ID, msg.ConversationID), Preview: previewOf(msg), }) }