feat: sync collectible star gifts
This commit is contained in:
parent
47fcf0ea41
commit
5ecf4e912d
64 changed files with 7559 additions and 403 deletions
|
|
@ -561,6 +561,10 @@ const (
|
|||
// MessageServiceActionStarGift 映射 messageActionStarGift:收到一份 Star 礼物。
|
||||
// 礼物快照(贴纸/星价)内嵌在 action 里,收礼人无需额外拉取即可渲染气泡。
|
||||
MessageServiceActionStarGift MessageServiceActionKind = "star_gift"
|
||||
// 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"
|
||||
)
|
||||
|
||||
// MessagePhoneCallAction 是 messageActionPhoneCall 的协议中立载荷。
|
||||
|
|
@ -617,24 +621,42 @@ type MessageServiceAction struct {
|
|||
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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// MessageMedia 是一条消息媒体载荷的业务表示(落库为消息行上的 JSONB 快照)。
|
||||
|
|
|
|||
|
|
@ -246,19 +246,23 @@ type MessageFilter struct {
|
|||
|
||||
// SendPrivateTextRequest 是私聊文本/媒体发送命令。
|
||||
type SendPrivateTextRequest struct {
|
||||
SenderUserID int64
|
||||
RecipientUserID int64
|
||||
RandomID int64
|
||||
Message string
|
||||
Entities []MessageEntity
|
||||
Media *MessageMedia
|
||||
Silent bool
|
||||
NoForwards bool
|
||||
ReplyTo *MessageReply
|
||||
Forward *MessageForward
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
SenderUserID int64
|
||||
RecipientUserID int64
|
||||
RandomID int64
|
||||
Message string
|
||||
Entities []MessageEntity
|
||||
Media *MessageMedia
|
||||
Silent bool
|
||||
NoForwards bool
|
||||
ReplyTo *MessageReply
|
||||
Forward *MessageForward
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
// OriginUserID identifies the authenticated initiator when a server-generated
|
||||
// service message is authored by another user. Zero preserves the ordinary
|
||||
// send path where the sender is the initiator.
|
||||
OriginUserID int64
|
||||
RecipientBlocked bool
|
||||
// IdempotencyFingerprint 是调用边界对原始、不可变发送请求计算的 SHA-256。
|
||||
// RPC 层应优先填入原始 TL 请求指纹,避免链接预览、骰子结果、上传媒体
|
||||
|
|
|
|||
|
|
@ -3,36 +3,255 @@ package domain
|
|||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Star gift(payments.sendStarsForm + inputInvoiceStarGift)领域模型。目录是从已 seed 的
|
||||
// animated_emoji 合成的静态集合(StarGift);peer 收到的礼物实例落 peer_star_gifts(SavedStarGift)。
|
||||
// Star gift(payments.sendStarsForm + inputInvoiceStarGift)领域模型。目录和不可变版本
|
||||
// 持久化在 star_gift_catalog(_revisions);peer 收到的礼物实例落 peer_star_gifts。
|
||||
// 与 Stars 账本配合:发礼 Debit、转换回 Stars 时 Credit。
|
||||
|
||||
// StarGift 是一个可购买礼物目录项(合成、非用户持有)。
|
||||
// StarGift 是一个可购买礼物目录项。RevisionID 标识不可变的标题/价格/动画快照。
|
||||
type StarGift struct {
|
||||
ID int64
|
||||
Stars int64 // 购买价(Stars)
|
||||
ConvertStars int64 // 收礼人可转换回的 Stars(v1 = Stars,全额)
|
||||
Title string // 可选标题
|
||||
Sticker Document // 礼物贴纸快照(tg 投影必须是带 sticker 属性的有效 Document,否则客户端丢弃)
|
||||
ID int64
|
||||
RevisionID int64
|
||||
Stars int64 // 购买价(Stars)
|
||||
ConvertStars int64 // 收礼人可转换回的 Stars
|
||||
UpgradeStars int64 // 升级为唯一礼物所需 Stars;0 表示当前不可升级
|
||||
UpgradeTotal int // 当前已发布属性池允许发行的唯一礼物总量
|
||||
UpgradeIssued int // 当前已发行数量
|
||||
Title string // 可选标题
|
||||
Sticker Document // 礼物贴纸快照(tg 投影必须是带 sticker 属性的有效 Document,否则客户端丢弃)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// StarGiftCollectibleAttributeKind 是唯一礼物三个必选属性槽位。
|
||||
type StarGiftCollectibleAttributeKind string
|
||||
|
||||
const (
|
||||
StarGiftCollectibleModel StarGiftCollectibleAttributeKind = "model"
|
||||
StarGiftCollectiblePattern StarGiftCollectibleAttributeKind = "pattern"
|
||||
StarGiftCollectibleBackdrop StarGiftCollectibleAttributeKind = "backdrop"
|
||||
)
|
||||
|
||||
// StarGiftCollectibleAttribute 是已发布属性池的一项。RarityPermille 同时是客户端展示的
|
||||
// 精确稀有度和升级抽取概率;同一 revision、同一 kind 的总和必须恰好为 1000。
|
||||
type StarGiftCollectibleAttribute struct {
|
||||
ID int64
|
||||
CollectibleRevisionID int64
|
||||
Kind StarGiftCollectibleAttributeKind
|
||||
Name string
|
||||
Document *Document
|
||||
BackdropID int
|
||||
CenterColor int
|
||||
EdgeColor int
|
||||
PatternColor int
|
||||
TextColor int
|
||||
RarityPermille int
|
||||
SortOrder int
|
||||
Animation *StarGiftAnimation
|
||||
Blob *FileBlob
|
||||
}
|
||||
|
||||
// StarGiftCollectibleRevision 是某普通礼物的一份不可变、可发布属性池。
|
||||
type StarGiftCollectibleRevision struct {
|
||||
ID int64
|
||||
Owner Peer // 收礼 peer(user/channel)
|
||||
FromUserID int64 // 送礼人(匿名也保留真实值供账本,下发时按 NameHidden 决定是否暴露)
|
||||
GiftID int64 // → StarGift.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
|
||||
Message string // 附言(可选)
|
||||
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
|
||||
}
|
||||
|
||||
// StarGiftCollectibleWrite 是后台创建/发布属性池的协议无关输入。
|
||||
type StarGiftCollectibleWrite struct {
|
||||
GiftID int64
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
SlugPrefix string
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
Actor string
|
||||
CommandID string
|
||||
}
|
||||
|
||||
// UniqueStarGift 是一份已经发行的唯一礼物。属性、编号与 slug 一经创建永久不变。
|
||||
type UniqueStarGift struct {
|
||||
ID int64
|
||||
GiftID int64
|
||||
CollectibleRevisionID int64
|
||||
SourceSavedGiftID int64
|
||||
Title string
|
||||
Slug string
|
||||
Num int
|
||||
Owner Peer
|
||||
Model StarGiftCollectibleAttribute
|
||||
Pattern StarGiftCollectibleAttribute
|
||||
Backdrop StarGiftCollectibleAttribute
|
||||
AvailabilityIssued int
|
||||
AvailabilityTotal int
|
||||
KeepOriginalDetails bool
|
||||
OriginalFromUserID int64
|
||||
OriginalOwner Peer
|
||||
OriginalDate int
|
||||
OriginalMessage string
|
||||
OriginalNameHidden bool
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// StarGiftUpgradePreview 是客户端升级弹窗所需的当前价格和属性样例。
|
||||
type StarGiftUpgradePreview struct {
|
||||
GiftID int64
|
||||
Revision int
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
Issued int
|
||||
SlugPrefix string
|
||||
Models []StarGiftCollectibleAttribute
|
||||
Patterns []StarGiftCollectibleAttribute
|
||||
Backdrops []StarGiftCollectibleAttribute
|
||||
}
|
||||
|
||||
// StarGiftCollectibleAvailability is the lightweight current-pool projection used
|
||||
// when rendering historical saved gifts. The saved gift keeps its immutable catalog
|
||||
// revision for appearance and prices, while upgrade availability follows the pool
|
||||
// currently published for the logical gift ID.
|
||||
type StarGiftCollectibleAvailability struct {
|
||||
UpgradeStars int64
|
||||
SupplyTotal int
|
||||
Issued int
|
||||
}
|
||||
|
||||
// StarGiftUpgradeRequest is one idempotent user-owned upgrade command. Paid
|
||||
// invoice upgrades set ChargeStars; the direct payments.upgradeStarGift path
|
||||
// sets RequirePrepaid and charges zero at upgrade time.
|
||||
type StarGiftUpgradeRequest struct {
|
||||
UserID int64
|
||||
Ref SavedStarGiftRef
|
||||
KeepOriginalDetails bool
|
||||
ChargeStars int64
|
||||
RequirePrepaid bool
|
||||
FormID int64
|
||||
CommandKey string
|
||||
Date int
|
||||
OriginAuthKeyID [8]byte
|
||||
OriginSessionID int64
|
||||
}
|
||||
|
||||
type StarGiftUpgradeResult struct {
|
||||
Saved SavedStarGift
|
||||
Unique UniqueStarGift
|
||||
Balance StarsBalance
|
||||
Send SendPrivateTextResult
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
// StarGiftCollection 是 peer 资料页中的礼物集合;一份礼物可属于多个集合。
|
||||
type StarGiftCollection struct {
|
||||
Owner Peer
|
||||
CollectionID int
|
||||
Title string
|
||||
GiftIDs []int64 // peer_star_gifts.id,按集合内顺序
|
||||
Hash int64
|
||||
SortOrder int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// StarGiftCollectionPatch 描述 updateStarGiftCollection 的局部更新。
|
||||
type StarGiftCollectionPatch struct {
|
||||
Title *string
|
||||
DeleteIDs []int64
|
||||
AddIDs []int64
|
||||
Order []int64
|
||||
}
|
||||
|
||||
// StarGiftAnimationFormat 是后台导入源格式。服务端最终总是存储规范化 TGS。
|
||||
type StarGiftAnimationFormat string
|
||||
|
||||
const (
|
||||
StarGiftAnimationTGS StarGiftAnimationFormat = "tgs"
|
||||
StarGiftAnimationLottie StarGiftAnimationFormat = "lottie"
|
||||
)
|
||||
|
||||
// StarGiftAnimation 是已规范化并验证的动画。JSON 用于后台播放,TGS 用于客户端。
|
||||
type StarGiftAnimation struct {
|
||||
SourceName string
|
||||
SourceFormat StarGiftAnimationFormat
|
||||
JSON []byte
|
||||
TGS []byte
|
||||
SHA256 []byte
|
||||
Width int
|
||||
Height int
|
||||
FrameRate float64
|
||||
InPoint float64
|
||||
OutPoint float64
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// StarGiftCatalogEntry 是管理后台目录视图。
|
||||
type StarGiftCatalogEntry struct {
|
||||
Gift StarGift
|
||||
Enabled bool
|
||||
SortOrder int
|
||||
Revision int
|
||||
SourceName string
|
||||
SourceFormat StarGiftAnimationFormat
|
||||
AnimationSHA []byte
|
||||
AnimationSize int64
|
||||
Width int
|
||||
Height int
|
||||
FrameRate float64
|
||||
ReceivedCount int64
|
||||
CreatedBy string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// SavedStarGiftRef 是 payments.getSavedStarGift/saveStarGift/convertStarGift 的协议中立引用。
|
||||
|
|
@ -62,6 +281,24 @@ type SavedStarGiftPage struct {
|
|||
Count int // 总数(未转换、按 excludeUnsaved 过滤后)
|
||||
}
|
||||
|
||||
// SavedStarGiftFilter describes the client-visible filters supported by
|
||||
// payments.getSavedStarGifts. CollectionID is the collection membership filter;
|
||||
// zero means all collections. The current catalog is used only to decide whether
|
||||
// a regular gift remains upgradable, while its rendered gift snapshot still comes
|
||||
// from RevisionID.
|
||||
type SavedStarGiftFilter struct {
|
||||
Owner Peer
|
||||
ExcludeUnsaved bool
|
||||
ExcludeSaved bool
|
||||
ExcludeUnlimited bool
|
||||
ExcludeUnique bool
|
||||
ExcludeUpgradable bool
|
||||
ExcludeUnupgradable bool
|
||||
CollectionID int
|
||||
Offset string
|
||||
Limit int
|
||||
}
|
||||
|
||||
// Star gift 边界常量。
|
||||
const (
|
||||
// MaxSavedStarGiftsLimit 是 getSavedStarGifts 单页上限。
|
||||
|
|
@ -70,6 +307,20 @@ const (
|
|||
MaxStarGiftMessageRunes = 255
|
||||
// MaxStarGiftsOffsetBytes 是 keyset 游标字符串长度上限。
|
||||
MaxStarGiftsOffsetBytes = 64
|
||||
// MaxStarGiftTGSBytes 限制后台导入的压缩动画,避免管理面上传成为容量旁路。
|
||||
MaxStarGiftTGSBytes int64 = 512 << 10
|
||||
// MaxStarGiftLottieBytes 限制解压后的 Lottie JSON。
|
||||
MaxStarGiftLottieBytes int64 = 4 << 20
|
||||
// MaxStarGiftAnimationFrameRate / Seconds 限制管理后台播放器和客户端动画时间轴。
|
||||
MaxStarGiftAnimationFrameRate = 120
|
||||
MaxStarGiftAnimationSeconds = 30
|
||||
// MaxStarGiftCatalogSize 是当前普通礼物目录的有界上限。
|
||||
MaxStarGiftCatalogSize = 500
|
||||
MaxStarGiftTitleRunes = 128
|
||||
MaxStarGiftCollectibleAttributesPerKind = 256
|
||||
MaxStarGiftCollectionTitleRunes = 12
|
||||
MaxStarGiftCollectionsPerPeer = 100
|
||||
MaxStarGiftCollectionItems = 1000
|
||||
)
|
||||
|
||||
// Star gift 哨兵错误(rpc 层 errors.Is 映射为 tgerr)。
|
||||
|
|
@ -79,20 +330,138 @@ var (
|
|||
// ErrStarGiftNotFound 表示找不到该已收到礼物实例。
|
||||
ErrStarGiftNotFound = errors.New("stargift: saved gift not found")
|
||||
// ErrStarGiftAlreadyConverted 表示礼物已转换回 Stars(不可重复转换)。
|
||||
ErrStarGiftAlreadyConverted = errors.New("stargift: already converted")
|
||||
ErrStarGiftAlreadyConverted = errors.New("stargift: already converted")
|
||||
ErrStarGiftFileInvalid = errors.New("stargift: invalid animation file")
|
||||
ErrStarGiftCatalogFull = errors.New("stargift: catalog full")
|
||||
ErrStarGiftCollectibleUnavailable = errors.New("stargift: collectible upgrade unavailable")
|
||||
ErrStarGiftAlreadyUpgraded = errors.New("stargift: already upgraded")
|
||||
ErrStarGiftCollectibleSoldOut = errors.New("stargift: collectible supply exhausted")
|
||||
ErrStarGiftCollectibleInvalid = errors.New("stargift: invalid collectible definition")
|
||||
ErrStarGiftCollectionNotFound = errors.New("stargift: collection not found")
|
||||
ErrStarGiftCollectionsFull = errors.New("stargift: collections full")
|
||||
)
|
||||
|
||||
// StarGiftCatalogHash 由目录的 (gift_id, stars) 折叠出稳定 hash,供 getStarGifts NotModified。
|
||||
var starGiftCollectibleSlugPrefix = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,47}$`)
|
||||
|
||||
// ValidateStarGiftCollectibleDraft validates the operator-authored definition before animation
|
||||
// blobs/documents are allocated. This is the validation boundary used by admin dry-runs.
|
||||
func ValidateStarGiftCollectibleDraft(write StarGiftCollectibleWrite) error {
|
||||
write.SlugPrefix = strings.TrimSpace(strings.ToLower(write.SlugPrefix))
|
||||
if write.GiftID <= 0 || write.UpgradeStars <= 0 || write.SupplyTotal <= 0 ||
|
||||
!starGiftCollectibleSlugPrefix.MatchString(write.SlugPrefix) || strings.TrimSpace(write.CommandID) == "" {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if err := validateStarGiftAttributes(write.Models, StarGiftCollectibleModel, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateStarGiftAttributes(write.Patterns, StarGiftCollectiblePattern, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateStarGiftAttributes(write.Backdrops, StarGiftCollectibleBackdrop, false)
|
||||
}
|
||||
|
||||
// ValidateStarGiftCollectibleWrite validates a complete publish command. Published pools are
|
||||
// immutable, so partial definitions are rejected before any document/blob rows are written.
|
||||
func ValidateStarGiftCollectibleWrite(write StarGiftCollectibleWrite) error {
|
||||
if err := ValidateStarGiftCollectibleDraft(write); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateStarGiftAttributes(write.Models, StarGiftCollectibleModel, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateStarGiftAttributes(write.Patterns, StarGiftCollectiblePattern, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateStarGiftAttributes(write.Backdrops, StarGiftCollectibleBackdrop, true)
|
||||
}
|
||||
|
||||
func validateStarGiftAttributes(attributes []StarGiftCollectibleAttribute, kind StarGiftCollectibleAttributeKind, requireStoredAsset bool) error {
|
||||
if len(attributes) == 0 || len(attributes) > MaxStarGiftCollectibleAttributesPerKind {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
seen := make(map[string]struct{}, len(attributes))
|
||||
total := 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 {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
key := strings.ToLower(name)
|
||||
if _, ok := seen[key]; ok {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
total += attribute.RarityPermille
|
||||
switch kind {
|
||||
case StarGiftCollectibleModel, StarGiftCollectiblePattern:
|
||||
if attribute.Animation == nil || len(attribute.Animation.JSON) == 0 ||
|
||||
len(attribute.Animation.TGS) == 0 || len(attribute.Animation.SHA256) != 32 {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if requireStoredAsset && (attribute.Document == nil || !attribute.Document.IsSticker() ||
|
||||
attribute.Document.MimeType != "application/x-tgsticker" || attribute.Blob == nil) {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
case StarGiftCollectibleBackdrop:
|
||||
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 ||
|
||||
attribute.TextColor < 0 || attribute.TextColor > 0xffffff {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
default:
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
}
|
||||
if total != 1000 {
|
||||
return ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StarGiftCatalogHash 由客户端可见目录字段折叠出稳定 hash,供 getStarGifts NotModified。
|
||||
func StarGiftCatalogHash(catalog []StarGift) int {
|
||||
var h uint64
|
||||
for _, g := range catalog {
|
||||
h ^= uint64(g.ID)
|
||||
h = h*0x4f25 + uint64(g.ID)
|
||||
h = h*0x4f25 + uint64(g.RevisionID)
|
||||
h = h*0x4f25 + uint64(g.Stars)
|
||||
h = h*0x4f25 + uint64(g.ConvertStars)
|
||||
h = h*0x4f25 + uint64(g.UpgradeStars)
|
||||
h = h*0x4f25 + uint64(g.UpgradeTotal)
|
||||
h = h*0x4f25 + uint64(g.UpgradeIssued)
|
||||
h = h*0x4f25 + uint64(g.Sticker.ID)
|
||||
for _, r := range g.Title {
|
||||
h = h*131 + uint64(r)
|
||||
}
|
||||
}
|
||||
return int(h & 0x7fffffff)
|
||||
}
|
||||
|
||||
// StarGiftCollectionsHash 按服务端返回顺序折叠每个集合自己的稳定 hash。
|
||||
func StarGiftCollectionsHash(collections []StarGiftCollection) int64 {
|
||||
var h uint64
|
||||
for _, collection := range collections {
|
||||
h = h*0x4f25 + uint64(collection.Hash)
|
||||
}
|
||||
return int64(h & 0x7fffffffffffffff)
|
||||
}
|
||||
|
||||
// StarGiftCollectionHash returns the per-collection hash exposed by starGiftCollection.hash.
|
||||
func StarGiftCollectionHash(title string, giftIDs []int64) int64 {
|
||||
h := uint64(0x534743)
|
||||
for _, r := range title {
|
||||
h = h*131 + uint64(r)
|
||||
}
|
||||
for _, id := range giftIDs {
|
||||
h = h*0x4f25 + uint64(id)
|
||||
}
|
||||
return int64(h & 0x7fffffffffffffff)
|
||||
}
|
||||
|
||||
// EncodeStarGiftCursor / DecodeStarGiftCursor 是 saved gifts keyset 游标(最后一条实例 id)。
|
||||
func EncodeStarGiftCursor(id int64) string {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(strconv.FormatInt(id, 10)))
|
||||
|
|
|
|||
|
|
@ -20,21 +20,22 @@ type StarsBalance struct {
|
|||
type StarsTransactionReason string
|
||||
|
||||
const (
|
||||
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
|
||||
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
|
||||
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
|
||||
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
|
||||
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
|
||||
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
|
||||
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
|
||||
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
|
||||
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
|
||||
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
|
||||
StarsReasonGiftUpgrade StarsTransactionReason = "gift_upgrade" // 普通礼物升级为唯一礼物
|
||||
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
|
||||
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
|
||||
)
|
||||
|
||||
// StarsTransaction 是一条账本流水。amount 带符号:贷记 > 0(含 refund/收取),借记 < 0。
|
||||
type StarsTransaction struct {
|
||||
ID int64 // 单调递增账本 id(keyset 游标)
|
||||
UserID int64 // 账本归属
|
||||
Peer Peer // 对手方(grant/topup 等无对手时为零 Peer)
|
||||
Amount int64 // 带符号金额
|
||||
Date int // Unix 秒
|
||||
ID int64 // 单调递增账本 id(keyset 游标)
|
||||
UserID int64 // 账本归属
|
||||
Peer Peer // 对手方(grant/topup 等无对手时为零 Peer)
|
||||
Amount int64 // 带符号金额
|
||||
Date int // Unix 秒
|
||||
Reason StarsTransactionReason
|
||||
Title string // 可选,投影到 tg.StarsTransaction.Title
|
||||
Description string // 可选,投影到 tg.StarsTransaction.Description
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue