-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmethods.go
More file actions
284 lines (263 loc) · 8.88 KB
/
Copy pathmethods.go
File metadata and controls
284 lines (263 loc) · 8.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// SPDX-License-Identifier: Apache-2.0
package tg
import (
"context"
"encoding/json"
"net/url"
"strconv"
"time"
)
// noLinkPreview replaces the removed disable_web_page_preview parameter.
var noLinkPreview = &LinkPreviewOptions{IsDisabled: true}
func (c *Client) GetMe(ctx context.Context) (Me, error) {
var resp struct {
OK bool `json:"ok"`
Result Me `json:"result"`
}
if err := c.get(ctx, "getMe", url.Values{}, &resp); err != nil {
return Me{}, err
}
if !resp.OK {
return Me{}, okFalse("getMe")
}
return resp.Result, nil
}
// DeleteWebhook makes sure long polling is possible: a bot with a webhook set
// gets 409 from getUpdates forever.
func (c *Client) DeleteWebhook(ctx context.Context) error {
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "deleteWebhook", map[string]any{"drop_pending_updates": false}, &resp)
}
// GetUpdates long-polls. timeoutSeconds is Telegram's own poll duration; the
// HTTP deadline is derived from it, so a slow network cannot cut a poll short.
func (c *Client) GetUpdates(ctx context.Context, offset int64, timeoutSeconds int) ([]Update, error) {
values := url.Values{}
values.Set("timeout", strconv.Itoa(timeoutSeconds))
if len(c.allowed) > 0 {
encoded, err := json.Marshal(c.allowed)
if err != nil {
return nil, err
}
values.Set("allowed_updates", string(encoded))
}
if offset > 0 {
values.Set("offset", strconv.FormatInt(offset, 10))
}
var resp struct {
OK bool `json:"ok"`
Result []Update `json:"result"`
}
reqTimeout := time.Duration(timeoutSeconds)*time.Second + 15*time.Second
if err := c.getWithTimeout(ctx, "getUpdates", values, &resp, reqTimeout); err != nil {
return nil, err
}
if !resp.OK {
return nil, okFalse("getUpdates")
}
return resp.Result, nil
}
// SetMyCommands publishes the default command list.
func (c *Client) SetMyCommands(ctx context.Context, commands []BotCommand) error {
return c.SetMyCommandsForScope(ctx, commands, nil)
}
// SetMyCommandsForScope publishes one command list. language_code is a sibling
// of scope in the Bot API, not a field inside it, so it is lifted out here.
func (c *Client) SetMyCommandsForScope(ctx context.Context, commands []BotCommand, scope *BotCommandScope) error {
req := map[string]any{"commands": commands}
if scope != nil {
req["scope"] = scope
if scope.LanguageCode != "" {
req["language_code"] = scope.LanguageCode
}
}
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "setMyCommands", req, &resp)
}
func (c *Client) SendMessage(ctx context.Context, chatID int64, text string, markup *InlineKeyboardMarkup) (Message, error) {
return c.sendMessage(ctx, map[string]any{
"chat_id": chatID,
"text": text,
"parse_mode": "HTML",
"link_preview_options": noLinkPreview,
}, markup)
}
// SendPlainText sends text with no parse mode at all. It is the delivery of
// last resort: HTML that Telegram rejects -- malformed, or using a tag a
// newer client stopped accepting -- fails the whole send, and a notification
// that arrives unformatted beats one that does not arrive.
func (c *Client) SendPlainText(ctx context.Context, chatID int64, text string) (Message, error) {
return c.sendMessage(ctx, map[string]any{
"chat_id": chatID,
"text": text,
"link_preview_options": noLinkPreview,
}, nil)
}
// SendTextWithPreview sends HTML text and lets Telegram render its own preview
// of a link inside it. Every other send here suppresses the preview, because a
// bot's own message is normally the content; this exists for a bot relaying
// text whose author decided otherwise. A nil preview disables it, matching the
// rest of the package.
func (c *Client) SendTextWithPreview(ctx context.Context, chatID int64, threadID int, text string, preview *LinkPreviewOptions, markup *InlineKeyboardMarkup) (Message, error) {
if preview == nil {
preview = noLinkPreview
}
req := map[string]any{
"chat_id": chatID,
"text": text,
"parse_mode": "HTML",
"link_preview_options": preview,
}
if threadID > 0 {
req["message_thread_id"] = threadID
}
return c.sendMessage(ctx, req, markup)
}
// SendReply sends an HTML message as a reply, optionally inside a forum topic.
func (c *Client) SendReply(ctx context.Context, chatID, replyTo int64, threadID int, text string, markup *InlineKeyboardMarkup) (Message, error) {
req := map[string]any{
"chat_id": chatID,
"text": text,
"parse_mode": "HTML",
"link_preview_options": noLinkPreview,
}
if replyTo > 0 {
req["reply_parameters"] = map[string]any{
"message_id": replyTo,
"allow_sending_without_reply": true,
}
}
if threadID > 0 {
req["message_thread_id"] = threadID
}
return c.sendMessage(ctx, req, markup)
}
func (c *Client) sendMessage(ctx context.Context, req map[string]any, markup *InlineKeyboardMarkup) (Message, error) {
if markup != nil {
req["reply_markup"] = markup
}
var resp struct {
OK bool `json:"ok"`
Result Message `json:"result"`
}
if err := c.post(ctx, "sendMessage", req, &resp); err != nil {
return Message{}, err
}
if !resp.OK {
return Message{}, okFalse("sendMessage")
}
return resp.Result, nil
}
// EditMessageText replaces a message's text. A "message is not modified"
// answer is success: it means the rendering did not change.
func (c *Client) EditMessageText(ctx context.Context, chatID, messageID int64, text string, markup *InlineKeyboardMarkup) error {
req := map[string]any{
"chat_id": chatID,
"message_id": messageID,
"text": text,
"parse_mode": "HTML",
"link_preview_options": noLinkPreview,
}
if markup != nil {
req["reply_markup"] = markup
}
var resp struct {
OK bool `json:"ok"`
}
err := c.post(ctx, "editMessageText", req, &resp)
if IsMessageNotModified(err) {
return nil
}
return err
}
func (c *Client) DeleteMessage(ctx context.Context, chatID, messageID int64) error {
var resp struct {
OK bool `json:"ok"`
}
if err := c.post(ctx, "deleteMessage", map[string]any{
"chat_id": chatID,
"message_id": messageID,
}, &resp); err != nil {
return err
}
if !resp.OK {
return okFalse("deleteMessage")
}
return nil
}
// AnswerCallbackQuery closes the spinner on a tapped inline button. text is
// optional and shows as a toast.
func (c *Client) AnswerCallbackQuery(ctx context.Context, callbackID, text string) error {
return c.answerCallbackQuery(ctx, callbackID, text, false)
}
// AnswerCallbackQueryAlert answers with a modal the user has to dismiss.
func (c *Client) AnswerCallbackQueryAlert(ctx context.Context, callbackID, text string) error {
return c.answerCallbackQuery(ctx, callbackID, text, true)
}
// AnswerCallbackQueryURL answers a tapped button by opening a link, which for
// a t.me deep link means the client follows it without an intermediate
// message. Telegram only honors a URL that points at the bot itself or at a
// game it owns; anything else is ignored, and the tap looks like it did
// nothing.
func (c *Client) AnswerCallbackQueryURL(ctx context.Context, callbackID, url string) error {
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "answerCallbackQuery", map[string]any{
"callback_query_id": callbackID,
"url": url,
}, &resp)
}
func (c *Client) answerCallbackQuery(ctx context.Context, callbackID, text string, alert bool) error {
req := map[string]any{"callback_query_id": callbackID}
if text != "" {
req["text"] = text
}
if alert {
req["show_alert"] = true
}
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "answerCallbackQuery", req, &resp)
}
// SendChatAction shows "typing…" or "sending audio…" while work is in flight.
func (c *Client) SendChatAction(ctx context.Context, chatID int64, threadID int, action string) error {
req := map[string]any{"chat_id": chatID, "action": action}
if threadID > 0 {
req["message_thread_id"] = threadID
}
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "sendChatAction", req, &resp)
}
func (c *Client) GetChatMember(ctx context.Context, chatID, userID int64) (ChatMember, error) {
values := url.Values{}
values.Set("chat_id", strconv.FormatInt(chatID, 10))
values.Set("user_id", strconv.FormatInt(userID, 10))
var resp struct {
OK bool `json:"ok"`
Result ChatMember `json:"result"`
}
if err := c.get(ctx, "getChatMember", values, &resp); err != nil {
return ChatMember{}, err
}
if !resp.OK {
return ChatMember{}, okFalse("getChatMember")
}
return resp.Result, nil
}
// LogOut releases the token from the current Bot API server so it can be used
// on another one. Telegram refuses to log back in to the cloud server for ten
// minutes afterwards, so this is only ever called deliberately — and never by
// [Client.Probe].
func (c *Client) LogOut(ctx context.Context) error {
var resp struct {
OK bool `json:"ok"`
}
return c.post(ctx, "logOut", map[string]any{}, &resp)
}