359 lines
9.9 KiB
Go
359 lines
9.9 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gotd/td/tg"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func (r *Router) registerPremium(d *tg.ServerDispatcher) {
|
|
d.OnPremiumGetBoostsStatus(r.onPremiumGetBoostsStatus)
|
|
d.OnPremiumGetBoostsList(r.onPremiumGetBoostsList)
|
|
d.OnPremiumGetMyBoosts(r.onPremiumGetMyBoosts)
|
|
d.OnPremiumApplyBoost(r.onPremiumApplyBoost)
|
|
d.OnPremiumGetUserBoosts(r.onPremiumGetUserBoosts)
|
|
}
|
|
|
|
func (r *Router) onPremiumGetBoostsStatus(ctx context.Context, peer tg.InputPeerClass) (*tg.PremiumBoostsStatus, error) {
|
|
if r.deps.Channels == nil {
|
|
return nil, notImplementedErr()
|
|
}
|
|
userID, view, err := r.premiumBoostChannelView(ctx, peer, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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 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()
|
|
}
|
|
userID, view, err := r.premiumBoostChannelView(ctx, req.Peer, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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 r.deps.Channels == nil {
|
|
return nil, notImplementedErr()
|
|
}
|
|
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")
|
|
}
|
|
if len(slots) == 0 {
|
|
return nil, tgerr400("SLOTS_EMPTY")
|
|
}
|
|
if len(slots) > domain.MaxPremiumBoostSlotsPerApply {
|
|
return nil, limitInvalidErr()
|
|
}
|
|
for _, slot := range slots {
|
|
if slot != domain.DefaultPremiumBoostSlotID {
|
|
return nil, tgerr400("SLOTS_INVALID")
|
|
}
|
|
}
|
|
userID, view, err := r.premiumBoostChannelView(ctx, req.Peer, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, 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
|
|
}
|
|
ids, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.UserID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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) {
|
|
ref, ok := premiumBoostChannelRef(peer)
|
|
if !ok {
|
|
return 0, domain.ChannelView{}, peerIDInvalidErr()
|
|
}
|
|
input := &tg.InputChannel{ChannelID: ref.ID}
|
|
if ref.CheckAccessHash {
|
|
input.AccessHash = ref.AccessHash
|
|
}
|
|
userID, view, err := r.channelView(ctx, input)
|
|
if err != nil {
|
|
return 0, domain.ChannelView{}, err
|
|
}
|
|
if requireAdmin && view.Self.Role != domain.ChannelRoleCreator && view.Self.Role != domain.ChannelRoleAdmin {
|
|
return 0, domain.ChannelView{}, tgerr400("CHAT_ADMIN_REQUIRED")
|
|
}
|
|
return userID, view, nil
|
|
}
|
|
|
|
func premiumBoostChannelRef(peer tg.InputPeerClass) (channelInputRef, bool) {
|
|
switch p := peer.(type) {
|
|
case *tg.InputPeerChannel:
|
|
return channelInputRef{
|
|
ID: p.ChannelID,
|
|
AccessHash: p.AccessHash,
|
|
CheckAccessHash: p.AccessHash != 0,
|
|
}, p.ChannelID > 0
|
|
case *tg.InputPeerChannelFromMessage:
|
|
return channelInputRef{ID: p.ChannelID}, p.ChannelID > 0
|
|
case *tg.InputPeerChat:
|
|
return channelInputRef{ID: p.ChatID}, p.ChatID > 0
|
|
default:
|
|
return channelInputRef{}, false
|
|
}
|
|
}
|
|
|
|
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 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 (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: 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()
|
|
}
|
|
}
|