chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -2,18 +2,15 @@ package rpc
import (
"context"
"errors"
"fmt"
"time"
"github.com/gotd/td/tg"
"telesrv/internal/domain"
)
const (
maxPremiumBoostsListLimit = 100
maxPremiumBoostsOffsetBytes = 128
maxPremiumApplyBoostSlots = 16
)
func (r *Router) registerPremium(d *tg.ServerDispatcher) {
d.OnPremiumGetBoostsStatus(r.onPremiumGetBoostsStatus)
d.OnPremiumGetBoostsList(r.onPremiumGetBoostsList)
@ -23,30 +20,67 @@ func (r *Router) registerPremium(d *tg.ServerDispatcher) {
}
func (r *Router) onPremiumGetBoostsStatus(ctx context.Context, peer tg.InputPeerClass) (*tg.PremiumBoostsStatus, error) {
if _, _, err := r.premiumBoostChannelView(ctx, peer, false); err != nil {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, view, err := r.premiumBoostChannelView(ctx, peer, false)
if err != nil {
return nil, err
}
return emptyPremiumBoostsStatus(), nil
status, err := r.deps.Channels.GetPremiumBoostStatus(ctx, userID, view.Channel.ID, int(time.Now().Unix()))
if err != nil {
return nil, premiumBoostErr(err)
}
return tgPremiumBoostsStatus(view.Channel.ID, status), nil
}
func (r *Router) onPremiumGetBoostsList(ctx context.Context, req *tg.PremiumGetBoostsListRequest) (*tg.PremiumBoostsList, error) {
if req.Limit < 0 || req.Limit > maxPremiumBoostsListLimit || len(req.Offset) > maxPremiumBoostsOffsetBytes {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if req == nil {
return nil, inputRequestInvalidErr()
}
if req.Limit < 0 || req.Limit > domain.MaxPremiumBoostsListLimit || len(req.Offset) > domain.MaxPremiumBoostsOffsetBytes {
return nil, limitInvalidErr()
}
if _, _, err := r.premiumBoostChannelView(ctx, req.Peer, true); err != nil {
userID, view, err := r.premiumBoostChannelView(ctx, req.Peer, true)
if err != nil {
return nil, err
}
return emptyPremiumBoostsList(), nil
limit := req.Limit
if limit == 0 {
limit = domain.MaxPremiumBoostsListLimit
}
list, err := r.deps.Channels.ListPremiumBoosts(ctx, userID, view.Channel.ID, req.Gifts, req.Offset, limit, int(time.Now().Unix()))
if err != nil {
return nil, premiumBoostErr(err)
}
return r.tgPremiumBoostsList(ctx, userID, list), nil
}
func (r *Router) onPremiumGetMyBoosts(ctx context.Context) (*tg.PremiumMyBoosts, error) {
if _, _, err := r.currentUserID(ctx); err != nil {
return nil, internalErr()
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
return emptyPremiumMyBoosts(), nil
userID, premiumUntil, err := r.currentPremiumUntil(ctx)
if err != nil {
return nil, err
}
my, err := r.deps.Channels.GetPremiumMyBoosts(ctx, userID, int(time.Now().Unix()), premiumUntil)
if err != nil {
return nil, premiumBoostErr(err)
}
return r.tgPremiumMyBoosts(ctx, userID, my), nil
}
func (r *Router) onPremiumApplyBoost(ctx context.Context, req *tg.PremiumApplyBoostRequest) (*tg.PremiumMyBoosts, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if req == nil {
return nil, inputRequestInvalidErr()
}
slots, ok := req.GetSlots()
if !ok {
return nil, tgerr400("BOOSTS_EMPTY")
@ -54,30 +88,52 @@ func (r *Router) onPremiumApplyBoost(ctx context.Context, req *tg.PremiumApplyBo
if len(slots) == 0 {
return nil, tgerr400("SLOTS_EMPTY")
}
if len(slots) > maxPremiumApplyBoostSlots {
if len(slots) > domain.MaxPremiumBoostSlotsPerApply {
return nil, limitInvalidErr()
}
for _, slot := range slots {
if slot < 0 {
if slot != domain.DefaultPremiumBoostSlotID {
return nil, tgerr400("SLOTS_INVALID")
}
}
if _, _, err := r.premiumBoostChannelView(ctx, req.Peer, false); err != nil {
userID, view, err := r.premiumBoostChannelView(ctx, req.Peer, false)
if err != nil {
return nil, err
}
return emptyPremiumMyBoosts(), nil
_, premiumUntil, err := r.currentPremiumUntil(ctx)
if err != nil {
return nil, err
}
my, err := r.deps.Channels.ApplyPremiumBoost(ctx, userID, view.Channel.ID, slots, int(time.Now().Unix()), premiumUntil)
if err != nil {
return nil, premiumBoostErr(err)
}
return r.tgPremiumMyBoosts(ctx, userID, my), nil
}
func (r *Router) onPremiumGetUserBoosts(ctx context.Context, req *tg.PremiumGetUserBoostsRequest) (*tg.PremiumBoostsList, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if req == nil {
return nil, inputRequestInvalidErr()
}
userID, view, err := r.premiumBoostChannelView(ctx, req.Peer, true)
if err != nil {
return nil, err
}
if _, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.UserID}); err != nil {
ids, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.UserID})
if err != nil {
return nil, err
}
_ = view
return emptyPremiumBoostsList(), nil
if len(ids) != 1 || ids[0] == 0 {
return nil, userIDInvalidErr()
}
list, err := r.deps.Channels.GetPremiumUserBoosts(ctx, userID, view.Channel.ID, ids[0], int(time.Now().Unix()))
if err != nil {
return nil, premiumBoostErr(err)
}
return r.tgPremiumBoostsList(ctx, userID, list), nil
}
func (r *Router) premiumBoostChannelView(ctx context.Context, peer tg.InputPeerClass, requireAdmin bool) (int64, domain.ChannelView, error) {
@ -116,27 +172,188 @@ func premiumBoostChannelRef(peer tg.InputPeerClass) (channelInputRef, bool) {
}
}
func emptyPremiumBoostsStatus() *tg.PremiumBoostsStatus {
return &tg.PremiumBoostsStatus{
Level: 0,
CurrentLevelBoosts: 0,
Boosts: 0,
BoostURL: "",
func (r *Router) currentPremiumUntil(ctx context.Context) (int64, int, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil || userID == 0 {
return 0, 0, internalErr()
}
if r.deps.Users == nil {
return userID, 0, nil
}
u, found, err := r.deps.Users.ByID(ctx, userID, userID)
if err != nil {
return 0, 0, internalErr()
}
if !found {
return userID, 0, nil
}
return userID, u.PremiumUntil, nil
}
func emptyPremiumBoostsList() *tg.PremiumBoostsList {
return &tg.PremiumBoostsList{
Count: 0,
Boosts: []tg.Boost{},
Users: []tg.UserClass{},
func tgPremiumBoostsStatus(channelID int64, in domain.PremiumBoostStatus) *tg.PremiumBoostsStatus {
out := &tg.PremiumBoostsStatus{
MyBoost: len(in.MyBoostSlots) > 0,
Level: in.Level,
CurrentLevelBoosts: in.CurrentLevelBoosts,
Boosts: in.Boosts,
BoostURL: fmt.Sprintf("https://telesrv.net/boost?c=%d", channelID),
}
if in.GiftBoosts > 0 {
out.SetGiftBoosts(in.GiftBoosts)
}
if in.HasNextLevelBoosts {
out.SetNextLevelBoosts(in.NextLevelBoosts)
}
if in.PremiumAudienceTotal > 0 {
out.SetPremiumAudience(tg.StatsPercentValue{
Part: float64(in.PremiumAudiencePart),
Total: float64(in.PremiumAudienceTotal),
})
}
if len(in.MyBoostSlots) > 0 {
out.SetMyBoostSlots(premiumBoostSlotIDs(in.MyBoostSlots))
}
return out
}
func emptyPremiumMyBoosts() *tg.PremiumMyBoosts {
func (r *Router) tgPremiumBoostsList(ctx context.Context, viewerUserID int64, in domain.PremiumBoostList) *tg.PremiumBoostsList {
users := in.Users
if len(users) == 0 {
users = r.domainUsersForIDs(ctx, viewerUserID, premiumBoostUserIDs(in.Boosts))
}
out := &tg.PremiumBoostsList{
Count: in.Count,
Boosts: tgBoosts(in.Boosts),
Users: tgUsersForViewer(viewerUserID, users),
}
if in.NextOffset != "" {
out.SetNextOffset(in.NextOffset)
}
return out
}
func (r *Router) tgPremiumMyBoosts(ctx context.Context, viewerUserID int64, in domain.PremiumMyBoosts) *tg.PremiumMyBoosts {
users := in.Users
if len(users) == 0 {
users = r.domainUsersForIDs(ctx, viewerUserID, premiumBoostPeerUserIDs(in.Slots))
}
return &tg.PremiumMyBoosts{
MyBoosts: []tg.MyBoost{},
Chats: []tg.ChatClass{},
Users: []tg.UserClass{},
MyBoosts: tgMyBoosts(in.Slots),
Chats: tgChannels(viewerUserID, in.Channels),
Users: tgUsersForViewer(viewerUserID, users),
}
}
func tgBoosts(slots []domain.PremiumBoostSlot) []tg.Boost {
out := make([]tg.Boost, 0, len(slots))
for _, slot := range slots {
boost := tg.Boost{
ID: fmt.Sprintf("%d:%d:%d", slot.UserID, slot.Slot, slot.Date),
Date: slot.Date,
Expires: slot.Expires,
}
if slot.UserID != 0 {
boost.SetUserID(slot.UserID)
}
if slot.Gift {
boost.SetGift(true)
}
if slot.Giveaway {
boost.SetGiveaway(true)
}
if slot.Unclaimed {
boost.SetUnclaimed(true)
}
if slot.GiveawayMsgID > 0 {
boost.SetGiveawayMsgID(slot.GiveawayMsgID)
}
if slot.UsedGiftSlug != "" {
boost.SetUsedGiftSlug(slot.UsedGiftSlug)
}
if slot.Multiplier > 1 {
boost.SetMultiplier(slot.Multiplier)
}
if slot.Stars > 0 {
boost.SetStars(slot.Stars)
}
out = append(out, boost)
}
return out
}
func tgMyBoosts(slots []domain.PremiumBoostSlot) []tg.MyBoost {
out := make([]tg.MyBoost, 0, len(slots))
for _, slot := range slots {
item := tg.MyBoost{
Slot: slot.Slot,
Date: slot.Date,
Expires: slot.Expires,
}
if peer := tgPeer(slot.Peer); peer != nil {
item.SetPeer(peer)
}
if slot.CooldownUntil > 0 {
item.SetCooldownUntilDate(slot.CooldownUntil)
}
out = append(out, item)
}
return out
}
func premiumBoostUserIDs(slots []domain.PremiumBoostSlot) []int64 {
ids := make([]int64, 0, len(slots))
for _, slot := range slots {
if slot.UserID != 0 {
ids = append(ids, slot.UserID)
}
}
return ids
}
func premiumBoostSlotIDs(slots []domain.PremiumBoostSlot) []int {
ids := make([]int, 0, len(slots))
seen := make(map[int]struct{}, len(slots))
for _, slot := range slots {
if slot.Slot <= 0 {
continue
}
if _, ok := seen[slot.Slot]; ok {
continue
}
seen[slot.Slot] = struct{}{}
ids = append(ids, slot.Slot)
}
return ids
}
func premiumBoostPeerUserIDs(slots []domain.PremiumBoostSlot) []int64 {
ids := make([]int64, 0)
for _, slot := range slots {
if slot.Peer.Type == domain.PeerTypeUser && slot.Peer.ID != 0 {
ids = append(ids, slot.Peer.ID)
}
}
return ids
}
func premiumBoostErr(err error) error {
if seconds, ok := domain.PremiumBoostFloodWaitSeconds(err); ok {
return floodWaitErr(seconds)
}
switch {
case errors.Is(err, domain.ErrPremiumRequired):
return tgerr400("PREMIUM_ACCOUNT_REQUIRED")
case errors.Is(err, domain.ErrBoostNotModified):
return tgerr400("BOOST_NOT_MODIFIED")
case errors.Is(err, domain.ErrChannelAdminRequired):
return tgerr400("CHAT_ADMIN_REQUIRED")
case errors.Is(err, domain.ErrChannelInvalid):
return tgerr400("CHANNEL_INVALID")
case errors.Is(err, domain.ErrChannelPrivate):
return tgerr400("CHANNEL_PRIVATE")
case errors.Is(err, domain.ErrChannelUserBanned):
return tgerr400("USER_BANNED_IN_CHANNEL")
default:
return internalErr()
}
}