Skip to content

Commit 0d7c11c

Browse files
ernadoclaude
andcommitted
feat: add GetUserChatBoosts
GetUserChatBoosts (premium.getUserBoosts) with ChatBoost and the ChatBoostSource union (premium / gift_code / giveaway). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 405bacf commit 0d7c11c

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

boosts.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/tg"
7+
)
8+
9+
// ChatBoostSource is a sealed union describing how a chat boost was obtained.
10+
//
11+
// Concrete variants: ChatBoostSourcePremium, ChatBoostSourceGiftCode,
12+
// ChatBoostSourceGiveaway.
13+
type ChatBoostSource interface {
14+
isChatBoostSource()
15+
}
16+
17+
// ChatBoostSourcePremium is a boost from a user subscribing to Telegram Premium
18+
// or gifting a Premium subscription to another user.
19+
type ChatBoostSourcePremium struct {
20+
Source string `json:"source"`
21+
User User `json:"user"`
22+
}
23+
24+
// ChatBoostSourceGiftCode is a boost from a user using a gift code made by the
25+
// chat (for example as part of a giveaway).
26+
type ChatBoostSourceGiftCode struct {
27+
Source string `json:"source"`
28+
User User `json:"user"`
29+
}
30+
31+
// ChatBoostSourceGiveaway is a boost from a giveaway prize created by the chat.
32+
type ChatBoostSourceGiveaway struct {
33+
Source string `json:"source"`
34+
// GiveawayMessageID is the id of the message with the giveaway; it may be 0
35+
// if the message was deleted.
36+
GiveawayMessageID int `json:"giveaway_message_id"`
37+
// User is the user that won the prize, if any.
38+
User *User `json:"user,omitempty"`
39+
// IsUnclaimed reports whether the giveaway prize was not claimed.
40+
IsUnclaimed bool `json:"is_unclaimed,omitempty"`
41+
}
42+
43+
func (ChatBoostSourcePremium) isChatBoostSource() {}
44+
func (ChatBoostSourceGiftCode) isChatBoostSource() {}
45+
func (ChatBoostSourceGiveaway) isChatBoostSource() {}
46+
47+
// Chat boost source discriminators.
48+
const (
49+
chatBoostSourcePremium = "premium"
50+
chatBoostSourceGiftCode = "gift_code"
51+
chatBoostSourceGiveaway = "giveaway"
52+
)
53+
54+
// ChatBoost describes a boost applied to a chat.
55+
type ChatBoost struct {
56+
// BoostID is the unique identifier of the boost.
57+
BoostID string `json:"boost_id"`
58+
// AddDate is the Unix time when the chat was boosted.
59+
AddDate int `json:"add_date"`
60+
// ExpirationDate is the Unix time when the boost will automatically expire.
61+
ExpirationDate int `json:"expiration_date"`
62+
// Source is the source of the added boost.
63+
Source ChatBoostSource `json:"source"`
64+
}
65+
66+
// chatBoostFromTg converts an MTProto boost into the Bot API ChatBoost. users
67+
// resolves the booster by id.
68+
func chatBoostFromTg(boost tg.Boost, users map[int64]*tg.User) ChatBoost {
69+
var booster User
70+
71+
if u, ok := users[boost.UserID]; ok {
72+
booster = userFromTgUser(u)
73+
} else {
74+
booster = User{ID: boost.UserID}
75+
}
76+
77+
var source ChatBoostSource
78+
79+
switch {
80+
case boost.Giveaway:
81+
g := ChatBoostSourceGiveaway{
82+
Source: chatBoostSourceGiveaway,
83+
GiveawayMessageID: boost.GiveawayMsgID,
84+
IsUnclaimed: boost.Unclaimed,
85+
}
86+
if boost.UserID != 0 {
87+
source = withGiveawayUser(g, booster)
88+
} else {
89+
source = g
90+
}
91+
case boost.Gift:
92+
source = ChatBoostSourceGiftCode{Source: chatBoostSourceGiftCode, User: booster}
93+
default:
94+
source = ChatBoostSourcePremium{Source: chatBoostSourcePremium, User: booster}
95+
}
96+
97+
return ChatBoost{
98+
BoostID: boost.ID,
99+
AddDate: boost.Date,
100+
ExpirationDate: boost.Expires,
101+
Source: source,
102+
}
103+
}
104+
105+
// withGiveawayUser attaches the winning user to a giveaway boost source.
106+
func withGiveawayUser(g ChatBoostSourceGiveaway, user User) ChatBoostSourceGiveaway {
107+
u := user
108+
109+
g.User = &u
110+
111+
return g
112+
}
113+
114+
// GetUserChatBoosts returns the list of boosts added to a chat by a user. The
115+
// bot must be an administrator in the chat.
116+
func (b *Bot) GetUserChatBoosts(ctx context.Context, chat ChatID, userID int64) ([]ChatBoost, error) {
117+
peer, err := b.resolveInputPeer(ctx, chat)
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
user, err := b.resolveInputUser(ctx, userID)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
res, err := b.raw.PremiumGetUserBoosts(ctx, &tg.PremiumGetUserBoostsRequest{
128+
Peer: peer,
129+
UserID: user,
130+
})
131+
if err != nil {
132+
return nil, asAPIError(err)
133+
}
134+
135+
users := usersByID(res.Users)
136+
out := make([]ChatBoost, 0, len(res.Boosts))
137+
138+
for _, boost := range res.Boosts {
139+
out = append(out, chatBoostFromTg(boost, users))
140+
}
141+
142+
return out, nil
143+
}

boosts_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
func TestGetUserChatBoosts(t *testing.T) {
11+
inv := newMockInvoker()
12+
inv.reply(tg.PremiumGetUserBoostsRequestTypeID, &tg.PremiumBoostsList{
13+
Count: 2,
14+
Boosts: []tg.Boost{
15+
{ID: "b1", UserID: 10, Date: 100, Expires: 200},
16+
{ID: "b2", UserID: 10, Date: 150, Expires: 250, Giveaway: true, GiveawayMsgID: 7, Unclaimed: true},
17+
},
18+
Users: []tg.UserClass{&tg.User{ID: 10, AccessHash: 1, FirstName: "Boo"}},
19+
})
20+
21+
boosts, err := newMockBot(inv).GetUserChatBoosts(context.Background(), tdlibChannel(50), 10)
22+
if err != nil {
23+
t.Fatalf("GetUserChatBoosts: %v", err)
24+
}
25+
26+
if len(boosts) != 2 {
27+
t.Fatalf("got %d boosts", len(boosts))
28+
}
29+
30+
premium, ok := boosts[0].Source.(ChatBoostSourcePremium)
31+
if !ok || premium.User.FirstName != "Boo" || boosts[0].BoostID != "b1" {
32+
t.Fatalf("boost[0] = %#v", boosts[0])
33+
}
34+
35+
giveaway, ok := boosts[1].Source.(ChatBoostSourceGiveaway)
36+
if !ok || giveaway.GiveawayMessageID != 7 || !giveaway.IsUnclaimed {
37+
t.Fatalf("boost[1] = %#v", boosts[1])
38+
}
39+
40+
if giveaway.User == nil || giveaway.User.ID != 10 {
41+
t.Fatalf("giveaway user = %#v", giveaway.User)
42+
}
43+
}

0 commit comments

Comments
 (0)