Ctrl K
ComunidadGuías…

internal/domain/boost.go

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

Archivo original ↗Preguntar a KiBot
package domain

import (
	"errors"
	"fmt"
	"sort"
)

const (
	// DefaultPremiumBoostSlotID is the single base boost slot every active premium
	// user owns in the current small-scale implementation.
	DefaultPremiumBoostSlotID = 1
	// MaxPremiumBoostSlotsPerApply bounds premium.applyBoost slots before storage.
	MaxPremiumBoostSlotsPerApply = 16
	// MaxPremiumBoostsListLimit bounds premium.getBoostsList pages.
	MaxPremiumBoostsListLimit = 100
	// MaxPremiumBoostsOffsetBytes bounds opaque boost pagination cursors.
	MaxPremiumBoostsOffsetBytes = 128
	// MaxChannelBoostsToUnblockRestrictions matches Layer 225 TDesktop UI bounds.
	MaxChannelBoostsToUnblockRestrictions = 8
	// MaxDefaultPremiumBoostLevel bounds the built-in level table so status replies
	// never expose unbounded next-level values to clients.
	MaxDefaultPremiumBoostLevel = 100
	// DefaultPremiumBoostReassignCooldownSeconds keeps the current dev behavior of
	// allowing immediate moves while preserving a single hook for official cooldowns.
	DefaultPremiumBoostReassignCooldownSeconds = 0
)

var (
	ErrBoostNotModified = errors.New("boost not modified")

	defaultPremiumBoostLevelPolicy = PremiumBoostLevelPolicy{
		Thresholds: linearPremiumBoostThresholds(MaxDefaultPremiumBoostLevel),
	}
)

// PremiumBoostSlot describes one user-owned boost slot without TL types.
type PremiumBoostSlot struct {
	UserID        int64
	Slot          int
	Peer          Peer
	Date          int
	Expires       int
	CooldownUntil int
	Multiplier    int
	Gift          bool
	Giveaway      bool
	Unclaimed     bool
	GiveawayMsgID int
	UsedGiftSlug  string
	Stars         int64
}

// Active reports whether the slot can currently count for a peer.
func (s PremiumBoostSlot) Active(now int) bool {
	if s.UserID == 0 || s.Slot <= 0 {
		return false
	}
	return s.Expires == 0 || s.Expires > now
}

// Assigned reports whether the active slot is currently applied to a peer.
func (s PremiumBoostSlot) Assigned(now int) bool {
	return s.Active(now) && s.Peer.ID != 0
}

// Weight returns the boost contribution of one active assigned slot.
func (s PremiumBoostSlot) Weight(now int) int {
	if !s.Assigned(now) {
		return 0
	}
	if s.Multiplier <= 0 {
		return 1
	}
	return s.Multiplier
}

// PremiumBoostStatus is the domain projection behind premium.boostsStatus.
type PremiumBoostStatus struct {
	Peer                 Peer
	Level                int
	CurrentLevelBoosts   int
	Boosts               int
	GiftBoosts           int
	NextLevelBoosts      int
	HasNextLevelBoosts   bool
	PremiumAudiencePart  int
	PremiumAudienceTotal int
	MyBoostSlots         []PremiumBoostSlot
}

// PremiumBoostList is a bounded admin-visible boosts page.
type PremiumBoostList struct {
	Count      int
	Boosts     []PremiumBoostSlot
	Users      []User
	NextOffset string
}

// PremiumMyBoosts is the current user's slot inventory.
type PremiumMyBoosts struct {
	Slots    []PremiumBoostSlot
	Users    []User
	Channels []Channel
}

// PremiumBoostLevelPolicy maps cumulative active boosts to channel boost levels.
// Thresholds[n-1] is the minimum boost count required for level n.
type PremiumBoostLevelPolicy struct {
	Thresholds []int
}

// DefaultPremiumBoostLevelPolicy returns the current bounded telesrv level table.
// It is intentionally policy-backed: production deployments can replace the table
// with official thresholds without touching store/RPC state transitions.
func DefaultPremiumBoostLevelPolicy() PremiumBoostLevelPolicy {
	return PremiumBoostLevelPolicy{
		Thresholds: append([]int(nil), defaultPremiumBoostLevelPolicy.Thresholds...),
	}
}

// NewPremiumBoostLevelPolicy creates a monotonic threshold policy from raw values.
func NewPremiumBoostLevelPolicy(thresholds []int) PremiumBoostLevelPolicy {
	return PremiumBoostLevelPolicy{Thresholds: normalizePremiumBoostThresholds(thresholds)}
}

// LevelForBoosts returns level bounds for the given active boost count.
func (p PremiumBoostLevelPolicy) LevelForBoosts(boosts int) (level, currentLevelBoosts, nextLevelBoosts int, hasNext bool) {
	if boosts < 0 {
		boosts = 0
	}
	thresholds := p.thresholds()
	if len(thresholds) == 0 {
		return 0, 0, 0, false
	}
	level = sort.Search(len(thresholds), func(i int) bool { return thresholds[i] > boosts })
	if level > 0 {
		currentLevelBoosts = thresholds[level-1]
	}
	if level < len(thresholds) {
		return level, currentLevelBoosts, thresholds[level], true
	}
	return len(thresholds), thresholds[len(thresholds)-1], 0, false
}

