Skip to content

Commit a5402d1

Browse files
ernadoclaude
andcommitted
feat(games): implement SendGame, SetGameScore, GetGameHighScores
Add Game and GameHighScore types, SendGame (InputMediaGame with the bot's own short name), SetGameScore (force / disable-edit options) and GetGameHighScores. Drop the games family from the conformance deferred list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 823be67 commit a5402d1

2 files changed

Lines changed: 133 additions & 3 deletions

File tree

conformance_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ var deferredMethods = map[string]string{
2525
"answerShippingQuery": "payments — deferred until payment updates land",
2626
"sendInvoice": "payments — deferred",
2727
"setPassportDataErrors": "Telegram Passport — deferred",
28-
"sendGame": "games — deferred",
29-
"setGameScore": "games — deferred",
30-
"getGameHighScores": "games — deferred",
3128
}
3229

3330
// notApplicableMethods lists published methods that do not apply to the

games.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/telegram/message"
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
// Game represents a game. Use BotFather to set up a game for your bot.
11+
type Game struct {
12+
Title string `json:"title"`
13+
Description string `json:"description"`
14+
Photo []PhotoSize `json:"photo"`
15+
Text string `json:"text,omitempty"`
16+
TextEntities []MessageEntity `json:"text_entities,omitempty"`
17+
Animation *Animation `json:"animation,omitempty"`
18+
}
19+
20+
// GameHighScore represents one row of a game's high-score table.
21+
type GameHighScore struct {
22+
Position int `json:"position"`
23+
User User `json:"user"`
24+
Score int `json:"score"`
25+
}
26+
27+
// SendGame sends a game identified by its short name (configured via BotFather).
28+
func (b *Bot) SendGame(ctx context.Context, chat ChatID, gameShortName string, opts ...SendOption) (*Message, error) {
29+
var cfg sendConfig
30+
for _, o := range opts {
31+
o(&cfg)
32+
}
33+
34+
peer, err := b.resolveInputPeer(ctx, chat)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
media := message.Media(&tg.InputMediaGame{
40+
ID: &tg.InputGameShortName{BotID: &tg.InputUserSelf{}, ShortName: gameShortName},
41+
})
42+
43+
builder := &b.sender.To(peer).Builder
44+
builder, err = b.applySendConfig(builder, cfg)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
resp, err := builder.Media(ctx, media)
50+
return b.sentMessage(ctx, peer, resp, err)
51+
}
52+
53+
// SetGameScoreOption configures a SetGameScore call.
54+
type SetGameScoreOption func(*gameScoreConfig)
55+
56+
type gameScoreConfig struct {
57+
force bool
58+
disableEditMessage bool
59+
}
60+
61+
// WithForceScore updates the score even if it is lower than the user's current
62+
// best.
63+
func WithForceScore() SetGameScoreOption {
64+
return func(c *gameScoreConfig) { c.force = true }
65+
}
66+
67+
// WithoutEditMessage leaves the game message unchanged instead of updating it
68+
// with the new score.
69+
func WithoutEditMessage() SetGameScoreOption {
70+
return func(c *gameScoreConfig) { c.disableEditMessage = true }
71+
}
72+
73+
// SetGameScore sets a user's score in the game contained in the given message.
74+
func (b *Bot) SetGameScore(
75+
ctx context.Context, chat ChatID, messageID int, userID int64, score int, opts ...SetGameScoreOption,
76+
) (*Message, error) {
77+
var cfg gameScoreConfig
78+
for _, o := range opts {
79+
o(&cfg)
80+
}
81+
82+
peer, err := b.resolveInputPeer(ctx, chat)
83+
if err != nil {
84+
return nil, err
85+
}
86+
user, err := b.resolveInputUser(ctx, userID)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
resp, err := b.raw.MessagesSetGameScore(ctx, &tg.MessagesSetGameScoreRequest{
92+
EditMessage: !cfg.disableEditMessage,
93+
Force: cfg.force,
94+
Peer: peer,
95+
ID: messageID,
96+
UserID: user,
97+
Score: score,
98+
})
99+
return b.sentMessage(ctx, peer, resp, err)
100+
}
101+
102+
// GetGameHighScores returns the high scores of the game in the given message for
103+
// the user and their close neighbors.
104+
func (b *Bot) GetGameHighScores(ctx context.Context, chat ChatID, messageID int, userID int64) ([]GameHighScore, error) {
105+
peer, err := b.resolveInputPeer(ctx, chat)
106+
if err != nil {
107+
return nil, err
108+
}
109+
user, err := b.resolveInputUser(ctx, userID)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
res, err := b.raw.MessagesGetGameHighScores(ctx, &tg.MessagesGetGameHighScoresRequest{
115+
Peer: peer,
116+
ID: messageID,
117+
UserID: user,
118+
})
119+
if err != nil {
120+
return nil, asAPIError(err)
121+
}
122+
123+
users := usersByID(res.Users)
124+
out := make([]GameHighScore, 0, len(res.Scores))
125+
for _, s := range res.Scores {
126+
hs := GameHighScore{Position: s.Pos, Score: s.Score, User: User{ID: s.UserID}}
127+
if u, ok := users[s.UserID]; ok {
128+
hs.User = userFromTgUser(u)
129+
}
130+
out = append(out, hs)
131+
}
132+
return out, nil
133+
}

0 commit comments

Comments
 (0)