Skip to content

Commit e3dd0dc

Browse files
ernadoclaude
andcommitted
feat(chat): implement GetChat, GetUserProfilePhotos, chat sticker set, admin title
Fill deferred methods: GetChat (peer info), GetUserProfilePhotos (photos.getUserPhotos with paging), SetChatStickerSet/DeleteChatStickerSet (channels.setStickers) and SetChatAdministratorCustomTitle (preserves existing rights via getParticipant + editAdmin). Drop them from the conformance deferred list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d098b9a commit e3dd0dc

4 files changed

Lines changed: 155 additions & 16 deletions

File tree

chat_admin.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,33 @@ func (b *Bot) SetChatPermissions(ctx context.Context, chat ChatID, permissions C
138138
}
139139
return nil
140140
}
141+
142+
// SetChatStickerSet sets the group sticker set for a supergroup.
143+
func (b *Bot) SetChatStickerSet(ctx context.Context, chat ChatID, stickerSetName string) error {
144+
channel, err := b.resolveChannel(ctx, chat)
145+
if err != nil {
146+
return err
147+
}
148+
if _, err := b.raw.ChannelsSetStickers(ctx, &tg.ChannelsSetStickersRequest{
149+
Channel: channel,
150+
Stickerset: &tg.InputStickerSetShortName{ShortName: stickerSetName},
151+
}); err != nil {
152+
return asAPIError(err)
153+
}
154+
return nil
155+
}
156+
157+
// DeleteChatStickerSet removes the group sticker set from a supergroup.
158+
func (b *Bot) DeleteChatStickerSet(ctx context.Context, chat ChatID) error {
159+
channel, err := b.resolveChannel(ctx, chat)
160+
if err != nil {
161+
return err
162+
}
163+
if _, err := b.raw.ChannelsSetStickers(ctx, &tg.ChannelsSetStickersRequest{
164+
Channel: channel,
165+
Stickerset: &tg.InputStickerSetEmpty{},
166+
}); err != nil {
167+
return asAPIError(err)
168+
}
169+
return nil
170+
}

chat_info.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/tg"
7+
)
8+
9+
// GetChat returns up-to-date information about a chat.
10+
func (b *Bot) GetChat(ctx context.Context, chat ChatID) (*Chat, error) {
11+
p, err := b.resolvePeer(ctx, chat)
12+
if err != nil {
13+
return nil, err
14+
}
15+
c := chatFromPeer(p)
16+
return &c, nil
17+
}
18+
19+
// GetUserProfilePhotosOption configures a GetUserProfilePhotos call.
20+
type GetUserProfilePhotosOption func(*userPhotosConfig)
21+
22+
type userPhotosConfig struct {
23+
offset int
24+
limit int
25+
}
26+
27+
// WithProfilePhotosOffset sets the number of photos to skip.
28+
func WithProfilePhotosOffset(offset int) GetUserProfilePhotosOption {
29+
return func(c *userPhotosConfig) { c.offset = offset }
30+
}
31+
32+
// WithProfilePhotosLimit caps the number of photos returned (1-100).
33+
func WithProfilePhotosLimit(limit int) GetUserProfilePhotosOption {
34+
return func(c *userPhotosConfig) { c.limit = limit }
35+
}
36+
37+
// GetUserProfilePhotos returns a user's profile photos.
38+
func (b *Bot) GetUserProfilePhotos(ctx context.Context, userID int64, opts ...GetUserProfilePhotosOption) (*UserProfilePhotos, error) {
39+
cfg := userPhotosConfig{limit: 100}
40+
for _, o := range opts {
41+
o(&cfg)
42+
}
43+
44+
user, err := b.resolveInputUser(ctx, userID)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
res, err := b.raw.PhotosGetUserPhotos(ctx, &tg.PhotosGetUserPhotosRequest{
50+
UserID: user,
51+
Offset: cfg.offset,
52+
Limit: cfg.limit,
53+
})
54+
if err != nil {
55+
return nil, asAPIError(err)
56+
}
57+
58+
var (
59+
photos []tg.PhotoClass
60+
total int
61+
)
62+
switch p := res.(type) {
63+
case *tg.PhotosPhotos:
64+
photos = p.Photos
65+
total = len(p.Photos)
66+
case *tg.PhotosPhotosSlice:
67+
photos = p.Photos
68+
total = p.Count
69+
}
70+
71+
out := &UserProfilePhotos{TotalCount: total}
72+
for _, ph := range photos {
73+
if photo, ok := ph.(*tg.Photo); ok {
74+
out.Photos = append(out.Photos, photoSizesFromTg(photo))
75+
}
76+
}
77+
return out, nil
78+
}

