Skip to content

Commit bd030d9

Browse files
ernadoclaude
andcommitted
feat: add user emoji status and custom verification methods
SetUserEmojiStatus (bots.updateUserEmojiStatus) and VerifyUser/VerifyChat/ RemoveUserVerification/RemoveChatVerification (bots.setCustomVerification). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5a42ea6 commit bd030d9

4 files changed

Lines changed: 275 additions & 0 deletions

File tree

emoji_status.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"strconv"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
// EmojiStatusOption configures a SetUserEmojiStatus call.
11+
type EmojiStatusOption func(*emojiStatusConfig)
12+
13+
type emojiStatusConfig struct {
14+
until int
15+
}
16+
17+
// WithEmojiStatusExpiration sets the Unix time when the emoji status will
18+
// expire. By default the status does not expire.
19+
func WithEmojiStatusExpiration(unixTime int) EmojiStatusOption {
20+
return func(c *emojiStatusConfig) { c.until = unixTime }
21+
}
22+
23+
// SetUserEmojiStatus sets the emoji status of a user that previously allowed the
24+
// bot to manage it. An empty customEmojiID removes the current emoji status.
25+
func (b *Bot) SetUserEmojiStatus(ctx context.Context, userID int64, customEmojiID string, opts ...EmojiStatusOption) error {
26+
var cfg emojiStatusConfig
27+
28+
for _, o := range opts {
29+
o(&cfg)
30+
}
31+
32+
user, err := b.resolveInputUser(ctx, userID)
33+
if err != nil {
34+
return err
35+
}
36+
37+
var status tg.EmojiStatusClass = &tg.EmojiStatusEmpty{}
38+
39+
if customEmojiID != "" {
40+
id, err := strconv.ParseInt(customEmojiID, 10, 64)
41+
if err != nil {
42+
return errInvalidCustomEmojiID()
43+
}
44+
45+
s := &tg.EmojiStatus{DocumentID: id}
46+
if cfg.until != 0 {
47+
s.SetUntil(cfg.until)
48+
}
49+
50+
status = s
51+
}
52+
53+
if _, err := b.raw.BotsUpdateUserEmojiStatus(ctx, &tg.BotsUpdateUserEmojiStatusRequest{
54+
UserID: user,
55+
EmojiStatus: status,
56+
}); err != nil {
57+
return asAPIError(err)
58+
}
59+
60+
return nil
61+
}

emoji_status_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
func TestSetUserEmojiStatus(t *testing.T) {
11+
inv := newMockInvoker()
12+
inv.reply(tg.BotsUpdateUserEmojiStatusRequestTypeID, &tg.BoolTrue{})
13+
14+
if err := newMockBot(inv).SetUserEmojiStatus(context.Background(), 99, "555", WithEmojiStatusExpiration(1000)); err != nil {
15+
t.Fatalf("SetUserEmojiStatus: %v", err)
16+
}
17+
18+
var req tg.BotsUpdateUserEmojiStatusRequest
19+
20+
inv.decode(t, tg.BotsUpdateUserEmojiStatusRequestTypeID, &req)
21+
22+
status, ok := req.EmojiStatus.(*tg.EmojiStatus)
23+
if !ok || status.DocumentID != 555 {
24+
t.Fatalf("status = %#v", req.EmojiStatus)
25+
}
26+
27+
if until, ok := status.GetUntil(); !ok || until != 1000 {
28+
t.Fatalf("until = %d, ok=%v", until, ok)
29+
}
30+
}
31+
32+
func TestSetUserEmojiStatusClear(t *testing.T) {
33+
inv := newMockInvoker()
34+
inv.reply(tg.BotsUpdateUserEmojiStatusRequestTypeID, &tg.BoolTrue{})
35+
36+
if err := newMockBot(inv).SetUserEmojiStatus(context.Background(), 99, ""); err != nil {
37+
t.Fatalf("SetUserEmojiStatus: %v", err)
38+
}
39+
40+
var req tg.BotsUpdateUserEmojiStatusRequest
41+
42+
inv.decode(t, tg.BotsUpdateUserEmojiStatusRequestTypeID, &req)
43+
44+
if _, ok := req.EmojiStatus.(*tg.EmojiStatusEmpty); !ok {
45+
t.Fatalf("status = %#v, want empty", req.EmojiStatus)
46+
}
47+
}