func (p PremiumBoostLevelPolicy) thresholds() []int {
	if len(p.Thresholds) == 0 {
		return defaultPremiumBoostLevelPolicy.Thresholds
	}
	last := 0
	for _, threshold := range p.Thresholds {
		if threshold <= 0 || threshold <= last {
			return normalizePremiumBoostThresholds(p.Thresholds)
		}
		last = threshold
	}
	return p.Thresholds
}

func normalizePremiumBoostThresholds(thresholds []int) []int {
	if len(thresholds) == 0 {
		return nil
	}
	sorted := make([]int, 0, len(thresholds))
	for _, threshold := range thresholds {
		if threshold > 0 {
			sorted = append(sorted, threshold)
		}
	}
	sort.Ints(sorted)
	out := sorted[:0]
	last := 0
	for _, threshold := range sorted {
		if threshold == last {
			continue
		}
		out = append(out, threshold)
		last = threshold
	}
	return append([]int(nil), out...)
}

func linearPremiumBoostThresholds(maxLevel int) []int {
	if maxLevel <= 0 {
		return nil
	}
	out := make([]int, maxLevel)
	for i := range out {
		out[i] = i + 1
	}
	return out
}

// PremiumBoostStatusForCount returns the bounded default level projection.
func PremiumBoostStatusForCount(peer Peer, boosts int, my []PremiumBoostSlot) PremiumBoostStatus {
	return PremiumBoostStatusForPolicy(peer, boosts, my, defaultPremiumBoostLevelPolicy)
}

// PremiumBoostStatusForPolicy returns a status projection from an explicit level policy.
func PremiumBoostStatusForPolicy(peer Peer, boosts int, my []PremiumBoostSlot, policy PremiumBoostLevelPolicy) PremiumBoostStatus {
	if boosts < 0 {
		boosts = 0
	}
	level, current, next, hasNext := policy.LevelForBoosts(boosts)
	return PremiumBoostStatus{
		Peer:               peer,
		Level:              level,
		CurrentLevelBoosts: current,
		Boosts:             boosts,
		NextLevelBoosts:    next,
		HasNextLevelBoosts: hasNext,
		MyBoostSlots:       append([]PremiumBoostSlot(nil), my...),
	}
}

// ApplyPremiumBoostSlot applies one user-owned slot to a peer and enforces shared
// lifecycle rules for all store implementations.
func ApplyPremiumBoostSlot(slot PremiumBoostSlot, userID int64, slotID int, peer Peer, now, premiumUntil, cooldownSeconds int) (PremiumBoostSlot, bool, error) {
	if userID == 0 || slotID <= 0 || peer.Type != PeerTypeChannel || peer.ID == 0 || now < 0 {
		return PremiumBoostSlot{}, false, ErrChannelInvalid
	}
	if premiumUntil <= now {
		return PremiumBoostSlot{}, false, ErrPremiumRequired
	}
	if cooldownSeconds < 0 {
		cooldownSeconds = 0
	}
	if slot.UserID == 0 || slot.Slot == 0 || !slot.Active(now) {
		slot = PremiumBoostSlot{
			UserID:     userID,
			Slot:       slotID,
			Multiplier: 1,
		}
	}
	if slot.UserID != userID || slot.Slot != slotID {
		return PremiumBoostSlot{}, false, ErrChannelInvalid
	}
	if slot.Multiplier <= 0 {
		slot.Multiplier = 1
	}
	if slot.Assigned(now) && slot.Peer == peer {
		if slot.Expires != 0 && slot.Expires != premiumUntil {
			slot.Expires = premiumUntil
			return slot, true, nil
		}
		return slot, false, ErrBoostNotModified
	}
	if slot.Assigned(now) && slot.Peer != peer && slot.CooldownUntil > now {
		return slot, false, NewPremiumBoostFloodWaitError(slot.CooldownUntil - now)
	}
	wasAssigned := slot.Assigned(now)
	slot.Peer = peer
	slot.Date = now
	slot.Expires = premiumUntil
	if wasAssigned && cooldownSeconds > 0 {
		slot.CooldownUntil = now + cooldownSeconds
	} else if slot.CooldownUntil <= now {
		slot.CooldownUntil = 0
	}
	return slot, true, nil
}

// PremiumBoostFloodWaitError carries remaining seconds for boost reassign cooldowns.
type PremiumBoostFloodWaitError struct {
	Seconds int
}

func (e PremiumBoostFloodWaitError) Error() string {
	if e.Seconds <= 0 {
		return "premium boost flood wait"
	}
	return fmt.Sprintf("premium boost flood wait %d seconds", e.Seconds)
}

func NewPremiumBoostFloodWaitError(seconds int) error {
	if seconds <= 0 {
		seconds = 1
	}
	return PremiumBoostFloodWaitError{Seconds: seconds}
}

func PremiumBoostFloodWaitSeconds(err error) (int, bool) {
	var wait PremiumBoostFloodWaitError
	if !errors.As(err, &wait) {
		return 0, false
	}
	if wait.Seconds <= 0 {
		return 1, true
	}
	return wait.Seconds, true
}