chat_member_methods.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,39 @@ func userToInputPeer(u tg.InputUserClass) tg.InputPeerClass {
239239
return &tg.InputPeerEmpty{}
240240
}
241241
}
242+
243+
// SetChatAdministratorCustomTitle sets a custom title (rank) for an
244+
// administrator that the bot has promoted in a supergroup. It preserves the
245+
// administrator's existing rights.
246+
func (b *Bot) SetChatAdministratorCustomTitle(ctx context.Context, chat ChatID, userID int64, customTitle string) error {
247+
channel, err := b.resolveChannel(ctx, chat)
248+
if err != nil {
249+
return err
250+
}
251+
user, err := b.resolveInputUser(ctx, userID)
252+
if err != nil {
253+
return err
254+
}
255+
256+
res, err := b.raw.ChannelsGetParticipant(ctx, &tg.ChannelsGetParticipantRequest{
257+
Channel: channel,
258+
Participant: userToInputPeer(user),
259+
})
260+
if err != nil {
261+
return asAPIError(err)
262+
}
263+
admin, ok := res.Participant.(*tg.ChannelParticipantAdmin)
264+
if !ok {
265+
return &Error{Code: 400, Description: "Bad Request: user is not an administrator"}
266+
}
267+
268+
if _, err := b.raw.ChannelsEditAdmin(ctx, &tg.ChannelsEditAdminRequest{
269+
Channel: channel,
270+
UserID: user,
271+
AdminRights: admin.AdminRights,
272+
Rank: customTitle,
273+
}); err != nil {
274+
return asAPIError(err)
275+
}
276+
return nil
277+
}

conformance_test.go

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,17 @@ var coveredByOtherMeans = map[string]string{
2121
// implemented. Each is an acknowledged gap tracked in docs/roadmap.md; the
2222
// conformance test allows them so it can still catch *unacknowledged* drift.
2323
var deferredMethods = map[string]string{
24-
"editMessageMedia": "Phase 3 deferred",
25-
"answerPreCheckoutQuery": "payments — deferred until payment updates land",
26-
"answerShippingQuery": "payments — deferred until payment updates land",
27-
"sendInvoice": "payments — deferred",
28-
"setPassportDataErrors": "Telegram Passport — deferred",
29-
"sendGame": "games — deferred",
30-
"setGameScore": "games — deferred",
31-
"getGameHighScores": "games — deferred",
32-
"getStickerSet": "stickers — deferred (needs Sticker[] conversion)",
33-
"setStickerSetThumb": "stickers — deferred",
34-
"getChat": "chat info — deferred",
35-
"getUserProfilePhotos": "deferred",
36-
"setChatStickerSet": "chat sticker set — deferred",
37-
"deleteChatStickerSet": "chat sticker set — deferred",
38-
"setChatAdministratorCustomTitle": "member management — deferred",
39-
"stopPoll": "deferred",
24+
"editMessageMedia": "Phase 3 deferred",
25+
"answerPreCheckoutQuery": "payments — deferred until payment updates land",
26+
"answerShippingQuery": "payments — deferred until payment updates land",
27+
"sendInvoice": "payments — deferred",
28+
"setPassportDataErrors": "Telegram Passport — deferred",
29+
"sendGame": "games — deferred",
30+
"setGameScore": "games — deferred",
31+
"getGameHighScores": "games — deferred",
32+
"getStickerSet": "stickers — deferred (needs Sticker[] conversion)",
33+
"setStickerSetThumb": "stickers — deferred",
34+
"stopPoll": "deferred",
4035
}
4136

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

0 commit comments

Comments
 (0)