verification.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/tg"
7+
)
8+
9+
// VerificationOption configures a verify call.
10+
type VerificationOption func(*verificationConfig)
11+
12+
type verificationConfig struct {
13+
description string
14+
}
15+
16+
// WithVerificationDescription sets a custom description for the verification
17+
// shown instead of the bot's default verification description.
18+
func WithVerificationDescription(text string) VerificationOption {
19+
return func(c *verificationConfig) { c.description = text }
20+
}
21+
22+
// setCustomVerification enables or disables the bot's custom verification of a
23+
// peer.
24+
func (b *Bot) setCustomVerification(ctx context.Context, peer tg.InputPeerClass, enabled bool, opts []VerificationOption) error {
25+
var cfg verificationConfig
26+
27+
for _, o := range opts {
28+
o(&cfg)
29+
}
30+
31+
req := &tg.BotsSetCustomVerificationRequest{Peer: peer}
32+
req.SetEnabled(enabled)
33+
34+
if enabled && cfg.description != "" {
35+
req.SetCustomDescription(cfg.description)
36+
}
37+
38+
if _, err := b.raw.BotsSetCustomVerification(ctx, req); err != nil {
39+
return asAPIError(err)
40+
}
41+
42+
return nil
43+
}
44+
45+
// VerifyUser verifies a user on behalf of the organization represented by the
46+
// bot.
47+
func (b *Bot) VerifyUser(ctx context.Context, userID int64, opts ...VerificationOption) error {
48+
user, err := b.resolveInputUser(ctx, userID)
49+
if err != nil {
50+
return err
51+
}
52+
53+
return b.setCustomVerification(ctx, userToInputPeer(user), true, opts)
54+
}
55+
56+
// VerifyChat verifies a chat on behalf of the organization represented by the
57+
// bot.
58+
func (b *Bot) VerifyChat(ctx context.Context, chat ChatID, opts ...VerificationOption) error {
59+
peer, err := b.resolveInputPeer(ctx, chat)
60+
if err != nil {
61+
return err
62+
}
63+
64+
return b.setCustomVerification(ctx, peer, true, opts)
65+
}
66+
67+
// RemoveUserVerification removes the verification from a user that is verified on
68+
// behalf of the organization represented by the bot.
69+
func (b *Bot) RemoveUserVerification(ctx context.Context, userID int64) error {
70+
user, err := b.resolveInputUser(ctx, userID)
71+
if err != nil {
72+
return err
73+
}
74+
75+
return b.setCustomVerification(ctx, userToInputPeer(user), false, nil)
76+
}
77+
78+
// RemoveChatVerification removes the verification from a chat that is verified on
79+
// behalf of the organization represented by the bot.
80+
func (b *Bot) RemoveChatVerification(ctx context.Context, chat ChatID) error {
81+
peer, err := b.resolveInputPeer(ctx, chat)
82+
if err != nil {
83+
return err
84+
}
85+
86+
return b.setCustomVerification(ctx, peer, false, nil)
87+
}

verification_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
func TestVerifyUser(t *testing.T) {
11+
inv := newMockInvoker()
12+
inv.reply(tg.BotsSetCustomVerificationRequestTypeID, &tg.BoolTrue{})
13+
14+
if err := newMockBot(inv).VerifyUser(context.Background(), 99, WithVerificationDescription("trusted")); err != nil {
15+
t.Fatalf("VerifyUser: %v", err)
16+
}
17+
18+
var req tg.BotsSetCustomVerificationRequest
19+
20+
inv.decode(t, tg.BotsSetCustomVerificationRequestTypeID, &req)
21+
22+
if !req.GetEnabled() {
23+
t.Fatal("enabled should be true")
24+
}
25+
26+
if desc, ok := req.GetCustomDescription(); !ok || desc != "trusted" {
27+
t.Fatalf("description = %q, ok=%v", desc, ok)
28+
}
29+
30+
if _, ok := req.Peer.(*tg.InputPeerUser); !ok {
31+
t.Fatalf("peer = %#v, want user", req.Peer)
32+
}
33+
}
34+
35+
func TestVerifyChat(t *testing.T) {
36+
inv := newMockInvoker()
37+
inv.reply(tg.BotsSetCustomVerificationRequestTypeID, &tg.BoolTrue{})
38+
39+
if err := newMockBot(inv).VerifyChat(context.Background(), tdlibChannel(50)); err != nil {
40+
t.Fatalf("VerifyChat: %v", err)
41+
}
42+
43+
var req tg.BotsSetCustomVerificationRequest
44+
45+
inv.decode(t, tg.BotsSetCustomVerificationRequestTypeID, &req)
46+
47+
if _, ok := req.Peer.(*tg.InputPeerChannel); !ok {
48+
t.Fatalf("peer = %#v, want channel", req.Peer)
49+
}
50+
}
51+
52+
func TestRemoveUserVerification(t *testing.T) {
53+
inv := newMockInvoker()
54+
inv.reply(tg.BotsSetCustomVerificationRequestTypeID, &tg.BoolTrue{})
55+
56+
if err := newMockBot(inv).RemoveUserVerification(context.Background(), 99); err != nil {
57+
t.Fatalf("RemoveUserVerification: %v", err)
58+
}
59+
60+
var req tg.BotsSetCustomVerificationRequest
61+
62+
inv.decode(t, tg.BotsSetCustomVerificationRequestTypeID, &req)
63+
64+
if req.GetEnabled() {
65+
t.Fatal("enabled should be false")
66+
}
67+
}
68+
69+
func TestRemoveChatVerification(t *testing.T) {
70+
inv := newMockInvoker()
71+
inv.reply(tg.BotsSetCustomVerificationRequestTypeID, &tg.BoolTrue{})
72+
73+
if err := newMockBot(inv).RemoveChatVerification(context.Background(), tdlibChannel(50)); err != nil {
74+
t.Fatalf("RemoveChatVerification: %v", err)
75+
}
76+
77+
if !inv.called(tg.BotsSetCustomVerificationRequestTypeID) {
78+
t.Fatal("verification not called")
79+
}
80+
}

0 commit comments

Comments
 (0)