feat: sync official star gift lifecycle tooling
Sync telesrv 4c0e2d9 (feat: complete official star gift lifecycle and admin tooling). Public adjustments: skipped private docs/deploy-nginx/README source changes, kept iamxvbaba/td public dependency, replaced local/private sample IP and orange seed label.
This commit is contained in:
parent
f2c2fd0236
commit
14bf7d1e20
92 changed files with 14768 additions and 727 deletions
|
|
@ -550,6 +550,9 @@ const (
|
|||
ChannelActionPaidMessagesPrice ChannelMessageActionType = "paid_messages_price"
|
||||
// ChannelActionStarGift 映射 messageActionStarGift:频道礼物的 admin-log 快照。
|
||||
ChannelActionStarGift ChannelMessageActionType = "star_gift"
|
||||
// ChannelActionStarGiftUnique 映射 messageActionStarGiftUnique:collectible
|
||||
// 升级、转赠等所有权变更只进入 Recent Actions,不伪造频道历史/pts。
|
||||
ChannelActionStarGiftUnique ChannelMessageActionType = "star_gift_unique"
|
||||
// ChannelActionSetChatWallpaper 映射 messageActionSetChatWallPaper:频道外观页设置 wallpaper。
|
||||
ChannelActionSetChatWallpaper ChannelMessageActionType = "set_chat_wallpaper"
|
||||
)
|
||||
|
|
@ -583,7 +586,8 @@ type ChannelMessageAction struct {
|
|||
Incompleted []int
|
||||
TodoItems []MessageTodoItem
|
||||
// StarGift 仅 star_gift 服务消息使用。
|
||||
StarGift *MessageStarGiftAction
|
||||
StarGift *MessageStarGiftAction
|
||||
StarGiftUnique *MessageStarGiftUniqueAction
|
||||
// Wallpaper 仅 set_chat_wallpaper 服务消息使用。
|
||||
Wallpaper *Wallpaper
|
||||
// Photo 仅 chat_edit_photo 服务消息使用。
|
||||
|
|
|
|||
|
|
@ -564,7 +564,9 @@ const (
|
|||
// MessageServiceActionStarGiftUnique maps messageActionStarGiftUnique. The
|
||||
// immutable collectible snapshot is carried by the service message so an
|
||||
// exact replay/difference never depends on mutable catalog state.
|
||||
MessageServiceActionStarGiftUnique MessageServiceActionKind = "star_gift_unique"
|
||||
MessageServiceActionStarGiftUnique MessageServiceActionKind = "star_gift_unique"
|
||||
MessageServiceActionStarGiftOffer MessageServiceActionKind = "star_gift_offer"
|
||||
MessageServiceActionStarGiftOfferDeclined MessageServiceActionKind = "star_gift_offer_declined"
|
||||
)
|
||||
|
||||
// MessagePhoneCallAction 是 messageActionPhoneCall 的协议中立载荷。
|
||||
|
|
@ -612,51 +614,87 @@ type MessageRequestedPeerAction struct {
|
|||
|
||||
// MessageServiceAction 是私聊服务消息动作的协议中立表示。
|
||||
type MessageServiceAction struct {
|
||||
Kind MessageServiceActionKind `json:"kind"`
|
||||
Photo *Photo `json:"photo,omitempty"`
|
||||
Call *MessagePhoneCallAction `json:"call,omitempty"`
|
||||
ConferenceCall *MessageConferenceCallAction `json:"conference_call,omitempty"`
|
||||
BotAllowed *MessageBotAllowedAction `json:"bot_allowed,omitempty"`
|
||||
WebViewData *MessageWebViewDataAction `json:"web_view_data,omitempty"`
|
||||
RequestedPeer *MessageRequestedPeerAction `json:"requested_peer,omitempty"`
|
||||
ChatThemeEmoticon string `json:"chat_theme_emoticon,omitempty"`
|
||||
StarGift *MessageStarGiftAction `json:"star_gift,omitempty"`
|
||||
StarGiftUnique *MessageStarGiftUniqueAction `json:"star_gift_unique,omitempty"`
|
||||
Kind MessageServiceActionKind `json:"kind"`
|
||||
Photo *Photo `json:"photo,omitempty"`
|
||||
Call *MessagePhoneCallAction `json:"call,omitempty"`
|
||||
ConferenceCall *MessageConferenceCallAction `json:"conference_call,omitempty"`
|
||||
BotAllowed *MessageBotAllowedAction `json:"bot_allowed,omitempty"`
|
||||
WebViewData *MessageWebViewDataAction `json:"web_view_data,omitempty"`
|
||||
RequestedPeer *MessageRequestedPeerAction `json:"requested_peer,omitempty"`
|
||||
ChatThemeEmoticon string `json:"chat_theme_emoticon,omitempty"`
|
||||
StarGift *MessageStarGiftAction `json:"star_gift,omitempty"`
|
||||
StarGiftUnique *MessageStarGiftUniqueAction `json:"star_gift_unique,omitempty"`
|
||||
StarGiftOffer *MessageStarGiftOfferAction `json:"star_gift_offer,omitempty"`
|
||||
StarGiftOfferDeclined *MessageStarGiftOfferDeclinedAction `json:"star_gift_offer_declined,omitempty"`
|
||||
}
|
||||
|
||||
// MessageStarGiftAction 是 messageActionStarGift 的协议中立载荷:内嵌礼物快照(贴纸/星价)
|
||||
// 使收礼人无需额外拉取即可渲染。PeerUserID/PeerChannelID 为收礼方;NameHidden 时下发不暴露 from。
|
||||
type MessageStarGiftAction struct {
|
||||
GiftID int64 `json:"gift_id"`
|
||||
Stars int64 `json:"stars"`
|
||||
ConvertStars int64 `json:"convert_stars,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Sticker *Document `json:"sticker,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
FromUserID int64 `json:"from_user_id,omitempty"`
|
||||
PeerUserID int64 `json:"peer_user_id,omitempty"`
|
||||
PeerChannelID int64 `json:"peer_channel_id,omitempty"`
|
||||
SavedID int64 `json:"saved_id,omitempty"`
|
||||
NameHidden bool `json:"name_hidden,omitempty"`
|
||||
Saved bool `json:"saved,omitempty"`
|
||||
Converted bool `json:"converted,omitempty"`
|
||||
CanUpgrade bool `json:"can_upgrade,omitempty"`
|
||||
PrepaidUpgrade bool `json:"prepaid_upgrade,omitempty"`
|
||||
UpgradeStars int64 `json:"upgrade_stars,omitempty"`
|
||||
UpgradeMsgID int `json:"upgrade_msg_id,omitempty"`
|
||||
GiftID int64 `json:"gift_id"`
|
||||
Stars int64 `json:"stars"`
|
||||
ConvertStars int64 `json:"convert_stars,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Sticker *Document `json:"sticker,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
FromUserID int64 `json:"from_user_id,omitempty"`
|
||||
PeerUserID int64 `json:"peer_user_id,omitempty"`
|
||||
PeerChannelID int64 `json:"peer_channel_id,omitempty"`
|
||||
SavedID int64 `json:"saved_id,omitempty"`
|
||||
NameHidden bool `json:"name_hidden,omitempty"`
|
||||
Saved bool `json:"saved,omitempty"`
|
||||
Converted bool `json:"converted,omitempty"`
|
||||
CanUpgrade bool `json:"can_upgrade,omitempty"`
|
||||
PrepaidUpgrade bool `json:"prepaid_upgrade,omitempty"`
|
||||
PrepaidUpgradeHash string `json:"prepaid_upgrade_hash,omitempty"`
|
||||
UpgradeSeparate bool `json:"upgrade_separate,omitempty"`
|
||||
// UpgradePriceStars belongs to the inner StarGift.upgrade_stars field and
|
||||
// is the price of a normal paid upgrade. UpgradeStars below belongs to the
|
||||
// outer messageActionStarGift and is only the amount already prepaid by the
|
||||
// sender. TDesktop uses these two fields to choose the paid vs free flow.
|
||||
UpgradePriceStars int64 `json:"upgrade_price_stars,omitempty"`
|
||||
UpgradeStars int64 `json:"upgrade_stars,omitempty"`
|
||||
UpgradeMsgID int `json:"upgrade_msg_id,omitempty"`
|
||||
GiftMsgID int `json:"gift_msg_id,omitempty"`
|
||||
GiftNum int `json:"gift_num,omitempty"`
|
||||
AuctionAcquired bool `json:"auction_acquired,omitempty"`
|
||||
To Peer `json:"to,omitempty"`
|
||||
}
|
||||
|
||||
// MessageStarGiftUniqueAction is the protocol-neutral payload of an upgrade
|
||||
// service message. Commercial transfer/resale/export fields are intentionally
|
||||
// absent from the collectibles mainline.
|
||||
type MessageStarGiftUniqueAction struct {
|
||||
Gift UniqueStarGift `json:"gift"`
|
||||
FromUserID int64 `json:"from_user_id,omitempty"`
|
||||
Peer Peer `json:"peer"`
|
||||
SavedID int64 `json:"saved_id,omitempty"`
|
||||
Upgrade bool `json:"upgrade,omitempty"`
|
||||
Saved bool `json:"saved,omitempty"`
|
||||
PrepaidUpgrade bool `json:"prepaid_upgrade,omitempty"`
|
||||
Gift UniqueStarGift `json:"gift"`
|
||||
FromUserID int64 `json:"from_user_id,omitempty"`
|
||||
Peer Peer `json:"peer"`
|
||||
SavedID int64 `json:"saved_id,omitempty"`
|
||||
Upgrade bool `json:"upgrade,omitempty"`
|
||||
Saved bool `json:"saved,omitempty"`
|
||||
PrepaidUpgrade bool `json:"prepaid_upgrade,omitempty"`
|
||||
Transferred bool `json:"transferred,omitempty"`
|
||||
Refunded bool `json:"refunded,omitempty"`
|
||||
Assigned bool `json:"assigned,omitempty"`
|
||||
FromOffer bool `json:"from_offer,omitempty"`
|
||||
Craft bool `json:"craft,omitempty"`
|
||||
CanExportAt int `json:"can_export_at,omitempty"`
|
||||
TransferStars int64 `json:"transfer_stars,omitempty"`
|
||||
ResaleAmount *StarGiftAmount `json:"resale_amount,omitempty"`
|
||||
CanTransferAt int `json:"can_transfer_at,omitempty"`
|
||||
CanResellAt int `json:"can_resell_at,omitempty"`
|
||||
DropOriginalDetailsStars int64 `json:"drop_original_details_stars,omitempty"`
|
||||
CanCraftAt int `json:"can_craft_at,omitempty"`
|
||||
}
|
||||
|
||||
type MessageStarGiftOfferAction struct {
|
||||
Gift UniqueStarGift `json:"gift"`
|
||||
Price StarGiftAmount `json:"price"`
|
||||
ExpiresAt int `json:"expires_at"`
|
||||
Accepted bool `json:"accepted,omitempty"`
|
||||
Declined bool `json:"declined,omitempty"`
|
||||
}
|
||||
|
||||
type MessageStarGiftOfferDeclinedAction struct {
|
||||
Gift UniqueStarGift `json:"gift"`
|
||||
Price StarGiftAmount `json:"price"`
|
||||
Expired bool `json:"expired,omitempty"`
|
||||
}
|
||||
|
||||
// MessageMedia 是一条消息媒体载荷的业务表示(落库为消息行上的 JSONB 快照)。
|
||||
|
|
|
|||
|
|
@ -24,29 +24,85 @@ type StarGift struct {
|
|||
UpgradeIssued int // 当前已发行数量
|
||||
Title string // 可选标题
|
||||
Sticker Document // 礼物贴纸快照(tg 投影必须是带 sticker 属性的有效 Document,否则客户端丢弃)
|
||||
|
||||
// Layer 228 regular-gift shape. Static release facts live in the immutable
|
||||
// catalog revision; AvailabilityRemains/AvailabilityResale are the current
|
||||
// inventory projection maintained on the catalog aggregate.
|
||||
Limited bool
|
||||
SoldOut bool
|
||||
Birthday bool
|
||||
RequirePremium bool
|
||||
LimitedPerUser bool
|
||||
PeerColorAvailable bool
|
||||
Auction bool
|
||||
AvailabilityRemains int
|
||||
AvailabilityTotal int
|
||||
AvailabilityResale int64
|
||||
FirstSaleDate int
|
||||
LastSaleDate int
|
||||
ResellMinStars int64
|
||||
ReleasedBy Peer
|
||||
PerUserTotal int
|
||||
PerUserRemains int
|
||||
LockedUntilDate int
|
||||
AuctionSlug string
|
||||
GiftsPerRound int
|
||||
AuctionStartDate int
|
||||
UpgradeVariants int
|
||||
Background *StarGiftBackground
|
||||
}
|
||||
|
||||
// StarGiftBackground is the release-level palette used by auction cards and
|
||||
// gift previews before a collectible backdrop is selected.
|
||||
type StarGiftBackground struct {
|
||||
CenterColor int
|
||||
EdgeColor int
|
||||
TextColor int
|
||||
}
|
||||
|
||||
// SavedStarGift 是一条已收到的礼物实例(peer_star_gifts 一行)。
|
||||
type SavedStarGift struct {
|
||||
ID int64
|
||||
Owner Peer // 收礼 peer(user/channel)
|
||||
FromUserID int64 // 送礼人(匿名也保留真实值供账本,下发时按 NameHidden 决定是否暴露)
|
||||
GiftID int64 // → StarGift.ID
|
||||
RevisionID int64 // → star_gift_catalog_revisions.id,历史查询必须按此版本投影
|
||||
MsgID int // 用户礼物的私聊 msg_id;频道礼物不进历史,固定为 0
|
||||
SavedID int64 // 频道礼物 inputSavedStarGiftChat.saved_id;用户礼物为 0
|
||||
Date int // 收到时刻 Unix 秒
|
||||
NameHidden bool // 送礼人请求隐藏姓名
|
||||
Unsaved bool // 未展示在个人资料(saveStarGift 切换)
|
||||
Converted bool // 已转换回 Stars(终态,从列表排除)
|
||||
ConvertStars int64 // 转换可退回的 Stars
|
||||
PrepaidUpgradeStars int64 // 送礼人随礼物预付的唯一礼物升级额
|
||||
Message string // 附言(可选)
|
||||
UniqueGiftID int64 // 非 0 表示已升级为唯一礼物;与 Converted 互斥
|
||||
UpgradeMsgID int // messageActionStarGiftUnique 的 owner 侧消息 id
|
||||
PinnedOrder int // >0 表示资料页置顶顺序
|
||||
CollectionIDs []int // 当前所属集合;按集合顺序稳定返回
|
||||
Unique *UniqueStarGift
|
||||
ID int64
|
||||
Owner Peer // 收礼 peer(user/channel)
|
||||
FromUserID int64 // 送礼人(匿名也保留真实值供账本,下发时按 NameHidden 决定是否暴露)
|
||||
GiftID int64 // → StarGift.ID
|
||||
RevisionID int64 // → star_gift_catalog_revisions.id,历史查询必须按此版本投影
|
||||
MsgID int // 用户礼物的私聊 msg_id;频道礼物不进历史,固定为 0
|
||||
SavedID int64 // 频道礼物 inputSavedStarGiftChat.saved_id;用户礼物为 0
|
||||
Date int // 收到时刻 Unix 秒
|
||||
NameHidden bool // 送礼人请求隐藏姓名
|
||||
Unsaved bool // 未展示在个人资料(saveStarGift 切换)
|
||||
Converted bool // 已转换回 Stars(终态,从列表排除)
|
||||
LifecycleStatus StarGiftLifecycleStatus
|
||||
ConvertStars int64 // 转换可退回的 Stars
|
||||
PrepaidUpgradeStars int64 // 送礼人随礼物预付的唯一礼物升级额
|
||||
PrepaidUpgradeHash string // 第三方单独代付升级的一次性 entitlement
|
||||
GiftNum int // auction-acquired release number for regular gifts
|
||||
Message string // 附言(可选)
|
||||
UniqueGiftID int64 // 非 0 表示已升级为唯一礼物;与 Converted 互斥
|
||||
TransferStars int64
|
||||
CanExportAt int
|
||||
CanTransferAt int
|
||||
CanResellAt int
|
||||
DropOriginalDetailsStars int64
|
||||
CanCraftAt int
|
||||
UpgradeMsgID int // 当前 owner 侧承载 messageActionStarGiftUnique 的消息 id;所有权转移时随新消息更新
|
||||
PinnedOrder int // >0 表示资料页置顶顺序
|
||||
CollectionIDs []int // 当前所属集合;按集合顺序稳定返回
|
||||
Unique *UniqueStarGift
|
||||
}
|
||||
|
||||
type StarGiftLifecycleStatus string
|
||||
|
||||
const (
|
||||
StarGiftLifecycleActive StarGiftLifecycleStatus = "active"
|
||||
StarGiftLifecycleConverted StarGiftLifecycleStatus = "converted"
|
||||
StarGiftLifecycleBurned StarGiftLifecycleStatus = "burned"
|
||||
StarGiftLifecycleExported StarGiftLifecycleStatus = "exported"
|
||||
)
|
||||
|
||||
func (s StarGiftLifecycleStatus) Live() bool {
|
||||
return s == StarGiftLifecycleActive
|
||||
}
|
||||
|
||||
// StarGiftCollectibleAttributeKind 是唯一礼物三个必选属性槽位。
|
||||
|
|
@ -58,8 +114,31 @@ const (
|
|||
StarGiftCollectibleBackdrop StarGiftCollectibleAttributeKind = "backdrop"
|
||||
)
|
||||
|
||||
// StarGiftCollectibleAttribute 是已发布属性池的一项。RarityPermille 同时是客户端展示的
|
||||
// 精确稀有度和升级抽取概率;同一 revision、同一 kind 的总和必须恰好为 1000。
|
||||
// StarGiftAttributeRarityKind mirrors the Layer 228 rarity union. Permille is the only
|
||||
// kind eligible for a regular upgrade draw; named rarities are currently used by
|
||||
// craft-only models and must still be preserved in the published attribute directory.
|
||||
type StarGiftAttributeRarityKind string
|
||||
|
||||
const (
|
||||
StarGiftRarityPermille StarGiftAttributeRarityKind = "permille"
|
||||
StarGiftRarityUncommon StarGiftAttributeRarityKind = "uncommon"
|
||||
StarGiftRarityRare StarGiftAttributeRarityKind = "rare"
|
||||
StarGiftRarityEpic StarGiftAttributeRarityKind = "epic"
|
||||
StarGiftRarityLegendary StarGiftAttributeRarityKind = "legendary"
|
||||
)
|
||||
|
||||
func (k StarGiftAttributeRarityKind) Valid() bool {
|
||||
switch k {
|
||||
case StarGiftRarityPermille, StarGiftRarityUncommon, StarGiftRarityRare,
|
||||
StarGiftRarityEpic, StarGiftRarityLegendary:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StarGiftCollectibleAttribute 是已发布属性池的一项。RarityKind/RarityPermille
|
||||
// 是客户端展示事实;普通升级把非 crafted 的 permille 值当相对权重,不要求合计为 1000。
|
||||
type StarGiftCollectibleAttribute struct {
|
||||
ID int64
|
||||
CollectibleRevisionID int64
|
||||
|
|
@ -71,7 +150,10 @@ type StarGiftCollectibleAttribute struct {
|
|||
EdgeColor int
|
||||
PatternColor int
|
||||
TextColor int
|
||||
RarityKind StarGiftAttributeRarityKind
|
||||
RarityPermille int
|
||||
Crafted bool
|
||||
OfficialDocumentID int64
|
||||
SortOrder int
|
||||
Animation *StarGiftAnimation
|
||||
Blob *FileBlob
|
||||
|
|
@ -79,33 +161,37 @@ type StarGiftCollectibleAttribute struct {
|
|||
|
||||
// StarGiftCollectibleRevision 是某普通礼物的一份不可变、可发布属性池。
|
||||
type StarGiftCollectibleRevision struct {
|
||||
ID int64
|
||||
GiftID int64
|
||||
Revision int
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
Issued int
|
||||
SlugPrefix string
|
||||
Published bool
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
CreatedBy string
|
||||
CreatedAt time.Time
|
||||
PublishedAt time.Time
|
||||
ID int64
|
||||
GiftID int64
|
||||
Revision int
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
Issued int
|
||||
SlugPrefix string
|
||||
Published bool
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
CreatedBy string
|
||||
CreatedAt time.Time
|
||||
PublishedAt time.Time
|
||||
OfficialGiftID int64
|
||||
SourceManifestSHA256 []byte
|
||||
}
|
||||
|
||||
// StarGiftCollectibleWrite 是后台创建/发布属性池的协议无关输入。
|
||||
type StarGiftCollectibleWrite struct {
|
||||
GiftID int64
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
SlugPrefix string
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
Actor string
|
||||
CommandID string
|
||||
GiftID int64
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
SlugPrefix string
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
Actor string
|
||||
CommandID string
|
||||
OfficialGiftID int64
|
||||
SourceManifestSHA256 []byte
|
||||
}
|
||||
|
||||
// UniqueStarGift 是一份已经发行的唯一礼物。属性、编号与 slug 一经创建永久不变。
|
||||
|
|
@ -118,6 +204,26 @@ type UniqueStarGift struct {
|
|||
Slug string
|
||||
Num int
|
||||
Owner Peer
|
||||
RequirePremium bool
|
||||
ResaleTonOnly bool
|
||||
ThemeAvailable bool
|
||||
Burned bool
|
||||
Crafted bool
|
||||
OwnerName string
|
||||
OwnerAddress string
|
||||
GiftAddress string
|
||||
ResellAmount *StarGiftAmount
|
||||
ResellVersion int64
|
||||
ReleasedBy Peer
|
||||
ValueAmount int64
|
||||
ValueCurrency string
|
||||
ValueUSD int64
|
||||
ThemePeer Peer
|
||||
Host Peer
|
||||
OfferMinStars int
|
||||
CraftChancePermille int
|
||||
LastSaleDate int
|
||||
LastSaleAmount *StarGiftAmount
|
||||
Model StarGiftCollectibleAttribute
|
||||
Pattern StarGiftCollectibleAttribute
|
||||
Backdrop StarGiftCollectibleAttribute
|
||||
|
|
@ -132,6 +238,33 @@ type UniqueStarGift struct {
|
|||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type StarGiftCurrency string
|
||||
|
||||
const (
|
||||
StarGiftCurrencyStars StarGiftCurrency = "XTR"
|
||||
StarGiftCurrencyTON StarGiftCurrency = "TON"
|
||||
)
|
||||
|
||||
type StarGiftAmount struct {
|
||||
Currency StarGiftCurrency
|
||||
Amount int64
|
||||
Nanos int
|
||||
}
|
||||
|
||||
func (a StarGiftAmount) Valid() bool {
|
||||
if a.Amount <= 0 {
|
||||
return false
|
||||
}
|
||||
switch a.Currency {
|
||||
case StarGiftCurrencyStars:
|
||||
return a.Nanos >= -999999999 && a.Nanos <= 999999999
|
||||
case StarGiftCurrencyTON:
|
||||
return a.Nanos == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StarGiftUpgradePreview 是客户端升级弹窗所需的当前价格和属性样例。
|
||||
type StarGiftUpgradePreview struct {
|
||||
GiftID int64
|
||||
|
|
@ -171,7 +304,205 @@ type StarGiftUpgradeRequest struct {
|
|||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftPurchaseRequest struct {
|
||||
BuyerUserID int64
|
||||
BuyerPremium bool
|
||||
To Peer
|
||||
GiftID int64
|
||||
RevisionID int64
|
||||
IncludeUpgrade bool
|
||||
HideName bool
|
||||
Message string
|
||||
ChargeStars int64
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
RecipientBlocked bool
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
// StarGiftPurchaseForm is the server-issued, short-lived payment intent that
|
||||
// binds payments.getPaymentForm to one later payments.sendStarsForm call. A
|
||||
// fresh form represents a fresh purchase even when every invoice field is the
|
||||
// same; retrying one form represents the same purchase command.
|
||||
type StarGiftPurchaseForm struct {
|
||||
FormID int64
|
||||
BuyerUserID int64
|
||||
To Peer
|
||||
GiftID int64
|
||||
RevisionID int64
|
||||
IncludeUpgrade bool
|
||||
HideName bool
|
||||
Message string
|
||||
ChargeStars int64
|
||||
IssuedAt int
|
||||
ExpiresAt int
|
||||
}
|
||||
|
||||
type StarGiftPurchaseResult struct {
|
||||
Gift StarGift
|
||||
Saved SavedStarGift
|
||||
Balance StarsBalance
|
||||
Send SendPrivateTextResult
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
// StarGiftConvertRequest identifies one owner-scoped regular gift conversion.
|
||||
// ActorUserID is the authenticated user who owns the user gift or administers
|
||||
// the channel gift; authorization is checked again at the RPC boundary.
|
||||
type StarGiftConvertRequest struct {
|
||||
ActorUserID int64
|
||||
Ref SavedStarGiftRef
|
||||
Date int
|
||||
}
|
||||
|
||||
// StarGiftConvertResult exposes the committed aggregate state. OwnerBalance is
|
||||
// the post-credit balance of either the user or the channel internal Stars
|
||||
// ledger selected by Saved.Owner.
|
||||
type StarGiftConvertResult struct {
|
||||
Saved SavedStarGift
|
||||
OwnerBalance int64
|
||||
}
|
||||
|
||||
type StarGiftUpgradeResult struct {
|
||||
Saved SavedStarGift
|
||||
Unique UniqueStarGift
|
||||
Balance StarsBalance
|
||||
Send SendPrivateTextResult
|
||||
SourceEdits []EditedMessageForUser
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
// StarGiftUpgradeReceipt is the immutable command envelope needed to replay a
|
||||
// committed upgrade after the saved gift has entered its unique terminal state.
|
||||
// In particular, a paid replay must not be rebound to a later catalog price.
|
||||
type StarGiftUpgradeReceipt struct {
|
||||
UserID int64
|
||||
SourceSavedGiftID int64
|
||||
FormID int64
|
||||
UniqueGiftID int64
|
||||
ChargeStars int64
|
||||
BalanceAfter int64
|
||||
SourceEditPts int
|
||||
RequirePrepaid bool
|
||||
KeepOriginalDetails bool
|
||||
}
|
||||
|
||||
type StarGiftPrepaidUpgradeRequest struct {
|
||||
PayerUserID int64
|
||||
Owner Peer
|
||||
Hash string
|
||||
ChargeStars int64
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftPrepaidUpgradeResult struct {
|
||||
Saved SavedStarGift
|
||||
Balance StarsBalance
|
||||
Send SendPrivateTextResult
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
type StarGiftDropOriginalDetailsRequest struct {
|
||||
UserID int64
|
||||
Ref SavedStarGiftRef
|
||||
ChargeStars int64
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
}
|
||||
|
||||
type StarGiftDropOriginalDetailsResult struct {
|
||||
Saved SavedStarGift
|
||||
Unique UniqueStarGift
|
||||
Balance StarsBalance
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
// StarGiftLifecyclePolicy is the server-owned policy snapshotted when a regular
|
||||
// gift becomes collectible. It deliberately contains no wallet/node/provider
|
||||
// configuration: TON remains only a currency unit in the local ledger.
|
||||
type StarGiftLifecyclePolicy struct {
|
||||
TransferStars int64
|
||||
DropOriginalDetailsStars int64
|
||||
OfferMinStars int
|
||||
ExportDelaySeconds int
|
||||
TransferDelaySeconds int
|
||||
ResellDelaySeconds int
|
||||
CraftDelaySeconds int
|
||||
CraftChancePermille int
|
||||
}
|
||||
|
||||
func (p StarGiftLifecyclePolicy) Valid() bool {
|
||||
return p.TransferStars >= 0 && p.DropOriginalDetailsStars >= 0 && p.OfferMinStars >= 0 &&
|
||||
p.ExportDelaySeconds >= 0 && p.TransferDelaySeconds >= 0 && p.ResellDelaySeconds >= 0 &&
|
||||
p.CraftDelaySeconds >= 0 && p.CraftChancePermille >= 0 && p.CraftChancePermille <= 1000
|
||||
}
|
||||
|
||||
// StarGiftMarketPolicy is snapshotted in the running aggregate coordinator.
|
||||
// Proceeds permille is the seller share; the remainder is recorded as platform
|
||||
// commission. TON is still only a unit in the local ledger.
|
||||
type StarGiftMarketPolicy struct {
|
||||
StarsProceedsPermille int
|
||||
TONProceedsPermille int
|
||||
}
|
||||
|
||||
func (p StarGiftMarketPolicy) Valid() bool {
|
||||
return p.StarsProceedsPermille >= 0 && p.StarsProceedsPermille <= 1000 &&
|
||||
p.TONProceedsPermille >= 0 && p.TONProceedsPermille <= 1000
|
||||
}
|
||||
|
||||
type StarGiftResaleFilter struct {
|
||||
GiftID int64
|
||||
SortByPrice bool
|
||||
SortByNum bool
|
||||
ForCraft bool
|
||||
StarsOnly bool
|
||||
ModelIDs []int64
|
||||
PatternIDs []int64
|
||||
BackdropIDs []int64
|
||||
Offset string
|
||||
Limit int
|
||||
}
|
||||
|
||||
type StarGiftResalePage struct {
|
||||
Gifts []UniqueStarGift
|
||||
Count int
|
||||
NextOffset string
|
||||
}
|
||||
|
||||
type StarGiftValueInfo struct {
|
||||
Currency string
|
||||
Value int64
|
||||
ValueIsAverage bool
|
||||
InitialSaleDate int
|
||||
InitialSaleStars int64
|
||||
InitialSalePrice int64
|
||||
LastSaleDate int
|
||||
LastSalePrice int64
|
||||
FloorPrice int64
|
||||
AveragePrice int64
|
||||
ListedCount int
|
||||
}
|
||||
|
||||
type StarGiftTransferRequest struct {
|
||||
ActorUserID int64
|
||||
Ref SavedStarGiftRef
|
||||
To Peer
|
||||
ChargeStars int64
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftTransferResult struct {
|
||||
Saved SavedStarGift
|
||||
Unique UniqueStarGift
|
||||
Balance StarsBalance
|
||||
|
|
@ -179,6 +510,161 @@ type StarGiftUpgradeResult struct {
|
|||
Duplicate bool
|
||||
}
|
||||
|
||||
type StarGiftListingRequest struct {
|
||||
ActorUserID int64
|
||||
Ref SavedStarGiftRef
|
||||
Amount *StarGiftAmount
|
||||
Date int
|
||||
}
|
||||
|
||||
type StarGiftResalePurchaseRequest struct {
|
||||
BuyerUserID int64
|
||||
Slug string
|
||||
To Peer
|
||||
Amount StarGiftAmount
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftOfferRequest struct {
|
||||
BuyerUserID int64
|
||||
Owner Peer
|
||||
Slug string
|
||||
Price StarGiftAmount
|
||||
Duration int
|
||||
RandomID int64
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftOffer struct {
|
||||
ID int64
|
||||
BuyerUserID int64
|
||||
Owner Peer
|
||||
UniqueGiftID int64
|
||||
Price StarGiftAmount
|
||||
RandomID int64
|
||||
OfferMsgID int
|
||||
BuyerMsgID int
|
||||
Status string
|
||||
CreatedAt int
|
||||
ExpiresAt int
|
||||
ResolvedAt int
|
||||
Gift UniqueStarGift
|
||||
}
|
||||
|
||||
type StarGiftOfferResult struct {
|
||||
Offer StarGiftOffer
|
||||
Saved SavedStarGift
|
||||
Unique UniqueStarGift
|
||||
Balance StarsBalance
|
||||
Send SendPrivateTextResult
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
type StarGiftResolveOfferRequest struct {
|
||||
OwnerUserID int64
|
||||
OfferMsgID int
|
||||
Decline bool
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftCraftRequest struct {
|
||||
UserID int64
|
||||
Refs []SavedStarGiftRef
|
||||
CommandKey string
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftCraftResult struct {
|
||||
Success bool
|
||||
Chance int
|
||||
Gift *UniqueStarGift
|
||||
Send SendPrivateTextResult
|
||||
SourceEdits []EditedMessageForUser
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
type StarGiftAuction struct {
|
||||
Gift StarGift
|
||||
Version int
|
||||
StartDate int
|
||||
EndDate int
|
||||
MinBidAmount int64
|
||||
NextRoundAt int
|
||||
LastGiftNum int
|
||||
GiftsLeft int
|
||||
CurrentRound int
|
||||
TotalRounds int
|
||||
RoundDuration int
|
||||
BidLevels []StarGiftAuctionBidLevel
|
||||
TopBidders []int64
|
||||
UserState StarGiftAuctionUserState
|
||||
Finished bool
|
||||
AveragePrice int64
|
||||
ListedCount int
|
||||
}
|
||||
|
||||
type StarGiftAuctionBidLevel struct {
|
||||
Pos int
|
||||
Amount int64
|
||||
Date int
|
||||
}
|
||||
|
||||
type StarGiftAuctionUserState struct {
|
||||
Returned bool
|
||||
BidAmount int64
|
||||
BidDate int
|
||||
MinBidAmount int64
|
||||
BidPeer Peer
|
||||
AcquiredCount int
|
||||
}
|
||||
|
||||
type StarGiftAuctionBidRequest struct {
|
||||
UserID int64
|
||||
GiftID int64
|
||||
Peer Peer
|
||||
BidAmount int64
|
||||
HideName bool
|
||||
Message string
|
||||
UpdateBid bool
|
||||
FormID int64
|
||||
Date int
|
||||
}
|
||||
|
||||
type StarGiftAuctionAcquired struct {
|
||||
Peer Peer
|
||||
Date int
|
||||
BidAmount int64
|
||||
Round int
|
||||
Pos int
|
||||
Message string
|
||||
GiftNum int
|
||||
NameHidden bool
|
||||
}
|
||||
|
||||
type StarGiftWithdrawalRequest struct {
|
||||
UserID int64
|
||||
Ref SavedStarGiftRef
|
||||
Date int
|
||||
}
|
||||
|
||||
type StarGiftWithdrawal struct {
|
||||
ProviderRequestID string
|
||||
URL string
|
||||
ExpiresAt int
|
||||
Status string
|
||||
Gift UniqueStarGift
|
||||
}
|
||||
|
||||
// StarGiftCollection 是 peer 资料页中的礼物集合;一份礼物可属于多个集合。
|
||||
type StarGiftCollection struct {
|
||||
Owner Peer
|
||||
|
|
@ -223,17 +709,53 @@ type StarGiftAnimation struct {
|
|||
|
||||
// StarGiftCatalogWrite 是 store 原子创建目录版本所需的协议无关数据。
|
||||
type StarGiftCatalogWrite struct {
|
||||
GiftID int64 // 0 创建新礼物;非 0 为该礼物创建新 revision
|
||||
Title string
|
||||
Stars int64
|
||||
ConvertStars int64
|
||||
Enabled bool
|
||||
SortOrder int
|
||||
Document Document
|
||||
Blob FileBlob
|
||||
Animation StarGiftAnimation
|
||||
Actor string
|
||||
CommandID string
|
||||
GiftID int64 // 0 创建新礼物;非 0 为该礼物创建新 revision
|
||||
Title string
|
||||
Stars int64
|
||||
ConvertStars int64
|
||||
Enabled bool
|
||||
SortOrder int
|
||||
Document Document
|
||||
Blob FileBlob
|
||||
Animation StarGiftAnimation
|
||||
Actor string
|
||||
CommandID string
|
||||
OfficialGiftID int64
|
||||
SourceManifestSHA256 []byte
|
||||
OfficialSourceJSON []byte
|
||||
Limited bool
|
||||
SoldOut bool
|
||||
Birthday bool
|
||||
RequirePremium bool
|
||||
LimitedPerUser bool
|
||||
PeerColorAvailable bool
|
||||
Auction bool
|
||||
AvailabilityRemains int
|
||||
AvailabilityTotal int
|
||||
AvailabilityResale int64
|
||||
FirstSaleDate int
|
||||
LastSaleDate int
|
||||
ResellMinStars int64
|
||||
ReleasedBy Peer
|
||||
PerUserTotal int
|
||||
LockedUntilDate int
|
||||
AuctionSlug string
|
||||
GiftsPerRound int
|
||||
AuctionStartDate int
|
||||
UpgradeVariants int
|
||||
Background *StarGiftBackground
|
||||
}
|
||||
|
||||
// StarGiftCatalogBundleWrite atomically publishes one catalog revision and its optional
|
||||
// complete collectible pool. Collectible.GiftID is filled with the allocated local gift ID.
|
||||
type StarGiftCatalogBundleWrite struct {
|
||||
Catalog StarGiftCatalogWrite
|
||||
Collectible *StarGiftCollectibleWrite
|
||||
}
|
||||
|
||||
type StarGiftCatalogBundleResult struct {
|
||||
Catalog StarGiftCatalogEntry
|
||||
Collectible *StarGiftCollectibleRevision
|
||||
}
|
||||
|
||||
// StarGiftCatalogEntry 是管理后台目录视图。
|
||||
|
|
@ -255,20 +777,24 @@ type StarGiftCatalogEntry struct {
|
|||
}
|
||||
|
||||
// SavedStarGiftRef 是 payments.getSavedStarGift/saveStarGift/convertStarGift 的协议中立引用。
|
||||
// 用户礼物使用 inputSavedStarGiftUser.msg_id;频道礼物使用 inputSavedStarGiftChat.peer + saved_id。
|
||||
// 用户礼物使用 inputSavedStarGiftUser.msg_id;频道礼物使用 inputSavedStarGiftChat.peer + saved_id;
|
||||
// 已升级的唯一礼物也可使用官方 inputSavedStarGiftSlug.slug。三种身份必须互斥。
|
||||
type SavedStarGiftRef struct {
|
||||
Owner Peer
|
||||
MsgID int
|
||||
SavedID int64
|
||||
Slug string
|
||||
}
|
||||
|
||||
// Valid reports whether the reference has the identity required by its owner kind.
|
||||
func (r SavedStarGiftRef) Valid() bool {
|
||||
slug := strings.TrimSpace(r.Slug)
|
||||
validSlug := slug != "" && slug == r.Slug && len(slug) <= MaxStarGiftSlugBytes && r.MsgID == 0 && r.SavedID == 0
|
||||
switch r.Owner.Type {
|
||||
case PeerTypeUser:
|
||||
return r.Owner.ID != 0 && r.MsgID > 0
|
||||
return r.Owner.ID != 0 && (validSlug || r.MsgID > 0 && r.SavedID == 0 && slug == "")
|
||||
case PeerTypeChannel:
|
||||
return r.Owner.ID != 0 && r.SavedID > 0
|
||||
return r.Owner.ID != 0 && (validSlug || r.SavedID > 0 && r.MsgID == 0 && slug == "")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
@ -317,7 +843,8 @@ const (
|
|||
// MaxStarGiftCatalogSize 是当前普通礼物目录的有界上限。
|
||||
MaxStarGiftCatalogSize = 500
|
||||
MaxStarGiftTitleRunes = 128
|
||||
MaxStarGiftCollectibleAttributesPerKind = 256
|
||||
MaxStarGiftSlugBytes = 255
|
||||
MaxStarGiftCollectibleAttributesPerKind = 512
|
||||
MaxStarGiftCollectionTitleRunes = 12
|
||||
MaxStarGiftCollectionsPerPeer = 100
|
||||
MaxStarGiftCollectionItems = 1000
|
||||
|
|
@ -339,6 +866,18 @@ var (
|
|||
ErrStarGiftCollectibleInvalid = errors.New("stargift: invalid collectible definition")
|
||||
ErrStarGiftCollectionNotFound = errors.New("stargift: collection not found")
|
||||
ErrStarGiftCollectionsFull = errors.New("stargift: collections full")
|
||||
ErrStarGiftUnavailable = errors.New("stargift: unavailable")
|
||||
ErrStarGiftOwnerInvalid = errors.New("stargift: owner invalid")
|
||||
ErrStarGiftTransferUnavailable = errors.New("stargift: transfer unavailable")
|
||||
ErrStarGiftResaleUnavailable = errors.New("stargift: resale unavailable")
|
||||
ErrStarGiftOfferInvalid = errors.New("stargift: offer invalid")
|
||||
ErrStarGiftOfferExpired = errors.New("stargift: offer expired")
|
||||
ErrStarGiftCraftUnavailable = errors.New("stargift: craft unavailable")
|
||||
ErrStarGiftAuctionUnavailable = errors.New("stargift: auction unavailable")
|
||||
ErrStarGiftWithdrawalUnavailable = errors.New("stargift: withdrawal provider unavailable")
|
||||
ErrStarGiftFormExpired = errors.New("stargift: payment form expired")
|
||||
ErrStarGiftFormPurposeInvalid = errors.New("stargift: payment form purpose invalid")
|
||||
ErrStarGiftFormAmountMismatch = errors.New("stargift: payment form amount mismatch")
|
||||
)
|
||||
|
||||
var starGiftCollectibleSlugPrefix = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,47}$`)
|
||||
|
|
@ -351,6 +890,11 @@ func ValidateStarGiftCollectibleDraft(write StarGiftCollectibleWrite) error {
|
|||
!starGiftCollectibleSlugPrefix.MatchString(write.SlugPrefix) || strings.TrimSpace(write.CommandID) == "" {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if write.OfficialGiftID < 0 ||
|
||||
(write.OfficialGiftID == 0 && len(write.SourceManifestSHA256) != 0) ||
|
||||
(write.OfficialGiftID > 0 && len(write.SourceManifestSHA256) != 32) {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if err := validateStarGiftAttributes(write.Models, StarGiftCollectibleModel, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -380,11 +924,19 @@ func validateStarGiftAttributes(attributes []StarGiftCollectibleAttribute, kind
|
|||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
seen := make(map[string]struct{}, len(attributes))
|
||||
total := 0
|
||||
selectable := 0
|
||||
for _, attribute := range attributes {
|
||||
name := strings.TrimSpace(attribute.Name)
|
||||
if attribute.Kind != kind || name == "" || len([]rune(name)) > MaxStarGiftTitleRunes ||
|
||||
attribute.RarityPermille <= 0 || attribute.RarityPermille > 1000 {
|
||||
rarityKind := attribute.RarityKind
|
||||
if attribute.Kind != kind || name == "" || len([]rune(name)) > MaxStarGiftTitleRunes || !rarityKind.Valid() {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if rarityKind == StarGiftRarityPermille {
|
||||
if attribute.RarityPermille <= 0 || attribute.RarityPermille > 1000 || attribute.Crafted {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
selectable++
|
||||
} else if attribute.RarityPermille != 0 || !attribute.Crafted || kind != StarGiftCollectibleModel {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
key := strings.ToLower(name)
|
||||
|
|
@ -392,7 +944,6 @@ func validateStarGiftAttributes(attributes []StarGiftCollectibleAttribute, kind
|
|||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
total += attribute.RarityPermille
|
||||
switch kind {
|
||||
case StarGiftCollectibleModel, StarGiftCollectiblePattern:
|
||||
if attribute.Animation == nil || len(attribute.Animation.JSON) == 0 ||
|
||||
|
|
@ -404,7 +955,7 @@ func validateStarGiftAttributes(attributes []StarGiftCollectibleAttribute, kind
|
|||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
case StarGiftCollectibleBackdrop:
|
||||
if attribute.BackdropID <= 0 || attribute.Document != nil ||
|
||||
if attribute.BackdropID < 0 || attribute.Document != nil ||
|
||||
attribute.CenterColor < 0 || attribute.CenterColor > 0xffffff ||
|
||||
attribute.EdgeColor < 0 || attribute.EdgeColor > 0xffffff ||
|
||||
attribute.PatternColor < 0 || attribute.PatternColor > 0xffffff ||
|
||||
|
|
@ -415,7 +966,7 @@ func validateStarGiftAttributes(attributes []StarGiftCollectibleAttribute, kind
|
|||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
}
|
||||
if total != 1000 {
|
||||
if selectable == 0 {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
95
internal/domain/star_gift_collectible_test.go
Normal file
95
internal/domain/star_gift_collectible_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSavedStarGiftRefRequiresOneOfficialIdentity(t *testing.T) {
|
||||
user := Peer{Type: PeerTypeUser, ID: 42}
|
||||
channel := Peer{Type: PeerTypeChannel, ID: 84}
|
||||
tests := []struct {
|
||||
name string
|
||||
ref SavedStarGiftRef
|
||||
want bool
|
||||
}{
|
||||
{name: "user message", ref: SavedStarGiftRef{Owner: user, MsgID: 10}, want: true},
|
||||
{name: "channel saved id", ref: SavedStarGiftRef{Owner: channel, SavedID: 20}, want: true},
|
||||
{name: "user collectible slug", ref: SavedStarGiftRef{Owner: user, Slug: "official-42-1"}, want: true},
|
||||
{name: "channel collectible slug", ref: SavedStarGiftRef{Owner: channel, Slug: "official-84-1"}, want: true},
|
||||
{name: "message and slug", ref: SavedStarGiftRef{Owner: user, MsgID: 10, Slug: "official-42-1"}},
|
||||
{name: "saved id and slug", ref: SavedStarGiftRef{Owner: channel, SavedID: 20, Slug: "official-84-1"}},
|
||||
{name: "whitespace slug", ref: SavedStarGiftRef{Owner: user, Slug: " official-42-1"}},
|
||||
{name: "oversized slug", ref: SavedStarGiftRef{Owner: user, Slug: strings.Repeat("x", MaxStarGiftSlugBytes+1)}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.ref.Valid(); got != tt.want {
|
||||
t.Fatalf("Valid() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarGiftLifecycleStatusRequiresExplicitActive(t *testing.T) {
|
||||
if StarGiftLifecycleStatus("").Live() {
|
||||
t.Fatal("empty lifecycle status must not be treated as active")
|
||||
}
|
||||
if !StarGiftLifecycleActive.Live() {
|
||||
t.Fatal("active lifecycle status must be live")
|
||||
}
|
||||
}
|
||||
|
||||
func validCollectibleDraft() StarGiftCollectibleWrite {
|
||||
animation := &StarGiftAnimation{JSON: []byte(`{}`), TGS: []byte{1}, SHA256: make([]byte, sha256.Size)}
|
||||
return StarGiftCollectibleWrite{
|
||||
GiftID: 1, UpgradeStars: 25, SupplyTotal: 100, SlugPrefix: "official-1", CommandID: "test",
|
||||
Models: []StarGiftCollectibleAttribute{
|
||||
{Kind: StarGiftCollectibleModel, Name: "Regular", RarityKind: StarGiftRarityPermille, RarityPermille: 922, Animation: animation},
|
||||
{Kind: StarGiftCollectibleModel, Name: "Crafted", RarityKind: StarGiftRarityLegendary, Crafted: true, Animation: animation},
|
||||
},
|
||||
Patterns: []StarGiftCollectibleAttribute{
|
||||
{Kind: StarGiftCollectiblePattern, Name: "Pattern", RarityKind: StarGiftRarityPermille, RarityPermille: 989, Animation: animation},
|
||||
},
|
||||
Backdrops: []StarGiftCollectibleAttribute{
|
||||
{Kind: StarGiftCollectibleBackdrop, Name: "Backdrop", BackdropID: 0, RarityKind: StarGiftRarityPermille, RarityPermille: 999},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStarGiftCollectibleDraftOfficialProvenance(t *testing.T) {
|
||||
write := validCollectibleDraft()
|
||||
write.OfficialGiftID = 10
|
||||
write.SourceManifestSHA256 = make([]byte, sha256.Size)
|
||||
if err := ValidateStarGiftCollectibleDraft(write); err != nil {
|
||||
t.Fatalf("valid official draft: %v", err)
|
||||
}
|
||||
|
||||
tests := map[string]StarGiftCollectibleWrite{}
|
||||
withoutHash := write
|
||||
withoutHash.SourceManifestSHA256 = nil
|
||||
tests["official ID without hash"] = withoutHash
|
||||
withoutID := write
|
||||
withoutID.OfficialGiftID = 0
|
||||
tests["hash without official ID"] = withoutID
|
||||
negativeID := write
|
||||
negativeID.OfficialGiftID = -1
|
||||
tests["negative official ID"] = negativeID
|
||||
for name, invalid := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if err := ValidateStarGiftCollectibleDraft(invalid); !errors.Is(err, ErrStarGiftCollectibleInvalid) {
|
||||
t.Fatalf("err=%v, want ErrStarGiftCollectibleInvalid", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStarGiftCollectibleDraftRejectsImplicitRarity(t *testing.T) {
|
||||
write := validCollectibleDraft()
|
||||
write.Models[0].RarityKind = ""
|
||||
if err := ValidateStarGiftCollectibleDraft(write); !errors.Is(err, ErrStarGiftCollectibleInvalid) {
|
||||
t.Fatalf("err=%v, want ErrStarGiftCollectibleInvalid", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,13 +20,19 @@ type StarsBalance struct {
|
|||
type StarsTransactionReason string
|
||||
|
||||
const (
|
||||
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
|
||||
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
|
||||
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
|
||||
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
|
||||
StarsReasonGiftUpgrade StarsTransactionReason = "gift_upgrade" // 普通礼物升级为唯一礼物
|
||||
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
|
||||
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
|
||||
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
|
||||
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
|
||||
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
|
||||
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
|
||||
StarsReasonGiftUpgrade StarsTransactionReason = "gift_upgrade" // 普通礼物升级为唯一礼物
|
||||
StarsReasonGiftTransfer StarsTransactionReason = "gift_transfer"
|
||||
StarsReasonGiftResale StarsTransactionReason = "gift_resale"
|
||||
StarsReasonGiftOffer StarsTransactionReason = "gift_offer"
|
||||
StarsReasonGiftAuction StarsTransactionReason = "gift_auction"
|
||||
StarsReasonGiftPrepaid StarsTransactionReason = "gift_prepaid_upgrade"
|
||||
StarsReasonGiftDrop StarsTransactionReason = "gift_drop_original_details"
|
||||
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
|
||||
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
|
||||
)
|
||||
|
||||
// StarsTransaction 是一条账本流水。amount 带符号:贷记 > 0(含 refund/收取),借记 < 0。
|
||||
|
|
@ -52,6 +58,28 @@ type StarsTransactionPage struct {
|
|||
Users []User // History 中提到的对手方用户,供 tg Users 富化
|
||||
}
|
||||
|
||||
// TonTransaction is an entry in telesrv's internal nanoton ledger. It models
|
||||
// the Telegram TON-denominated gift UI without contacting a wallet, Fragment,
|
||||
// a TON node, or any blockchain service.
|
||||
type TonTransaction struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Peer Peer
|
||||
GiftID int64
|
||||
Amount int64 // signed nanoton amount
|
||||
Date int
|
||||
Reason StarsTransactionReason
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
type TonTransactionPage struct {
|
||||
Balance int64
|
||||
Transactions []TonTransaction
|
||||
NextOffset string
|
||||
Users []User
|
||||
}
|
||||
|
||||
// Stars 账本边界常量。
|
||||
const (
|
||||
// DefaultStarsStartingGrant 是惰性首读授予的起始 Stars 余额(本地测试用)。
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue