Ctrl K
ComunidadGuías…

internal/domain/botapi_update.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package domain

import "time"

// BotAPIUpdateKind is the Bot API delivery shape for a queued update.
type BotAPIUpdateKind string

const (
	BotAPIUpdateMessage          BotAPIUpdateKind = "message"
	BotAPIUpdateEditedMessage    BotAPIUpdateKind = "edited_message"
	BotAPIUpdateCallbackQuery    BotAPIUpdateKind = "callback_query"
	BotAPIUpdatePreCheckoutQuery BotAPIUpdateKind = "pre_checkout_query"
)

// BotPreCheckoutQuery is the protocol-neutral payload shared by the MTProto
// updateBotPrecheckoutQuery and the HTTP Bot API PreCheckoutQuery projection.
//
// It asks the bot to confirm an order before any Stars move. The payer is held in
// the payment RPC for PreCheckoutTimeout; if the bot has not answered by then the
// payment is failed, which is what makes this a real gate and not a notification.
type BotPreCheckoutQuery struct {
	ID        int64 `json:"id"`
	BotUserID int64 `json:"bot_user_id"`
	UserID    int64 `json:"user_id"`
	// Currency and TotalAmount are what the payer is about to be charged. The bot
	// is expected to verify them against its own order state.
	Currency    string `json:"currency"`
	TotalAmount int64  `json:"total_amount"`
	// Payload is the invoice_payload from sendInvoice, the bot's own order key.
	Payload string `json:"invoice_payload"`
	// ShippingOptionID is reserved: telesrv settles Stars only and models no
	// shipping, so it is always empty.
	ShippingOptionID string `json:"shipping_option_id,omitempty"`
}

// BotPreCheckoutAnswer is what the bot sends back. Error is the message the payer
// sees when OK is false.
type BotPreCheckoutAnswer struct {
	OK    bool
	Error string
}

// BotCallbackQuery is the protocol-neutral payload shared by MTProto
// updateBotCallbackQuery and the HTTP Bot API CallbackQuery projection.
type BotCallbackQuery struct {
	ID            int64
	BotUserID     int64
	UserID        int64
	Peer          Peer
	MessageID     int
	ChatInstance  int64
	Data          []byte
	InlineMessage *BotInlineMessageID
	// ClientSession is available only to an in-process service bot. It must not
	// become part of a durable/public Bot API CallbackQuery payload.
	ClientSession ClientSessionMetadata `json:"-"`
}

// BotAPIEphemeralPayload is a self-contained 24-hour Bot API queue snapshot.
// Ordinary queued messages are reloaded from their durable message tables;
// ephemeral messages have no such table and therefore travel in this explicit
// envelope instead of overloading SourcePts or an ordinary message id. The
// public shape deliberately cannot represent random IDs, payload hashes,
// auth-key/session identifiers, or the originating device.
type BotAPIEphemeralPayload struct {
	Message BotAPIEphemeralMessage
	ReplyTo *BotAPIEphemeralMessage `json:",omitempty"`
}

type BotAPIEphemeralMessage struct {
	ID                 int
	Peer               Peer
	SenderUserID       int64
	ReceiverUserID     int64
	Date               int
	EditDate           int
	TopMessageID       int
	ReplyToEphemeralID int
	Content            EphemeralContent
	Version            uint64
	ExpiresAt          time.Time
}

func NewBotAPIEphemeralPayload(message EphemeralMessage) *BotAPIEphemeralPayload {
	payload := &BotAPIEphemeralPayload{Message: publicBotAPIEphemeralMessage(message)}
	if message.BotAPIReply != nil {
		reply := publicBotAPIEphemeralMessage(*message.BotAPIReply)
		payload.ReplyTo = &reply
	}
	return payload
}

func publicBotAPIEphemeralMessage(message EphemeralMessage) BotAPIEphemeralMessage {
	return BotAPIEphemeralMessage{
		ID: message.ID, Peer: message.Peer,
		SenderUserID: message.SenderUserID, ReceiverUserID: message.ReceiverUserID,
		Date: message.Date, EditDate: message.EditDate,
		TopMessageID: message.TopMessageID, ReplyToEphemeralID: message.ReplyToEphemeralID,
		Content: message.Content, Version: message.Version, ExpiresAt: message.ExpiresAt,
	}
}

