configs.go (view raw)
1package tgbotapi
2
3import (
4 "encoding/json"
5 "io"
6 "net/url"
7 "strconv"
8)
9
10// Telegram constants
11const (
12 // APIEndpoint is the endpoint for all API methods,
13 // with formatting for Sprintf.
14 APIEndpoint = "https://api.telegram.org/bot%s/%s"
15 // FileEndpoint is the endpoint for downloading a file from Telegram.
16 FileEndpoint = "https://api.telegram.org/file/bot%s/%s"
17)
18
19// Constant values for ChatActions
20const (
21 ChatTyping = "typing"
22 ChatUploadPhoto = "upload_photo"
23 ChatRecordVideo = "record_video"
24 ChatUploadVideo = "upload_video"
25 ChatRecordAudio = "record_audio"
26 ChatUploadAudio = "upload_audio"
27 ChatUploadDocument = "upload_document"
28 ChatFindLocation = "find_location"
29)
30
31// API errors
32const (
33 // ErrAPIForbidden happens when a token is bad
34 ErrAPIForbidden = "forbidden"
35)
36
37// Constant values for ParseMode in MessageConfig
38const (
39 ModeMarkdown = "Markdown"
40 ModeHTML = "HTML"
41)
42
43// Library errors
44const (
45 // ErrBadFileType happens when you pass an unknown type
46 ErrBadFileType = "bad file type"
47 ErrBadURL = "bad or empty url"
48)
49
50// Chattable is any config type that can be sent.
51type Chattable interface {
52 values() (url.Values, error)
53 method() string
54}
55
56// Fileable is any config type that can be sent that includes a file.
57type Fileable interface {
58 Chattable
59 params() (map[string]string, error)
60 name() string
61 getFile() interface{}
62 useExistingFile() bool
63}
64
65// BaseChat is base type for all chat config types.
66type BaseChat struct {
67 ChatID int64 // required
68 ChannelUsername string
69 ReplyToMessageID int
70 ReplyMarkup interface{}
71 DisableNotification bool
72}
73
74// values returns url.Values representation of BaseChat
75func (chat *BaseChat) values() (url.Values, error) {
76 v := url.Values{}
77 if chat.ChannelUsername != "" {
78 v.Add("chat_id", chat.ChannelUsername)
79 } else {
80 v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10))
81 }
82
83 if chat.ReplyToMessageID != 0 {
84 v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID))
85 }
86
87 if chat.ReplyMarkup != nil {
88 data, err := json.Marshal(chat.ReplyMarkup)
89 if err != nil {
90 return v, err
91 }
92
93 v.Add("reply_markup", string(data))
94 }
95
96 v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification))
97
98 return v, nil
99}
100
101// BaseFile is a base type for all file config types.
102type BaseFile struct {
103 BaseChat
104 File interface{}
105 FileID string
106 UseExisting bool
107 MimeType string
108 FileSize int
109}
110
111// params returns a map[string]string representation of BaseFile.
112func (file BaseFile) params() (map[string]string, error) {
113 params := make(map[string]string)
114
115 if file.ChannelUsername != "" {
116 params["chat_id"] = file.ChannelUsername
117 } else {
118 params["chat_id"] = strconv.FormatInt(file.ChatID, 10)
119 }
120
121 if file.ReplyToMessageID != 0 {
122 params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID)
123 }
124
125 if file.ReplyMarkup != nil {
126 data, err := json.Marshal(file.ReplyMarkup)
127 if err != nil {
128 return params, err
129 }
130
131 params["reply_markup"] = string(data)
132 }
133
134 if file.MimeType != "" {
135 params["mime_type"] = file.MimeType
136 }
137
138 if file.FileSize > 0 {
139 params["file_size"] = strconv.Itoa(file.FileSize)
140 }
141
142 params["disable_notification"] = strconv.FormatBool(file.DisableNotification)
143
144 return params, nil
145}
146
147// getFile returns the file.
148func (file BaseFile) getFile() interface{} {
149 return file.File
150}
151
152// useExistingFile returns if the BaseFile has already been uploaded.
153func (file BaseFile) useExistingFile() bool {
154 return file.UseExisting
155}
156
157// BaseEdit is base type of all chat edits.
158type BaseEdit struct {
159 ChatID int64
160 ChannelUsername string
161 MessageID int
162 InlineMessageID string
163 ReplyMarkup *InlineKeyboardMarkup
164}
165
166func (edit BaseEdit) values() (url.Values, error) {
167 v := url.Values{}
168
169 if edit.InlineMessageID == "" {
170 if edit.ChannelUsername != "" {
171 v.Add("chat_id", edit.ChannelUsername)
172 } else {
173 v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10))
174 }
175 v.Add("message_id", strconv.Itoa(edit.MessageID))
176 } else {
177 v.Add("inline_message_id", edit.InlineMessageID)
178 }
179
180 if edit.ReplyMarkup != nil {
181 data, err := json.Marshal(edit.ReplyMarkup)
182 if err != nil {
183 return v, err
184 }
185 v.Add("reply_markup", string(data))
186 }
187
188 return v, nil
189}
190
191// MessageConfig contains information about a SendMessage request.
192type MessageConfig struct {
193 BaseChat
194 Text string
195 ParseMode string
196 DisableWebPagePreview bool
197}
198
199// values returns a url.Values representation of MessageConfig.
200func (config MessageConfig) values() (url.Values, error) {
201 v, err := config.BaseChat.values()
202 if err != nil {
203 return v, err
204 }
205 v.Add("text", config.Text)
206 v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
207 if config.ParseMode != "" {
208 v.Add("parse_mode", config.ParseMode)
209 }
210
211 return v, nil
212}
213
214// method returns Telegram API method name for sending Message.
215func (config MessageConfig) method() string {
216 return "sendMessage"
217}
218
219// ForwardConfig contains information about a ForwardMessage request.
220type ForwardConfig struct {
221 BaseChat
222 FromChatID int64 // required
223 FromChannelUsername string
224 MessageID int // required
225}
226
227// values returns a url.Values representation of ForwardConfig.
228func (config ForwardConfig) values() (url.Values, error) {
229 v, err := config.BaseChat.values()
230 if err != nil {
231 return v, err
232 }
233 v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10))
234 v.Add("message_id", strconv.Itoa(config.MessageID))
235 return v, nil
236}
237
238// method returns Telegram API method name for sending Forward.
239func (config ForwardConfig) method() string {
240 return "forwardMessage"
241}
242
243// PhotoConfig contains information about a SendPhoto request.
244type PhotoConfig struct {
245 BaseFile
246 Caption string
247}
248
249// Params returns a map[string]string representation of PhotoConfig.
250func (config PhotoConfig) params() (map[string]string, error) {
251 params, _ := config.BaseFile.params()
252
253 if config.Caption != "" {
254 params["caption"] = config.Caption
255 }
256
257 return params, nil
258}
259
260// Values returns a url.Values representation of PhotoConfig.
261func (config PhotoConfig) values() (url.Values, error) {
262 v, err := config.BaseChat.values()
263 if err != nil {
264 return v, err
265 }
266
267 v.Add(config.name(), config.FileID)
268 if config.Caption != "" {
269 v.Add("caption", config.Caption)
270 }
271 return v, nil
272}
273
274// name returns the field name for the Photo.
275func (config PhotoConfig) name() string {
276 return "photo"
277}
278
279// method returns Telegram API method name for sending Photo.
280func (config PhotoConfig) method() string {
281 return "sendPhoto"
282}
283
284// AudioConfig contains information about a SendAudio request.
285type AudioConfig struct {
286 BaseFile
287 Caption string
288 Duration int
289 Performer string
290 Title string
291}
292
293// values returns a url.Values representation of AudioConfig.
294func (config AudioConfig) values() (url.Values, error) {
295 v, err := config.BaseChat.values()
296 if err != nil {
297 return v, err
298 }
299
300 v.Add(config.name(), config.FileID)
301 if config.Duration != 0 {
302 v.Add("duration", strconv.Itoa(config.Duration))
303 }
304
305 if config.Performer != "" {
306 v.Add("performer", config.Performer)
307 }
308 if config.Title != "" {
309 v.Add("title", config.Title)
310 }
311 if config.Caption != "" {
312 v.Add("caption", config.Caption)
313 }
314
315 return v, nil
316}
317
318// params returns a map[string]string representation of AudioConfig.
319func (config AudioConfig) params() (map[string]string, error) {
320 params, _ := config.BaseFile.params()
321
322 if config.Duration != 0 {
323 params["duration"] = strconv.Itoa(config.Duration)
324 }
325
326 if config.Performer != "" {
327 params["performer"] = config.Performer
328 }
329 if config.Title != "" {
330 params["title"] = config.Title
331 }
332 if config.Caption != "" {
333 params["caption"] = config.Caption
334 }
335
336 return params, nil
337}
338
339// name returns the field name for the Audio.
340func (config AudioConfig) name() string {
341 return "audio"
342}
343
344// method returns Telegram API method name for sending Audio.
345func (config AudioConfig) method() string {
346 return "sendAudio"
347}
348
349// DocumentConfig contains information about a SendDocument request.
350type DocumentConfig struct {
351 BaseFile
352 Caption string
353}
354
355// values returns a url.Values representation of DocumentConfig.
356func (config DocumentConfig) values() (url.Values, error) {
357 v, err := config.BaseChat.values()
358 if err != nil {
359 return v, err
360 }
361
362 v.Add(config.name(), config.FileID)
363 if config.Caption != "" {
364 v.Add("caption", config.Caption)
365 }
366
367 return v, nil
368}
369
370// params returns a map[string]string representation of DocumentConfig.
371func (config DocumentConfig) params() (map[string]string, error) {
372 params, _ := config.BaseFile.params()
373
374 if config.Caption != "" {
375 params["caption"] = config.Caption
376 }
377
378 return params, nil
379}
380
381// name returns the field name for the Document.
382func (config DocumentConfig) name() string {
383 return "document"
384}
385
386// method returns Telegram API method name for sending Document.
387func (config DocumentConfig) method() string {
388 return "sendDocument"
389}
390
391// StickerConfig contains information about a SendSticker request.
392type StickerConfig struct {
393 BaseFile
394}
395
396// values returns a url.Values representation of StickerConfig.
397func (config StickerConfig) values() (url.Values, error) {
398 v, err := config.BaseChat.values()
399 if err != nil {
400 return v, err
401 }
402
403 v.Add(config.name(), config.FileID)
404
405 return v, nil
406}
407
408// params returns a map[string]string representation of StickerConfig.
409func (config StickerConfig) params() (map[string]string, error) {
410 params, _ := config.BaseFile.params()
411
412 return params, nil
413}
414
415// name returns the field name for the Sticker.
416func (config StickerConfig) name() string {
417 return "sticker"
418}
419
420// method returns Telegram API method name for sending Sticker.
421func (config StickerConfig) method() string {
422 return "sendSticker"
423}
424
425// VideoConfig contains information about a SendVideo request.
426type VideoConfig struct {
427 BaseFile
428 Duration int
429 Caption string
430}
431
432// values returns a url.Values representation of VideoConfig.
433func (config VideoConfig) values() (url.Values, error) {
434 v, err := config.BaseChat.values()
435 if err != nil {
436 return v, err
437 }
438
439 v.Add(config.name(), config.FileID)
440 if config.Duration != 0 {
441 v.Add("duration", strconv.Itoa(config.Duration))
442 }
443 if config.Caption != "" {
444 v.Add("caption", config.Caption)
445 }
446
447 return v, nil
448}
449
450// params returns a map[string]string representation of VideoConfig.
451func (config VideoConfig) params() (map[string]string, error) {
452 params, _ := config.BaseFile.params()
453
454 if config.Caption != "" {
455 params["caption"] = config.Caption
456 }
457
458 return params, nil
459}
460
461// name returns the field name for the Video.
462func (config VideoConfig) name() string {
463 return "video"
464}
465
466// method returns Telegram API method name for sending Video.
467func (config VideoConfig) method() string {
468 return "sendVideo"
469}
470
471// VideoNoteConfig contains information about a SendVideoNote request.
472type VideoNoteConfig struct {
473 BaseFile
474 Duration int
475 Length int
476}
477
478// values returns a url.Values representation of VideoNoteConfig.
479func (config VideoNoteConfig) values() (url.Values, error) {
480 v, err := config.BaseChat.values()
481 if err != nil {
482 return v, err
483 }
484
485 v.Add(config.name(), config.FileID)
486 if config.Duration != 0 {
487 v.Add("duration", strconv.Itoa(config.Duration))
488 }
489
490 // Telegram API seems to have a bug, if no length is provided or it is 0, it will send an error response
491 if config.Length != 0 {
492 v.Add("length", strconv.Itoa(config.Length))
493 }
494
495 return v, nil
496}
497
498// params returns a map[string]string representation of VideoNoteConfig.
499func (config VideoNoteConfig) params() (map[string]string, error) {
500 params, _ := config.BaseFile.params()
501
502 if config.Length != 0 {
503 params["length"] = strconv.Itoa(config.Length)
504 }
505 if config.Duration != 0 {
506 params["duration"] = strconv.Itoa(config.Duration)
507 }
508
509 return params, nil
510}
511
512// name returns the field name for the VideoNote.
513func (config VideoNoteConfig) name() string {
514 return "video_note"
515}
516
517// method returns Telegram API method name for sending VideoNote.
518func (config VideoNoteConfig) method() string {
519 return "sendVideoNote"
520}
521
522// VoiceConfig contains information about a SendVoice request.
523type VoiceConfig struct {
524 BaseFile
525 Caption string
526 Duration int
527}
528
529// values returns a url.Values representation of VoiceConfig.
530func (config VoiceConfig) values() (url.Values, error) {
531 v, err := config.BaseChat.values()
532 if err != nil {
533 return v, err
534 }
535
536 v.Add(config.name(), config.FileID)
537 if config.Duration != 0 {
538 v.Add("duration", strconv.Itoa(config.Duration))
539 }
540 if config.Caption != "" {
541 v.Add("caption", config.Caption)
542 }
543
544 return v, nil
545}
546
547// params returns a map[string]string representation of VoiceConfig.
548func (config VoiceConfig) params() (map[string]string, error) {
549 params, _ := config.BaseFile.params()
550
551 if config.Duration != 0 {
552 params["duration"] = strconv.Itoa(config.Duration)
553 }
554 if config.Caption != "" {
555 params["caption"] = config.Caption
556 }
557
558 return params, nil
559}
560
561// name returns the field name for the Voice.
562func (config VoiceConfig) name() string {
563 return "voice"
564}
565
566// method returns Telegram API method name for sending Voice.
567func (config VoiceConfig) method() string {
568 return "sendVoice"
569}
570
571// LocationConfig contains information about a SendLocation request.
572type LocationConfig struct {
573 BaseChat
574 Latitude float64 // required
575 Longitude float64 // required
576 LivePeriod int // optional
577}
578
579// values returns a url.Values representation of LocationConfig.
580func (config LocationConfig) values() (url.Values, error) {
581 v, err := config.BaseChat.values()
582 if err != nil {
583 return v, err
584 }
585
586 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
587 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
588 if config.LivePeriod != 0 {
589 v.Add("live_period", strconv.Itoa(config.LivePeriod))
590 }
591
592 return v, nil
593}
594
595// method returns Telegram API method name for sending Location.
596func (config LocationConfig) method() string {
597 return "sendLocation"
598}
599
600// LocationConfig contains information about a SendLocation request.
601type EditMessageLiveLocationConfig struct {
602 BaseEdit
603 Latitude float64 // required
604 Longitude float64 // required
605}
606
607// values returns a url.Values representation of EditMessageLiveLocationConfig.
608func (config EditMessageLiveLocationConfig) values() (url.Values, error) {
609 v, err := config.BaseEdit.values()
610 if err != nil {
611 return v, err
612 }
613
614 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
615 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
616
617 return v, nil
618}
619
620// method returns Telegram API method name for edit message Live Location.
621func (config EditMessageLiveLocationConfig) method() string {
622 return "editMessageLiveLocation"
623}
624
625// LocationConfig contains information about a StopMessageLiveLocation request.
626type StopMessageLiveLocationConfig struct {
627 BaseEdit
628}
629
630// values returns a url.Values representation of StopMessageLiveLocationConfig.
631func (config StopMessageLiveLocationConfig) values() (url.Values, error) {
632 v, err := config.BaseEdit.values()
633 if err != nil {
634 return v, err
635 }
636
637 return v, nil
638}
639
640// method returns Telegram API method name for stop message Live Location.
641func (config StopMessageLiveLocationConfig) method() string {
642 return "stopMessageLiveLocation"
643}
644
645// VenueConfig contains information about a SendVenue request.
646type VenueConfig struct {
647 BaseChat
648 Latitude float64 // required
649 Longitude float64 // required
650 Title string // required
651 Address string // required
652 FoursquareID string
653}
654
655func (config VenueConfig) values() (url.Values, error) {
656 v, err := config.BaseChat.values()
657 if err != nil {
658 return v, err
659 }
660
661 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
662 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
663 v.Add("title", config.Title)
664 v.Add("address", config.Address)
665 if config.FoursquareID != "" {
666 v.Add("foursquare_id", config.FoursquareID)
667 }
668
669 return v, nil
670}
671
672func (config VenueConfig) method() string {
673 return "sendVenue"
674}
675
676// ContactConfig allows you to send a contact.
677type ContactConfig struct {
678 BaseChat
679 PhoneNumber string
680 FirstName string
681 LastName string
682}
683
684func (config ContactConfig) values() (url.Values, error) {
685 v, err := config.BaseChat.values()
686 if err != nil {
687 return v, err
688 }
689
690 v.Add("phone_number", config.PhoneNumber)
691 v.Add("first_name", config.FirstName)
692 v.Add("last_name", config.LastName)
693
694 return v, nil
695}
696
697func (config ContactConfig) method() string {
698 return "sendContact"
699}
700
701// GameConfig allows you to send a game.
702type GameConfig struct {
703 BaseChat
704 GameShortName string
705}
706
707func (config GameConfig) values() (url.Values, error) {
708 v, err := config.BaseChat.values()
709 if err != nil {
710 return v, err
711 }
712
713 v.Add("game_short_name", config.GameShortName)
714
715 return v, nil
716}
717
718func (config GameConfig) method() string {
719 return "sendGame"
720}
721
722// SetGameScoreConfig allows you to update the game score in a chat.
723type SetGameScoreConfig struct {
724 UserID int
725 Score int
726 Force bool
727 DisableEditMessage bool
728 ChatID int
729 ChannelUsername string
730 MessageID int
731 InlineMessageID string
732}
733
734func (config SetGameScoreConfig) values() (url.Values, error) {
735 v := url.Values{}
736
737 v.Add("user_id", strconv.Itoa(config.UserID))
738 v.Add("score", strconv.Itoa(config.Score))
739 if config.InlineMessageID == "" {
740 if config.ChannelUsername == "" {
741 v.Add("chat_id", strconv.Itoa(config.ChatID))
742 } else {
743 v.Add("chat_id", config.ChannelUsername)
744 }
745 v.Add("message_id", strconv.Itoa(config.MessageID))
746 } else {
747 v.Add("inline_message_id", config.InlineMessageID)
748 }
749 v.Add("disable_edit_message", strconv.FormatBool(config.DisableEditMessage))
750
751 return v, nil
752}
753
754func (config SetGameScoreConfig) method() string {
755 return "setGameScore"
756}
757
758// GetGameHighScoresConfig allows you to fetch the high scores for a game.
759type GetGameHighScoresConfig struct {
760 UserID int
761 ChatID int
762 ChannelUsername string
763 MessageID int
764 InlineMessageID string
765}
766
767func (config GetGameHighScoresConfig) values() (url.Values, error) {
768 v := url.Values{}
769
770 v.Add("user_id", strconv.Itoa(config.UserID))
771 if config.InlineMessageID == "" {
772 if config.ChannelUsername == "" {
773 v.Add("chat_id", strconv.Itoa(config.ChatID))
774 } else {
775 v.Add("chat_id", config.ChannelUsername)
776 }
777 v.Add("message_id", strconv.Itoa(config.MessageID))
778 } else {
779 v.Add("inline_message_id", config.InlineMessageID)
780 }
781
782 return v, nil
783}
784
785func (config GetGameHighScoresConfig) method() string {
786 return "getGameHighScores"
787}
788
789// ChatActionConfig contains information about a SendChatAction request.
790type ChatActionConfig struct {
791 BaseChat
792 Action string // required
793}
794
795// values returns a url.Values representation of ChatActionConfig.
796func (config ChatActionConfig) values() (url.Values, error) {
797 v, err := config.BaseChat.values()
798 if err != nil {
799 return v, err
800 }
801 v.Add("action", config.Action)
802 return v, nil
803}
804
805// method returns Telegram API method name for sending ChatAction.
806func (config ChatActionConfig) method() string {
807 return "sendChatAction"
808}
809
810// EditMessageTextConfig allows you to modify the text in a message.
811type EditMessageTextConfig struct {
812 BaseEdit
813 Text string
814 ParseMode string
815 DisableWebPagePreview bool
816}
817
818func (config EditMessageTextConfig) values() (url.Values, error) {
819 v, err := config.BaseEdit.values()
820 if err != nil {
821 return v, err
822 }
823
824 v.Add("text", config.Text)
825 v.Add("parse_mode", config.ParseMode)
826 v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
827
828 return v, nil
829}
830
831func (config EditMessageTextConfig) method() string {
832 return "editMessageText"
833}
834
835// EditMessageCaptionConfig allows you to modify the caption of a message.
836type EditMessageCaptionConfig struct {
837 BaseEdit
838 Caption string
839}
840
841func (config EditMessageCaptionConfig) values() (url.Values, error) {
842 v, _ := config.BaseEdit.values()
843
844 v.Add("caption", config.Caption)
845
846 return v, nil
847}
848
849func (config EditMessageCaptionConfig) method() string {
850 return "editMessageCaption"
851}
852
853// EditMessageReplyMarkupConfig allows you to modify the reply markup
854// of a message.
855type EditMessageReplyMarkupConfig struct {
856 BaseEdit
857}
858
859func (config EditMessageReplyMarkupConfig) values() (url.Values, error) {
860 return config.BaseEdit.values()
861}
862
863func (config EditMessageReplyMarkupConfig) method() string {
864 return "editMessageReplyMarkup"
865}
866
867// UserProfilePhotosConfig contains information about a
868// GetUserProfilePhotos request.
869type UserProfilePhotosConfig struct {
870 UserID int
871 Offset int
872 Limit int
873}
874
875// FileConfig has information about a file hosted on Telegram.
876type FileConfig struct {
877 FileID string
878}
879
880// UpdateConfig contains information about a GetUpdates request.
881type UpdateConfig struct {
882 Offset int
883 Limit int
884 Timeout int
885}
886
887// WebhookConfig contains information about a SetWebhook request.
888type WebhookConfig struct {
889 URL *url.URL
890 Certificate interface{}
891 MaxConnections int
892}
893
894func (config WebhookConfig) method() string {
895 return "setWebhook"
896}
897
898func (config WebhookConfig) values() (url.Values, error) {
899 v := url.Values{}
900
901 if config.URL != nil {
902 v.Add("url", config.URL.String())
903 }
904 if config.MaxConnections != 0 {
905 v.Add("max_connections", strconv.Itoa(config.MaxConnections))
906 }
907
908 return v, nil
909}
910
911func (config WebhookConfig) params() (map[string]string, error) {
912 params := make(map[string]string)
913
914 if config.URL != nil {
915 params["url"] = config.URL.String()
916 }
917 if config.MaxConnections != 0 {
918 params["max_connections"] = strconv.Itoa(config.MaxConnections)
919 }
920
921 return params, nil
922}
923
924func (config WebhookConfig) name() string {
925 return "certificate"
926}
927
928func (config WebhookConfig) getFile() interface{} {
929 return config.Certificate
930}
931
932func (config WebhookConfig) useExistingFile() bool {
933 return config.URL != nil
934}
935
936// RemoveWebhookConfig is a helper to remove a webhook.
937type RemoveWebhookConfig struct {
938}
939
940func (config RemoveWebhookConfig) method() string {
941 return "setWebhook"
942}
943
944func (config RemoveWebhookConfig) values() (url.Values, error) {
945 return url.Values{}, nil
946}
947
948// FileBytes contains information about a set of bytes to upload
949// as a File.
950type FileBytes struct {
951 Name string
952 Bytes []byte
953}
954
955// FileReader contains information about a reader to upload as a File.
956// If Size is -1, it will read the entire Reader into memory to
957// calculate a Size.
958type FileReader struct {
959 Name string
960 Reader io.Reader
961 Size int64
962}
963
964// InlineConfig contains information on making an InlineQuery response.
965type InlineConfig struct {
966 InlineQueryID string `json:"inline_query_id"`
967 Results []interface{} `json:"results"`
968 CacheTime int `json:"cache_time"`
969 IsPersonal bool `json:"is_personal"`
970 NextOffset string `json:"next_offset"`
971 SwitchPMText string `json:"switch_pm_text"`
972 SwitchPMParameter string `json:"switch_pm_parameter"`
973}
974
975func (config InlineConfig) method() string {
976 return "answerInlineQuery"
977}
978
979func (config InlineConfig) values() (url.Values, error) {
980 v := url.Values{}
981
982 v.Add("inline_query_id", config.InlineQueryID)
983 v.Add("cache_time", strconv.Itoa(config.CacheTime))
984 v.Add("is_personal", strconv.FormatBool(config.IsPersonal))
985 v.Add("next_offset", config.NextOffset)
986 data, err := json.Marshal(config.Results)
987 if err != nil {
988 return v, err
989 }
990 v.Add("results", string(data))
991 v.Add("switch_pm_text", config.SwitchPMText)
992 v.Add("switch_pm_parameter", config.SwitchPMParameter)
993
994 return v, nil
995}
996
997// CallbackConfig contains information on making a CallbackQuery response.
998type CallbackConfig struct {
999 CallbackQueryID string `json:"callback_query_id"`
1000 Text string `json:"text"`
1001 ShowAlert bool `json:"show_alert"`
1002 URL string `json:"url"`
1003 CacheTime int `json:"cache_time"`
1004}
1005
1006func (config CallbackConfig) method() string {
1007 return "answerCallbackQuery"
1008}
1009
1010func (config CallbackConfig) values() (url.Values, error) {
1011 v := url.Values{}
1012
1013 v.Add("callback_query_id", config.CallbackQueryID)
1014 if config.Text != "" {
1015 v.Add("text", config.Text)
1016 }
1017 v.Add("show_alert", strconv.FormatBool(config.ShowAlert))
1018 if config.URL != "" {
1019 v.Add("url", config.URL)
1020 }
1021 v.Add("cache_time", strconv.Itoa(config.CacheTime))
1022
1023 return v, nil
1024}
1025
1026// ChatMemberConfig contains information about a user in a chat for use
1027// with administrative functions such as kicking or unbanning a user.
1028type ChatMemberConfig struct {
1029 ChatID int64
1030 SuperGroupUsername string
1031 ChannelUsername string
1032 UserID int
1033}
1034
1035// UnbanChatMemberConfig allows you to unban a user.
1036type UnbanChatMemberConfig struct {
1037 ChatMemberConfig
1038}
1039
1040func (config UnbanChatMemberConfig) method() string {
1041 return "unbanChatMember"
1042}
1043
1044func (config UnbanChatMemberConfig) values() (url.Values, error) {
1045 v := url.Values{}
1046
1047 if config.SuperGroupUsername != "" {
1048 v.Add("chat_id", config.SuperGroupUsername)
1049 } else if config.ChannelUsername != "" {
1050 v.Add("chat_id", config.ChannelUsername)
1051 } else {
1052 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1053 }
1054 v.Add("user_id", strconv.Itoa(config.UserID))
1055
1056 return v, nil
1057}
1058
1059// KickChatMemberConfig contains extra fields to kick user
1060type KickChatMemberConfig struct {
1061 ChatMemberConfig
1062 UntilDate int64
1063}
1064
1065func (config KickChatMemberConfig) method() string {
1066 return "kickChatMember"
1067}
1068
1069func (config KickChatMemberConfig) values() (url.Values, error) {
1070 v := url.Values{}
1071
1072 if config.SuperGroupUsername == "" {
1073 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1074 } else {
1075 v.Add("chat_id", config.SuperGroupUsername)
1076 }
1077 v.Add("user_id", strconv.Itoa(config.UserID))
1078
1079 if config.UntilDate != 0 {
1080 v.Add("until_date", strconv.FormatInt(config.UntilDate, 10))
1081 }
1082
1083 return v, nil
1084}
1085
1086// RestrictChatMemberConfig contains fields to restrict members of chat
1087type RestrictChatMemberConfig struct {
1088 ChatMemberConfig
1089 UntilDate int64
1090 CanSendMessages *bool
1091 CanSendMediaMessages *bool
1092 CanSendOtherMessages *bool
1093 CanAddWebPagePreviews *bool
1094}
1095
1096func (config RestrictChatMemberConfig) method() string {
1097 return "restrictChatMember"
1098}
1099
1100func (config RestrictChatMemberConfig) values() (url.Values, error) {
1101 v := url.Values{}
1102
1103 if config.SuperGroupUsername != "" {
1104 v.Add("chat_id", config.SuperGroupUsername)
1105 } else if config.ChannelUsername != "" {
1106 v.Add("chat_id", config.ChannelUsername)
1107 } else {
1108 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1109 }
1110 v.Add("user_id", strconv.Itoa(config.UserID))
1111
1112 if config.CanSendMessages != nil {
1113 v.Add("can_send_messages", strconv.FormatBool(*config.CanSendMessages))
1114 }
1115 if config.CanSendMediaMessages != nil {
1116 v.Add("can_send_media_messages", strconv.FormatBool(*config.CanSendMediaMessages))
1117 }
1118 if config.CanSendOtherMessages != nil {
1119 v.Add("can_send_other_messages", strconv.FormatBool(*config.CanSendOtherMessages))
1120 }
1121 if config.CanAddWebPagePreviews != nil {
1122 v.Add("can_add_web_page_previews", strconv.FormatBool(*config.CanAddWebPagePreviews))
1123 }
1124 if config.UntilDate != 0 {
1125 v.Add("until_date", strconv.FormatInt(config.UntilDate, 10))
1126 }
1127
1128 return v, nil
1129}
1130
1131// PromoteChatMemberConfig contains fields to promote members of chat
1132type PromoteChatMemberConfig struct {
1133 ChatMemberConfig
1134 CanChangeInfo *bool
1135 CanPostMessages *bool
1136 CanEditMessages *bool
1137 CanDeleteMessages *bool
1138 CanInviteUsers *bool
1139 CanRestrictMembers *bool
1140 CanPinMessages *bool
1141 CanPromoteMembers *bool
1142}
1143
1144func (config PromoteChatMemberConfig) method() string {
1145 return "promoteChatMember"
1146}
1147
1148func (config PromoteChatMemberConfig) values() (url.Values, error) {
1149 v := url.Values{}
1150
1151 if config.SuperGroupUsername != "" {
1152 v.Add("chat_id", config.SuperGroupUsername)
1153 } else if config.ChannelUsername != "" {
1154 v.Add("chat_id", config.ChannelUsername)
1155 } else {
1156 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1157 }
1158 v.Add("user_id", strconv.Itoa(config.UserID))
1159
1160 if config.CanChangeInfo != nil {
1161 v.Add("can_change_info", strconv.FormatBool(*config.CanChangeInfo))
1162 }
1163 if config.CanPostMessages != nil {
1164 v.Add("can_post_messages", strconv.FormatBool(*config.CanPostMessages))
1165 }
1166 if config.CanEditMessages != nil {
1167 v.Add("can_edit_messages", strconv.FormatBool(*config.CanEditMessages))
1168 }
1169 if config.CanDeleteMessages != nil {
1170 v.Add("can_delete_messages", strconv.FormatBool(*config.CanDeleteMessages))
1171 }
1172 if config.CanInviteUsers != nil {
1173 v.Add("can_invite_users", strconv.FormatBool(*config.CanInviteUsers))
1174 }
1175 if config.CanRestrictMembers != nil {
1176 v.Add("can_restrict_members", strconv.FormatBool(*config.CanRestrictMembers))
1177 }
1178 if config.CanPinMessages != nil {
1179 v.Add("can_pin_messages", strconv.FormatBool(*config.CanPinMessages))
1180 }
1181 if config.CanPromoteMembers != nil {
1182 v.Add("can_promote_members", strconv.FormatBool(*config.CanPromoteMembers))
1183 }
1184
1185 return v, nil
1186}
1187
1188// ChatConfig contains information about getting information on a chat.
1189type ChatConfig struct {
1190 ChatID int64
1191 SuperGroupUsername string
1192}
1193
1194// LeaveChatConfig allows you to leave a chat.
1195type LeaveChatConfig struct {
1196 ChatID int64
1197 ChannelUsername string
1198}
1199
1200func (config LeaveChatConfig) method() string {
1201 return "leaveChat"
1202}
1203
1204func (config LeaveChatConfig) values() (url.Values, error) {
1205 v := url.Values{}
1206
1207 if config.ChannelUsername == "" {
1208 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1209 } else {
1210 v.Add("chat_id", config.ChannelUsername)
1211 }
1212
1213 return v, nil
1214}
1215
1216// ChatConfigWithUser contains information about getting information on
1217// a specific user within a chat.
1218type ChatConfigWithUser struct {
1219 ChatID int64
1220 SuperGroupUsername string
1221 UserID int
1222}
1223
1224// InvoiceConfig contains information for sendInvoice request.
1225type InvoiceConfig struct {
1226 BaseChat
1227 Title string // required
1228 Description string // required
1229 Payload string // required
1230 ProviderToken string // required
1231 StartParameter string // required
1232 Currency string // required
1233 Prices *[]LabeledPrice // required
1234 PhotoURL string
1235 PhotoSize int
1236 PhotoWidth int
1237 PhotoHeight int
1238 NeedName bool
1239 NeedPhoneNumber bool
1240 NeedEmail bool
1241 NeedShippingAddress bool
1242 IsFlexible bool
1243}
1244
1245func (config InvoiceConfig) values() (url.Values, error) {
1246 v, err := config.BaseChat.values()
1247 if err != nil {
1248 return v, err
1249 }
1250 v.Add("title", config.Title)
1251 v.Add("description", config.Description)
1252 v.Add("payload", config.Payload)
1253 v.Add("provider_token", config.ProviderToken)
1254 v.Add("start_parameter", config.StartParameter)
1255 v.Add("currency", config.Currency)
1256 data, err := json.Marshal(config.Prices)
1257 if err != nil {
1258 return v, err
1259 }
1260 v.Add("prices", string(data))
1261 if config.PhotoURL != "" {
1262 v.Add("photo_url", config.PhotoURL)
1263 }
1264 if config.PhotoSize != 0 {
1265 v.Add("photo_size", strconv.Itoa(config.PhotoSize))
1266 }
1267 if config.PhotoWidth != 0 {
1268 v.Add("photo_width", strconv.Itoa(config.PhotoWidth))
1269 }
1270 if config.PhotoHeight != 0 {
1271 v.Add("photo_height", strconv.Itoa(config.PhotoHeight))
1272 }
1273 if config.NeedName != false {
1274 v.Add("need_name", strconv.FormatBool(config.NeedName))
1275 }
1276 if config.NeedPhoneNumber != false {
1277 v.Add("need_phone_number", strconv.FormatBool(config.NeedPhoneNumber))
1278 }
1279 if config.NeedEmail != false {
1280 v.Add("need_email", strconv.FormatBool(config.NeedEmail))
1281 }
1282 if config.NeedShippingAddress != false {
1283 v.Add("need_shipping_address", strconv.FormatBool(config.NeedShippingAddress))
1284 }
1285 if config.IsFlexible != false {
1286 v.Add("is_flexible", strconv.FormatBool(config.IsFlexible))
1287 }
1288
1289 return v, nil
1290}
1291
1292func (config InvoiceConfig) method() string {
1293 return "sendInvoice"
1294}
1295
1296// ShippingConfig contains information for answerShippingQuery request.
1297type ShippingConfig struct {
1298 ShippingQueryID string // required
1299 OK bool // required
1300 ShippingOptions *[]ShippingOption
1301 ErrorMessage string
1302}
1303
1304// PreCheckoutConfig conatins information for answerPreCheckoutQuery request.
1305type PreCheckoutConfig struct {
1306 PreCheckoutQueryID string // required
1307 OK bool // required
1308 ErrorMessage string
1309}
1310
1311// DeleteMessageConfig contains information of a message in a chat to delete.
1312type DeleteMessageConfig struct {
1313 ChatID int64
1314 MessageID int
1315}
1316
1317func (config DeleteMessageConfig) method() string {
1318 return "deleteMessage"
1319}
1320
1321func (config DeleteMessageConfig) values() (url.Values, error) {
1322 v := url.Values{}
1323
1324 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1325 v.Add("message_id", strconv.Itoa(config.MessageID))
1326
1327 return v, nil
1328}
1329
1330// PinChatMessageConfig contains information of a message in a chat to pin.
1331type PinChatMessageConfig struct {
1332 ChatID int64
1333 MessageID int
1334 DisableNotification bool
1335}
1336
1337func (config PinChatMessageConfig) method() string {
1338 return "pinChatMessage"
1339}
1340
1341func (config PinChatMessageConfig) values() (url.Values, error) {
1342 v := url.Values{}
1343
1344 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1345 v.Add("message_id", strconv.Itoa(config.MessageID))
1346 v.Add("disable_notification", strconv.FormatBool(config.DisableNotification))
1347
1348 return v, nil
1349}
1350
1351// UnpinChatMessageConfig contains information of chat to unpin.
1352type UnpinChatMessageConfig struct {
1353 ChatID int64
1354}
1355
1356func (config UnpinChatMessageConfig) method() string {
1357 return "unpinChatMessage"
1358}
1359
1360func (config UnpinChatMessageConfig) values() (url.Values, error) {
1361 v := url.Values{}
1362
1363 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1364
1365 return v, nil
1366}
1367
1368// SetChatPhotoConfig allows you to set a group, supergroup, or channel's photo.
1369type SetChatPhotoConfig struct {
1370 ChatID int64
1371 ChannelUsername string
1372
1373 Photo interface{}
1374}
1375
1376func (config SetChatPhotoConfig) method() string {
1377 return "setChatPhoto"
1378}
1379
1380func (config SetChatPhotoConfig) name() string {
1381 return "photo"
1382}
1383
1384func (config SetChatPhotoConfig) values() (url.Values, error) {
1385 v := url.Values{}
1386
1387 if config.ChannelUsername == "" {
1388 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1389 } else {
1390 v.Add("chat_id", config.ChannelUsername)
1391 }
1392
1393 return v, nil
1394}
1395
1396func (config SetChatPhotoConfig) params() map[string]string {
1397 params := make(map[string]string)
1398
1399 if config.ChannelUsername == "" {
1400 params["chat_id"] = strconv.FormatInt(config.ChatID, 10)
1401 } else {
1402 params["chat_id"] = config.ChannelUsername
1403 }
1404
1405 return params
1406}
1407
1408func (config SetChatPhotoConfig) getFile() interface{} {
1409 return config.Photo
1410}
1411
1412func (config SetChatPhotoConfig) useExistingFile() bool {
1413 return false
1414}
1415
1416// DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo.
1417type DeleteChatPhotoConfig struct {
1418 ChatID int64
1419 ChannelUsername string
1420}
1421
1422func (config DeleteChatPhotoConfig) method() string {
1423 return "deleteChatPhoto"
1424}
1425
1426func (config DeleteChatPhotoConfig) values() (url.Values, error) {
1427 v := url.Values{}
1428
1429 if config.ChannelUsername == "" {
1430 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1431 } else {
1432 v.Add("chat_id", config.ChannelUsername)
1433 }
1434
1435 return v, nil
1436}
1437
1438// SetChatTitleConfig allows you to set the title of something other than a private chat.
1439type SetChatTitleConfig struct {
1440 ChatID int64
1441 ChannelUsername string
1442
1443 Title string
1444}
1445
1446func (config SetChatTitleConfig) method() string {
1447 return "setChatTitle"
1448}
1449
1450func (config SetChatTitleConfig) values() (url.Values, error) {
1451 v := url.Values{}
1452
1453 if config.ChannelUsername == "" {
1454 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1455 } else {
1456 v.Add("chat_id", config.ChannelUsername)
1457 }
1458
1459 v.Add("title", config.Title)
1460
1461 return v, nil
1462}
1463
1464// SetChatDescriptionConfig allows you to set the description of a supergroup or channel.
1465type SetChatDescriptionConfig struct {
1466 ChatID int64
1467 ChannelUsername string
1468
1469 Description string
1470}
1471
1472func (config SetChatDescriptionConfig) method() string {
1473 return "setChatDescription"
1474}
1475
1476func (config SetChatDescriptionConfig) values() (url.Values, error) {
1477 v := url.Values{}
1478
1479 if config.ChannelUsername == "" {
1480 v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
1481 } else {
1482 v.Add("chat_id", config.ChannelUsername)
1483 }
1484
1485 v.Add("description", config.Description)
1486
1487 return v, nil
1488}
1489
1490// GetStickerSetConfig allows you to get the stickers in a set.
1491type GetStickerSetConfig struct {
1492 Name string
1493}
1494
1495func (config GetStickerSetConfig) method() string {
1496 return "getStickerSet"
1497}
1498
1499func (config GetStickerSetConfig) values() (url.Values, error) {
1500 v := url.Values{}
1501
1502 v.Add("name", config.Name)
1503
1504 return v, nil
1505}
1506
1507// UploadStickerConfig allows you to upload a sticker for use in a set later.
1508type UploadStickerConfig struct {
1509 UserID int64
1510 PNGSticker interface{}
1511}
1512
1513func (config UploadStickerConfig) method() string {
1514 return "uploadStickerFile"
1515}
1516
1517func (config UploadStickerConfig) values() (url.Values, error) {
1518 v := url.Values{}
1519
1520 v.Add("user_id", strconv.FormatInt(config.UserID, 10))
1521
1522 return v, nil
1523}
1524
1525func (config UploadStickerConfig) params() (map[string]string, error) {
1526 params := make(map[string]string)
1527
1528 params["user_id"] = strconv.FormatInt(config.UserID, 10)
1529
1530 return params, nil
1531}
1532
1533func (config UploadStickerConfig) name() string {
1534 return "png_sticker"
1535}
1536
1537func (config UploadStickerConfig) getFile() interface{} {
1538 return config.PNGSticker
1539}
1540
1541func (config UploadStickerConfig) useExistingFile() bool {
1542 return false
1543}
1544
1545// NewStickerSetConfig allows creating a new sticker set.
1546type NewStickerSetConfig struct {
1547 UserID int64
1548 Name string
1549 Title string
1550 PNGSticker interface{}
1551 Emojis string
1552 ContainsMasks bool
1553 MaskPosition *MaskPosition
1554}
1555
1556func (config NewStickerSetConfig) method() string {
1557 return "createNewStickerSet"
1558}
1559
1560func (config NewStickerSetConfig) values() (url.Values, error) {
1561 v := url.Values{}
1562
1563 v.Add("user_id", strconv.FormatInt(config.UserID, 10))
1564 v.Add("name", config.Name)
1565 v.Add("title", config.Title)
1566 if sticker, ok := config.PNGSticker.(string); ok {
1567 v.Add("png_sticker", sticker)
1568 }
1569 v.Add("emojis", config.Emojis)
1570 if config.ContainsMasks {
1571 v.Add("contains_masks", strconv.FormatBool(config.ContainsMasks))
1572
1573 data, err := json.Marshal(config.MaskPosition)
1574 if err != nil {
1575 return v, err
1576 }
1577
1578 v.Add("mask_position", string(data))
1579 }
1580
1581 return v, nil
1582}
1583
1584func (config NewStickerSetConfig) params() (map[string]string, error) {
1585 params := make(map[string]string)
1586
1587 params["user_id"] = strconv.FormatInt(config.UserID, 10)
1588 params["name"] = config.Name
1589 params["title"] = config.Title
1590 params["emojis"] = config.Emojis
1591 if config.ContainsMasks {
1592 params["contains_masks"] = strconv.FormatBool(config.ContainsMasks)
1593
1594 data, err := json.Marshal(config.MaskPosition)
1595 if err != nil {
1596 return params, err
1597 }
1598
1599 params["mask_position"] = string(data)
1600 }
1601
1602 return params, nil
1603}
1604
1605func (config NewStickerSetConfig) getFile() interface{} {
1606 return config.PNGSticker
1607}
1608
1609func (config NewStickerSetConfig) name() string {
1610 return "png_sticker"
1611}
1612
1613func (config NewStickerSetConfig) useExistingFile() bool {
1614 _, ok := config.PNGSticker.(string)
1615
1616 return ok
1617}
1618
1619// AddStickerConfig allows you to add a sticker to a set.
1620type AddStickerConfig struct {
1621 UserID int64
1622 Name string
1623 PNGSticker interface{}
1624 Emojis string
1625 MaskPosition *MaskPosition
1626}
1627
1628func (config AddStickerConfig) method() string {
1629 return "addStickerToSet"
1630}
1631
1632func (config AddStickerConfig) values() (url.Values, error) {
1633 v := url.Values{}
1634
1635 v.Add("user_id", strconv.FormatInt(config.UserID, 10))
1636 v.Add("name", config.Name)
1637 if sticker, ok := config.PNGSticker.(string); ok {
1638 v.Add("png_sticker", sticker)
1639 }
1640 v.Add("emojis", config.Emojis)
1641 if config.MaskPosition != nil {
1642 data, err := json.Marshal(config.MaskPosition)
1643 if err != nil {
1644 return v, err
1645 }
1646
1647 v.Add("mask_position", string(data))
1648 }
1649
1650 return v, nil
1651}
1652
1653func (config AddStickerConfig) params() (map[string]string, error) {
1654 params := make(map[string]string)
1655
1656 params["user_id"] = strconv.FormatInt(config.UserID, 10)
1657 params["name"] = config.Name
1658 params["emojis"] = config.Emojis
1659 if config.MaskPosition != nil {
1660 data, err := json.Marshal(config.MaskPosition)
1661 if err != nil {
1662 return params, err
1663 }
1664
1665 params["mask_position"] = string(data)
1666 }
1667
1668 return params, nil
1669}
1670
1671func (config AddStickerConfig) name() string {
1672 return "png_sticker"
1673}
1674
1675func (config AddStickerConfig) getFile() interface{} {
1676 return config.PNGSticker
1677}
1678
1679func (config AddStickerConfig) useExistingFile() bool {
1680 return false
1681}
1682
1683// SetStickerPositionConfig allows you to change the position of a sticker in a set.
1684type SetStickerPositionConfig struct {
1685 Sticker string
1686 Position int
1687}
1688
1689func (config SetStickerPositionConfig) method() string {
1690 return "setStickerPositionInSet"
1691}
1692
1693func (config SetStickerPositionConfig) values() (url.Values, error) {
1694 v := url.Values{}
1695
1696 v.Add("sticker", config.Sticker)
1697 v.Add("position", strconv.Itoa(config.Position))
1698
1699 return v, nil
1700}
1701
1702// DeleteStickerConfig allows you to delete a sticker from a set.
1703type DeleteStickerConfig struct {
1704 Sticker string
1705}
1706
1707func (config DeleteStickerConfig) method() string {
1708 return "deleteStickerFromSet"
1709}
1710
1711func (config DeleteStickerConfig) values() (url.Values, error) {
1712 v := url.Values{}
1713
1714 v.Add("sticker", config.Sticker)
1715
1716 return v, nil
1717}