199 lines
7.2 KiB
Go
199 lines
7.2 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// projectPrivateStarGiftSourceRef exposes a user-owned gift's stable source
|
|
// message identity only in the gift owner's message-box projection. Telegram
|
|
// defines gift_msg_id as receiver-only. A non-owner counterpart box id is not
|
|
// a valid substitute: it could resolve to an unrelated gift owned by that
|
|
// viewer. User unique actions do not use channel-only peer/saved_id fields;
|
|
// their owner-scoped message ids are registered separately at write time.
|
|
func projectPrivateStarGiftSourceRef(
|
|
_ context.Context,
|
|
_ pgx.Tx,
|
|
req *domain.SendPrivateTextRequest,
|
|
sourceOwnerUserID int64,
|
|
sourceOwnerBoxID int,
|
|
) (privateSendMediaProjection, error) {
|
|
if req == nil || req.Media == nil || sourceOwnerUserID <= 0 || sourceOwnerBoxID <= 0 ||
|
|
(sourceOwnerUserID != req.SenderUserID && sourceOwnerUserID != req.RecipientUserID) {
|
|
return privateSendMediaProjection{}, fmt.Errorf("project private star gift source: invalid scope")
|
|
}
|
|
|
|
shared, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
sender, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
recipient, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
|
|
switch {
|
|
case privateStarGiftAction(shared) != nil:
|
|
sharedAction := privateStarGiftAction(shared)
|
|
senderAction := privateStarGiftAction(sender)
|
|
recipientAction := privateStarGiftAction(recipient)
|
|
if sharedAction.GiftMsgID != sourceOwnerBoxID {
|
|
return privateSendMediaProjection{}, fmt.Errorf(
|
|
"project private star gift source: gift_msg_id %d does not match owner box %d",
|
|
sharedAction.GiftMsgID, sourceOwnerBoxID,
|
|
)
|
|
}
|
|
sharedAction.GiftMsgID = 0
|
|
senderAction.GiftMsgID = 0
|
|
recipientAction.GiftMsgID = 0
|
|
// messageActionStarGift.can_upgrade is receiver-only. A separate
|
|
// prepayment notification shares one logical private message, but its
|
|
// payer box must not advertise owner actions.
|
|
sharedAction.CanUpgrade = false
|
|
senderAction.CanUpgrade = req.SenderUserID == sourceOwnerUserID && senderAction.CanUpgrade
|
|
recipientAction.CanUpgrade = req.RecipientUserID == sourceOwnerUserID && recipientAction.CanUpgrade
|
|
if req.SenderUserID == sourceOwnerUserID {
|
|
senderAction.GiftMsgID = sourceOwnerBoxID
|
|
} else {
|
|
recipientAction.GiftMsgID = sourceOwnerBoxID
|
|
}
|
|
default:
|
|
return privateSendMediaProjection{}, fmt.Errorf("project private star gift source: unsupported media")
|
|
}
|
|
|
|
return privateSendMediaProjection{Shared: shared, Sender: sender, Recipient: recipient}, nil
|
|
}
|
|
|
|
// projectPrivateStarGiftPurchase scopes the two mutually exclusive actions of
|
|
// an ordinary user gift to the correct account-local message box:
|
|
// - the gift owner/receiver may upgrade it and must not receive the separate
|
|
// prepayment hash;
|
|
// - the non-owner sender may use the hash to prepay for the owner's upgrade,
|
|
// but must not receive the receiver-only can_upgrade capability.
|
|
//
|
|
// The shared logical envelope carries neither viewer-only field. This keeps a
|
|
// future read/replay path from accidentally treating it as either participant's
|
|
// projection while the two message_boxes remain the durable wire truth.
|
|
func projectPrivateStarGiftPurchase(
|
|
_ context.Context,
|
|
_ pgx.Tx,
|
|
req *domain.SendPrivateTextRequest,
|
|
) (privateSendMediaProjection, error) {
|
|
if req == nil || req.Media == nil || req.SenderUserID <= 0 || req.RecipientUserID <= 0 {
|
|
return privateSendMediaProjection{}, fmt.Errorf("project private star gift purchase: invalid scope")
|
|
}
|
|
action := privateStarGiftAction(req.Media)
|
|
if action == nil {
|
|
return privateSendMediaProjection{}, fmt.Errorf("project private star gift purchase: unsupported media")
|
|
}
|
|
ownerUserID := action.PeerUserID
|
|
if ownerUserID == 0 && action.To.Type == domain.PeerTypeUser {
|
|
ownerUserID = action.To.ID
|
|
}
|
|
if ownerUserID <= 0 || ownerUserID != req.RecipientUserID {
|
|
return privateSendMediaProjection{}, fmt.Errorf(
|
|
"project private star gift purchase: owner %d does not match recipient %d",
|
|
ownerUserID, req.RecipientUserID,
|
|
)
|
|
}
|
|
|
|
shared, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
sender, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
recipient, err := cloneMessageMedia(req.Media)
|
|
if err != nil {
|
|
return privateSendMediaProjection{}, err
|
|
}
|
|
|
|
sharedAction := privateStarGiftAction(shared)
|
|
senderAction := privateStarGiftAction(sender)
|
|
recipientAction := privateStarGiftAction(recipient)
|
|
sharedAction.PrepaidUpgradeHash = ""
|
|
sharedAction.CanUpgrade = false
|
|
if req.SenderUserID == ownerUserID {
|
|
senderAction.PrepaidUpgradeHash = ""
|
|
} else {
|
|
senderAction.CanUpgrade = false
|
|
}
|
|
recipientAction.PrepaidUpgradeHash = ""
|
|
return privateSendMediaProjection{Shared: shared, Sender: sender, Recipient: recipient}, nil
|
|
}
|
|
|
|
func cloneMessageMedia(media *domain.MessageMedia) (*domain.MessageMedia, error) {
|
|
encoded, err := encodeMessageMedia(media)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone private message media: %w", err)
|
|
}
|
|
cloned, err := decodeMessageMedia(string(encoded))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone private message media: %w", err)
|
|
}
|
|
return cloned, nil
|
|
}
|
|
|
|
// encodeSharedPrivateStarGiftMedia returns the logical private-message
|
|
// envelope for an already viewpoint-projected Star Gift service message.
|
|
// Conversation message ids belong to a single owner's message_boxes
|
|
// namespace, so the shared row must never retain them. saved_id is likewise
|
|
// box-local for user gifts, while channel saved ids remain globally meaningful
|
|
// inside the channel gift namespace.
|
|
func encodeSharedPrivateStarGiftMedia(media *domain.MessageMedia) ([]byte, error) {
|
|
shared, err := cloneMessageMedia(media)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch {
|
|
case privateStarGiftAction(shared) != nil:
|
|
action := privateStarGiftAction(shared)
|
|
action.GiftMsgID = 0
|
|
action.UpgradeMsgID = 0
|
|
if action.PeerUserID > 0 || action.To.Type == domain.PeerTypeUser {
|
|
action.SavedID = 0
|
|
action.PrepaidUpgradeHash = ""
|
|
action.CanUpgrade = false
|
|
}
|
|
case privateStarGiftUniqueAction(shared) != nil:
|
|
action := privateStarGiftUniqueAction(shared)
|
|
if action.Peer.Type == domain.PeerTypeUser {
|
|
action.SavedID = 0
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("encode shared private star gift media: unsupported media")
|
|
}
|
|
|
|
encoded, err := encodeMessageMedia(shared)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode shared private star gift media: %w", err)
|
|
}
|
|
return encoded, nil
|
|
}
|
|
|
|
func privateStarGiftAction(media *domain.MessageMedia) *domain.MessageStarGiftAction {
|
|
if media == nil || media.Kind != domain.MessageMediaKindService || media.ServiceAction == nil ||
|
|
media.ServiceAction.Kind != domain.MessageServiceActionStarGift {
|
|
return nil
|
|
}
|
|
return media.ServiceAction.StarGift
|
|
}
|
|
|
|
func privateStarGiftUniqueAction(media *domain.MessageMedia) *domain.MessageStarGiftUniqueAction {
|
|
if media == nil || media.Kind != domain.MessageMediaKindService || media.ServiceAction == nil ||
|
|
media.ServiceAction.Kind != domain.MessageServiceActionStarGiftUnique {
|
|
return nil
|
|
}
|
|
return media.ServiceAction.StarGiftUnique
|
|
}
|