func (m BotAPIEphemeralMessage) EphemeralMessage() EphemeralMessage {
	return EphemeralMessage{
		ID: m.ID, Peer: m.Peer,
		SenderUserID: m.SenderUserID, ReceiverUserID: m.ReceiverUserID,
		Date: m.Date, EditDate: m.EditDate,
		TopMessageID: m.TopMessageID, ReplyToEphemeralID: m.ReplyToEphemeralID,
		Content: m.Content, Version: m.Version, ExpiresAt: m.ExpiresAt,
	}
}

func (p BotAPIEphemeralPayload) EphemeralMessage() EphemeralMessage {
	message := p.Message.EphemeralMessage()
	if p.ReplyTo != nil {
		reply := p.ReplyTo.EphemeralMessage()
		message.BotAPIReply = &reply
	}
	return message
}

func (p BotAPIEphemeralPayload) Validate() error {
	if err := p.Message.Validate(); err != nil {
		return err
	}
	if p.Message.ReplyToEphemeralID == 0 {
		if p.ReplyTo != nil {
			return ErrEphemeralInvalid
		}
		return nil
	}
	if p.ReplyTo == nil || p.ReplyTo.Validate() != nil || p.ReplyTo.ID != p.Message.ReplyToEphemeralID ||
		p.ReplyTo.Peer != p.Message.Peer || p.ReplyTo.Date > p.Message.Date ||
		!sameEphemeralParticipantPair(p.Message.SenderUserID, p.Message.ReceiverUserID, p.ReplyTo.SenderUserID, p.ReplyTo.ReceiverUserID) {
		return ErrEphemeralInvalid
	}
	return nil
}

func sameEphemeralParticipantPair(firstSender, firstReceiver, secondSender, secondReceiver int64) bool {
	return (firstSender == secondSender && firstReceiver == secondReceiver) ||
		(firstSender == secondReceiver && firstReceiver == secondSender)
}

func (m BotAPIEphemeralMessage) Expired(now time.Time) bool {
	return !m.ExpiresAt.IsZero() && !now.Before(m.ExpiresAt)
}

func (m BotAPIEphemeralMessage) Validate() error {
	date := time.Unix(int64(m.Date), 0)
	if m.ID <= 0 || m.ID > MaxMessageBoxID || m.Peer.Type != PeerTypeChannel || m.Peer.ID <= 0 ||
		m.SenderUserID <= 0 || m.ReceiverUserID <= 0 || m.SenderUserID == m.ReceiverUserID ||
		m.Date <= 0 || m.Version == 0 || m.ExpiresAt.IsZero() || !m.ExpiresAt.After(date) ||
		m.ExpiresAt.Sub(date) > EphemeralMessageRetention+time.Second ||
		(m.EditDate != 0 && m.EditDate < m.Date) || m.TopMessageID < 0 || m.TopMessageID > MaxMessageBoxID ||
		m.ReplyToEphemeralID < 0 || m.ReplyToEphemeralID > MaxMessageBoxID || m.ReplyToEphemeralID == m.ID {
		return ErrEphemeralInvalid
	}
	return ValidateEphemeralContent(m.Content)
}

// BotInlineMessageID is the domain-only shape of inputBotInlineMessageID64.
// It can be projected both to MTProto and to Bot API's opaque
// inline_message_id without leaking tg types into the store boundary.
type BotInlineMessageID struct {
	DCID       int
	OwnerID    int64
	ID         int
	AccessHash int64
}

// BotAPIUpdate is a durable Bot API update cursor. ID is the Bot API update_id
// and is global across all bots, matching Telegram Bot API's monotonic offset
// contract without reusing MTProto pts from user/channel logs.
type BotAPIUpdate struct {
	ID        int64
	BotUserID int64
	Kind      BotAPIUpdateKind
	Peer      Peer
	MessageID int
	SourcePts int
	Date      int
	Callback  *BotCallbackQuery
	// PreCheckout carries a pre-checkout query. It has no message of its own, so
	// Peer and MessageID stay zero and the projection reads only this field.
	PreCheckout *BotPreCheckoutQuery
	Ephemeral   *BotAPIEphemeralPayload
}

// EnqueueBotAPIUpdateRequest describes a message-like update that should be
// delivered to one bot via getUpdates.
type EnqueueBotAPIUpdateRequest struct {
	BotUserID   int64
	Kind        BotAPIUpdateKind
	Peer        Peer
	MessageID   int
	SourcePts   int
	Date        int
	Callback    *BotCallbackQuery
	PreCheckout *BotPreCheckoutQuery
	Ephemeral   *BotAPIEphemeralPayload
}