From 6205cea402341a9a968d884a545321053f5ca291 Mon Sep 17 00:00:00 2001 From: Arman Date: Sat, 10 Mar 2018 01:52:21 +0330 Subject: [PATCH 01/17] added StopReceivingUpdates to stop updates routine There was no way to stop the bot from fetching updates, Added StopReceivingUpdates() so we can stop updates when we are done with bot. --- bot.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bot.go b/bot.go index ee1ddd0..8e34250 100644 --- a/bot.go +++ b/bot.go @@ -28,6 +28,7 @@ type BotAPI struct { Self User `json:"-"` Client *http.Client `json:"-"` + shutdownChannel chan interface{} } // NewBotAPI creates a new BotAPI instance. @@ -46,6 +47,7 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { Token: token, Client: client, Buffer: 100, + shutdownChannel: make(chan interface{}), } self, err := bot.GetMe() @@ -480,6 +482,12 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { go func() { for { + select { + case <-bot.shutdownChannel: + return + default: + } + updates, err := bot.GetUpdates(config) if err != nil { log.Println(err) @@ -501,6 +509,14 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { return ch, nil } +// StopReceivingUpdates stops the go routine which receives updates +func (bot *BotAPI) StopReceivingUpdates() { + if bot.Debug { + log.Println("Stopping the update receiver routine...") + } + close(bot.shutdownChannel) +} + // ListenForWebhook registers a http handler for a webhook. func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { ch := make(chan Update, bot.Buffer) From 0e0af0c480ea98e982d5f4d45fb39577c6ab1e3e Mon Sep 17 00:00:00 2001 From: Kirill Zhuharev Date: Wed, 28 Mar 2018 16:10:29 +0300 Subject: [PATCH 02/17] fix #159 --- bot.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bot.go b/bot.go index 1a3b7fd..8fb6200 100644 --- a/bot.go +++ b/bot.go @@ -731,16 +731,16 @@ func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIRespo } v.Add("user_id", strconv.Itoa(config.UserID)) - if &config.CanSendMessages != nil { + if config.CanSendMessages != nil { v.Add("can_send_messages", strconv.FormatBool(*config.CanSendMessages)) } - if &config.CanSendMediaMessages != nil { + if config.CanSendMediaMessages != nil { v.Add("can_send_media_messages", strconv.FormatBool(*config.CanSendMediaMessages)) } - if &config.CanSendOtherMessages != nil { + if config.CanSendOtherMessages != nil { v.Add("can_send_other_messages", strconv.FormatBool(*config.CanSendOtherMessages)) } - if &config.CanAddWebPagePreviews != nil { + if config.CanAddWebPagePreviews != nil { v.Add("can_add_web_page_previews", strconv.FormatBool(*config.CanAddWebPagePreviews)) } if config.UntilDate != 0 { @@ -765,28 +765,28 @@ func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIRespons } v.Add("user_id", strconv.Itoa(config.UserID)) - if &config.CanChangeInfo != nil { + if config.CanChangeInfo != nil { v.Add("can_change_info", strconv.FormatBool(*config.CanChangeInfo)) } - if &config.CanPostMessages != nil { + if config.CanPostMessages != nil { v.Add("can_post_messages", strconv.FormatBool(*config.CanPostMessages)) } - if &config.CanEditMessages != nil { + if config.CanEditMessages != nil { v.Add("can_edit_messages", strconv.FormatBool(*config.CanEditMessages)) } - if &config.CanDeleteMessages != nil { + if config.CanDeleteMessages != nil { v.Add("can_delete_messages", strconv.FormatBool(*config.CanDeleteMessages)) } - if &config.CanInviteUsers != nil { + if config.CanInviteUsers != nil { v.Add("can_invite_users", strconv.FormatBool(*config.CanInviteUsers)) } - if &config.CanRestrictMembers != nil { + if config.CanRestrictMembers != nil { v.Add("can_restrict_members", strconv.FormatBool(*config.CanRestrictMembers)) } - if &config.CanPinMessages != nil { + if config.CanPinMessages != nil { v.Add("can_pin_messages", strconv.FormatBool(*config.CanPinMessages)) } - if &config.CanPromoteMembers != nil { + if config.CanPromoteMembers != nil { v.Add("can_promote_members", strconv.FormatBool(*config.CanPromoteMembers)) } From 32775370ac1cdef44f66c8da5537b751adaa0567 Mon Sep 17 00:00:00 2001 From: Jianqiu Zhang Date: Sun, 22 Apr 2018 18:40:58 +0800 Subject: [PATCH 03/17] Add NewDeleteMessage Signed-off-by: Jianqiu Zhang --- helpers.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/helpers.go b/helpers.go index 476eeeb..25f2fc1 100644 --- a/helpers.go +++ b/helpers.go @@ -19,6 +19,13 @@ func NewMessage(chatID int64, text string) MessageConfig { } } +func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig { + return DeleteMessageConfig{ + ChatID: chatID, + MessageID: messageID, + } +} + // NewMessageToChannel creates a new Message that is sent to a channel // by username. // From 25ef323a6b7520dae3a7901a8603fafcf6ac3f47 Mon Sep 17 00:00:00 2001 From: Emanuele Rocco Petrone Date: Sat, 28 Apr 2018 00:31:17 +0200 Subject: [PATCH 04/17] Parse sticker pack name --- types.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types.go b/types.go index 4a58287..e64132f 100644 --- a/types.go +++ b/types.go @@ -290,6 +290,7 @@ type Sticker struct { Thumbnail *PhotoSize `json:"thumb"` // optional Emoji string `json:"emoji"` // optional FileSize int `json:"file_size"` // optional + SetName string `json:"set_name"` // optional } // Video contains information about a video. @@ -410,7 +411,7 @@ type InlineKeyboardButton struct { SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional CallbackGame *CallbackGame `json:"callback_game,omitempty"` // optional - Pay bool `json:"pay,omitempty"` // optional + Pay bool `json:"pay,omitempty"` // optional } // CallbackQuery is data sent when a keyboard button with callback data From 60668682f7b5f75cdc0abde06b9fb00f30783570 Mon Sep 17 00:00:00 2001 From: CssHammer Date: Wed, 23 May 2018 22:12:20 +0300 Subject: [PATCH 05/17] Add ParseMode to PhotoConfig --- configs.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/configs.go b/configs.go index 574b3dd..bcc850d 100644 --- a/configs.go +++ b/configs.go @@ -243,7 +243,8 @@ func (config ForwardConfig) method() string { // PhotoConfig contains information about a SendPhoto request. type PhotoConfig struct { BaseFile - Caption string + Caption string + ParseMode string } // Params returns a map[string]string representation of PhotoConfig. @@ -253,6 +254,9 @@ func (config PhotoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption } + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } return params, nil } @@ -268,6 +272,9 @@ func (config PhotoConfig) values() (url.Values, error) { if config.Caption != "" { v.Add("caption", config.Caption) } + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } return v, nil } From 0b68c9b7906ce2b3af7c9a42053f32c286b17801 Mon Sep 17 00:00:00 2001 From: Oleksandr Savchuk Date: Mon, 30 Jul 2018 19:53:11 +0300 Subject: [PATCH 06/17] add full support of parse mode in captions - parse_mode of caption support in AudioConfig - parse_mode of caption support in DocumentConfig - parse_mode of caption support in VideoConfig - parse_mode of caption support in VoiceConfig - in PhotoConfig add parse_mode only if caption is not empty --- configs.go | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/configs.go b/configs.go index bcc850d..e316ceb 100644 --- a/configs.go +++ b/configs.go @@ -253,9 +253,9 @@ func (config PhotoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption - } - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -271,10 +271,11 @@ func (config PhotoConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) - } + return v, nil } @@ -292,6 +293,7 @@ func (config PhotoConfig) method() string { type AudioConfig struct { BaseFile Caption string + ParseMode string Duration int Performer string Title string @@ -317,6 +319,9 @@ func (config AudioConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -338,6 +343,9 @@ func (config AudioConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -356,7 +364,8 @@ func (config AudioConfig) method() string { // DocumentConfig contains information about a SendDocument request. type DocumentConfig struct { BaseFile - Caption string + Caption string + ParseMode string } // values returns a url.Values representation of DocumentConfig. @@ -369,6 +378,9 @@ func (config DocumentConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -380,6 +392,9 @@ func (config DocumentConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -432,8 +447,9 @@ func (config StickerConfig) method() string { // VideoConfig contains information about a SendVideo request. type VideoConfig struct { BaseFile - Duration int - Caption string + Duration int + Caption string + ParseMode string } // values returns a url.Values representation of VideoConfig. @@ -449,6 +465,9 @@ func (config VideoConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -460,6 +479,9 @@ func (config VideoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -529,8 +551,9 @@ func (config VideoNoteConfig) method() string { // VoiceConfig contains information about a SendVoice request. type VoiceConfig struct { BaseFile - Caption string - Duration int + Caption string + ParseMode string + Duration int } // values returns a url.Values representation of VoiceConfig. @@ -546,6 +569,9 @@ func (config VoiceConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -560,6 +586,9 @@ func (config VoiceConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil From af72b0a3dbea69429d85ead3ccc39e22f4b83eef Mon Sep 17 00:00:00 2001 From: Oleksandr Savchuk Date: Mon, 30 Jul 2018 20:00:09 +0300 Subject: [PATCH 07/17] add support of parse_mode in EditMessageCaptionConfig --- configs.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/configs.go b/configs.go index e316ceb..a750182 100644 --- a/configs.go +++ b/configs.go @@ -822,13 +822,17 @@ func (config EditMessageTextConfig) method() string { // EditMessageCaptionConfig allows you to modify the caption of a message. type EditMessageCaptionConfig struct { BaseEdit - Caption string + Caption string + ParseMode string } func (config EditMessageCaptionConfig) values() (url.Values, error) { v, _ := config.BaseEdit.values() v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } return v, nil } From a1b3e4187891a214fb8d67ed937444145d569d23 Mon Sep 17 00:00:00 2001 From: Yan Mihailov Date: Tue, 7 Aug 2018 00:15:37 +0300 Subject: [PATCH 08/17] added custom logger and SetLogger func --- bot.go | 1 - helpers.go | 1 - log.go | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 log.go diff --git a/bot.go b/bot.go index 8fb6200..35d1e25 100644 --- a/bot.go +++ b/bot.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "os" diff --git a/helpers.go b/helpers.go index b5480ea..f04a0a7 100644 --- a/helpers.go +++ b/helpers.go @@ -1,7 +1,6 @@ package tgbotapi import ( - "log" "net/url" ) diff --git a/log.go b/log.go new file mode 100644 index 0000000..18e339f --- /dev/null +++ b/log.go @@ -0,0 +1,17 @@ +package tgbotapi + +import ( + "os" + "errors" + stdlog "log" +) + +var log = stdlog.New(os.Stderr, "", stdlog.LstdFlags) + +func SetLogger(newLog *stdlog.Logger) error { + if newLog == nil { + return errors.New("logger is nil") + } + log = newLog + return nil +} From ca185227e57755c4e0944e61e8d584a721c9e64d Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Sun, 26 Aug 2018 18:52:52 +0200 Subject: [PATCH 09/17] Test in up-to-date Go versions --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8408fb7..dc570dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: go go: - - 1.4 - - tip \ No newline at end of file + - '1.4' + - '1.10' + - '1.11' + - tip From 483f21c48bebae648c4464138be96b1344151035 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Mon, 27 Aug 2018 22:44:02 +0200 Subject: [PATCH 10/17] Remove go1.4 from travis configuration --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dc570dc..5769aa1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - '1.4' - '1.10' - '1.11' - tip From 2326345451850fb4504d464e365d1b5490bc5072 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Tue, 4 Sep 2018 10:52:52 -0700 Subject: [PATCH 11/17] Passport Updates (#1) --- .gitignore | 1 + passport.go | 322 ++++++++++++++++++++++++++++++++++++++++++++++++++++ types.go | 1 + 3 files changed, 324 insertions(+) create mode 100644 passport.go diff --git a/.gitignore b/.gitignore index aa7ac80..fb5a5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ coverage.out +tmp/ diff --git a/passport.go b/passport.go new file mode 100644 index 0000000..9afbcf1 --- /dev/null +++ b/passport.go @@ -0,0 +1,322 @@ +package tgbotapi + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" +) + +// PassportRequestInfoConfig allows you to request passport info +type PassportRequestInfoConfig struct { + BotID int `json:"bot_id"` + Scope *PassportScope `json:"scope"` + Nonce string `json:"nonce"` + PublicKey string `json:"public_key"` +} + +func LinkToPassportRequest(config PassportRequestInfoConfig) (string, error) { + scope, err := json.Marshal(config.Scope) + if err != nil { + panic("couldn't pack scope") + } + tgurl := fmt.Sprintf("tg://resolve?domain=telegrampassport&bot_id=%v&scope=%v&public_key=%v&nonce=%v", + strconv.Itoa(config.BotID), + url.PathEscape(string(scope)), + url.PathEscape(config.PublicKey), + url.PathEscape(config.Nonce), + ) + fmt.Println(tgurl) + + return tgurl, nil +} + +type PassportScopeElement interface { + ScopeType() string +} +type PassportScope struct { + V int `json:"v"` + Data []PassportScopeElement `json:"data"` +} + +type PassportScopeElementOneOfSeveral struct { +} + +func (eo *PassportScopeElementOneOfSeveral) ScopeType() string { + return "one_of" +} + +type PassportScopeElementOne struct { + Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email” + Selfie bool `json:"selfie"` + Translation bool `json:"translation"` + NativeNames bool `json:"native_name"` +} + +func (eo *PassportScopeElementOne) ScopeType() string { + return "one" +} + +type ( + // PassportData contains information about Telegram Passport data shared with + // the bot by the user. + PassportData struct { + // Array with information about documents and other Telegram Passport + // elements that was shared with the bot + Data []EncryptedPassportElement `json:"data"` + + // Encrypted credentials required to decrypt the data + Credentials *EncryptedCredentials `json:"credentials"` + } + + // PassportFile represents a file uploaded to Telegram Passport. Currently all + // Telegram Passport files are in JPEG format when decrypted and don't exceed + // 10MB. + PassportFile struct { + // Unique identifier for this file + FileID string `json:"file_id"` + + // File size + FileSize int `json:"file_size"` + + // Unix time when the file was uploaded + FileDate int64 `json:"file_date"` + } + + // EncryptedPassportElement contains information about documents or other + // Telegram Passport elements shared with the bot by the user. + EncryptedPassportElement struct { + // Element type. + Type string `json:"type"` + + // Base64-encoded encrypted Telegram Passport element data provided by + // the user, available for "personal_details", "passport", + // "driver_license", "identity_card", "identity_passport" and "address" + // types. Can be decrypted and verified using the accompanying + // EncryptedCredentials. + Data string `json:"data,omitempty"` + + // User's verified phone number, available only for "phone_number" type + PhoneNumber string `json:"phone_number,omitempty"` + + // User's verified email address, available only for "email" type + Email string `json:"email,omitempty"` + + // Array of encrypted files with documents provided by the user, + // available for "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration" and "temporary_registration" types. Files can + // be decrypted and verified using the accompanying EncryptedCredentials. + Files []PassportFile `json:"files,omitempty"` + + // Encrypted file with the front side of the document, provided by the + // user. Available for "passport", "driver_license", "identity_card" and + // "internal_passport". The file can be decrypted and verified using the + // accompanying EncryptedCredentials. + FrontSide *PassportFile `json:"front_side,omitempty"` + + // Encrypted file with the reverse side of the document, provided by the + // user. Available for "driver_license" and "identity_card". The file can + // be decrypted and verified using the accompanying EncryptedCredentials. + ReverseSide *PassportFile `json:"reverse_side,omitempty"` + + // Encrypted file with the selfie of the user holding a document, + // provided by the user; available for "passport", "driver_license", + // "identity_card" and "internal_passport". The file can be decrypted + // and verified using the accompanying EncryptedCredentials. + Selfie *PassportFile `json:"selfie,omitempty"` + } + + // EncryptedCredentials contains data required for decrypting and + // authenticating EncryptedPassportElement. See the Telegram Passport + // Documentation for a complete description of the data decryption and + // authentication processes. + EncryptedCredentials struct { + // Base64-encoded encrypted JSON-serialized data with unique user's + // payload, data hashes and secrets required for EncryptedPassportElement + // decryption and authentication + Data string `json:"data"` + + // Base64-encoded data hash for data authentication + Hash string `json:"hash"` + + // Base64-encoded secret, encrypted with the bot's public RSA key, + // required for data decryption + Secret string `json:"secret"` + } + + // PassportElementError represents an error in the Telegram Passport element + // which was submitted that should be resolved by the user. + PassportElementError interface{} + + // PassportElementErrorDataField represents an issue in one of the data + // fields that was provided by the user. The error is considered resolved + // when the field's value changes. + PassportElementErrorDataField struct { + // Error source, must be data + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the error, one + // of "personal_details", "passport", "driver_license", "identity_card", + // "internal_passport", "address" + Type string `json:"type"` + + // Name of the data field which has the error + FieldName string `json:"field_name"` + + // Base64-encoded data hash + DataHash string `json:"data_hash"` + + // Error message + Message string `json:"message"` + } + // PassportElementErrorFrontSide represents an issue with the front side of + // a document. The error is considered resolved when the file with the front + // side of the document changes. + PassportElementErrorFrontSide struct { + // Error source, must be front_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the front side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorReverseSide represents an issue with the reverse side + // of a document. The error is considered resolved when the file with reverse + // side of the document changes. + PassportElementErrorReverseSide struct { + // Error source, must be reverse_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "driver_license", "identity_card" + Type string `json:"type"` + + // Base64-encoded hash of the file with the reverse side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorSelfie represents an issue with the selfie with a + // document. The error is considered resolved when the file with the selfie + // changes. + PassportElementErrorSelfie struct { + // Error source, must be selfie + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the selfie + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFile represents an issue with a document scan. The + // error is considered resolved when the file with the document scan changes. + PassportElementErrorFile struct { + // Error source, must be file + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // Base64-encoded file hash + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFiles represents an issue with a list of scans. The + // error is considered resolved when the list of files containing the scans + // changes. + PassportElementErrorFiles struct { + // Error source, must be files + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // List of base64-encoded file hashes + FileHashes []string `json:"file_hashes"` + + // Error message + Message string `json:"message"` + } + + Credentials struct { + Data SecureData `json:"secure_data"` + // Nonce the same nonce given in the request + Nonce string `json:"nonce"` + } + + SecureData map[string]*SecureValue + // PersonalDetails *SecureValue `json:"personal_details"` + // Passport *SecureValue `json:"passport"` + // InternalPassport *SecureValue `json:"internal_passport"` + // DriverLicense *SecureValue `json:"driver_license"` + // IdentityCard *SecureValue `json:"identity_card"` + // Address *SecureValue `json:"address"` + // UtilityBill *SecureValue `json:"utility_bill"` + // BankStatement *SecureValue `json:"bank_statement"` + // RentalAgreement *SecureValue `json:"rental_agreement"` + // PassportRegistration *SecureValue `json:"passport_registration"` + // TemporaryRegistration *SecureValue `json:"temporary_registration"` + + SecureValue struct { + Data *DataCredentials `json:"data"` + FrontSide *FileCredentials `json:"front_side"` + ReverseSide *FileCredentials `json:"reverse_side"` + Selfie *FileCredentials `json:"selfie"` + Translation []*FileCredentials `json:"translation"` + Files []*FileCredentials `json:"files"` + } + DataCredentials struct { + // DataHash checksum of encrypted data + DataHash string `json:"data_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + FileCredentials struct { + // FileHash checksum of encrypted data + FileHash string `json:"file_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + // PersonalDetails https://core.telegram.org/passport#personaldetails + PersonalDetails struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + MiddleName string `json:"middle_name"` + BirthDate string `json:"birth_date"` + Gender string `json:"gender"` + CountryCode string `json:"country_code"` + ResidenceCountryCode string `json:"residence_country_code"` + FirstNameNative string `json:"first_name_native"` + LastNameNative string `json:"last_name_native"` + MiddleNameNative string `json:"middle_name_native"` + } + + // IdDocumentData https://core.telegram.org/passport#iddocumentdata + IDDocumentData struct { + DocumentNumber string `json:"document_no"` + ExpiryDate string `json:"expiry_date"` + } +) diff --git a/types.go b/types.go index 0843ab9..a283334 100644 --- a/types.go +++ b/types.go @@ -167,6 +167,7 @@ type Message struct { PinnedMessage *Message `json:"pinned_message"` // optional Invoice *Invoice `json:"invoice"` // optional SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional + PassportData *PassportData `json:"passport_data,omitempty"` // optional } // Time converts the message timestamp into a Time. From 0343f9ec270a847ed4d276e3ba2ce88e7b1282b9 Mon Sep 17 00:00:00 2001 From: Denis Orlikhin Date: Thu, 6 Sep 2018 15:44:42 +0300 Subject: [PATCH 12/17] Animation type support added --- configs.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ helpers.go | 31 ++++++++++++++++++++++++++++++- types.go | 13 +++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/configs.go b/configs.go index a750182..be12d3a 100644 --- a/configs.go +++ b/configs.go @@ -497,6 +497,59 @@ func (config VideoConfig) method() string { return "sendVideo" } +// AnimationConfig contains information about a SendAnimation request. +type AnimationConfig struct { + BaseFile + Duration int + Caption string + ParseMode string +} + +// values returns a url.Values representation of AnimationConfig. +func (config AnimationConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + v.Add(config.name(), config.FileID) + if config.Duration != 0 { + v.Add("duration", strconv.Itoa(config.Duration)) + } + if config.Caption != "" { + v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } + } + + return v, nil +} + +// params returns a map[string]string representation of AnimationConfig. +func (config AnimationConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Caption != "" { + params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } + } + + return params, nil +} + +// name returns the field name for the Animation. +func (config AnimationConfig) name() string { + return "animation" +} + +// method returns Telegram API method name for sending Animation. +func (config AnimationConfig) method() string { + return "sendAnimation" +} + // VideoNoteConfig contains information about a SendVideoNote request. type VideoNoteConfig struct { BaseFile diff --git a/helpers.go b/helpers.go index f04a0a7..cdff850 100644 --- a/helpers.go +++ b/helpers.go @@ -13,7 +13,7 @@ func NewMessage(chatID int64, text string) MessageConfig { ChatID: chatID, ReplyToMessageID: 0, }, - Text: text, + Text: text, DisableWebPagePreview: false, } } @@ -200,6 +200,35 @@ func NewVideoShare(chatID int64, fileID string) VideoConfig { } } +// NewAnimationUpload creates a new animation uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewAnimationShare shares an existing animation. +// You may use this to reshare an existing animation without reuploading it. +// +// chatID is where to send it, fileID is the ID of the animation +// already uploaded. +func NewAnimationShare(chatID int64, fileID string) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + // NewVideoNoteUpload creates a new video note uploader. // // chatID is where to send it, file is a string path to the file, diff --git a/types.go b/types.go index 0843ab9..cc315f4 100644 --- a/types.go +++ b/types.go @@ -144,6 +144,7 @@ type Message struct { Entities *[]MessageEntity `json:"entities"` // optional Audio *Audio `json:"audio"` // optional Document *Document `json:"document"` // optional + Animation *ChatAnimation `json:"animation"` // optional Game *Game `json:"game"` // optional Photo *[]PhotoSize `json:"photo"` // optional Sticker *Sticker `json:"sticker"` // optional @@ -293,6 +294,18 @@ type Sticker struct { SetName string `json:"set_name"` // optional } +// ChatAnimation contains information about an animation. +type ChatAnimation struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileName string `json:"file_name"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + // Video contains information about a video. type Video struct { FileID string `json:"file_id"` From 3fbd77ce76edcd2f439f1f961d22c0ffeb02d927 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Fri, 21 Sep 2018 13:26:47 -0400 Subject: [PATCH 13/17] Adds paspport update type --- passport.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/passport.go b/passport.go index 9afbcf1..ab0ba90 100644 --- a/passport.go +++ b/passport.go @@ -1,12 +1,5 @@ package tgbotapi -import ( - "encoding/json" - "fmt" - "net/url" - "strconv" -) - // PassportRequestInfoConfig allows you to request passport info type PassportRequestInfoConfig struct { BotID int `json:"bot_id"` @@ -15,22 +8,6 @@ type PassportRequestInfoConfig struct { PublicKey string `json:"public_key"` } -func LinkToPassportRequest(config PassportRequestInfoConfig) (string, error) { - scope, err := json.Marshal(config.Scope) - if err != nil { - panic("couldn't pack scope") - } - tgurl := fmt.Sprintf("tg://resolve?domain=telegrampassport&bot_id=%v&scope=%v&public_key=%v&nonce=%v", - strconv.Itoa(config.BotID), - url.PathEscape(string(scope)), - url.PathEscape(config.PublicKey), - url.PathEscape(config.Nonce), - ) - fmt.Println(tgurl) - - return tgurl, nil -} - type PassportScopeElement interface { ScopeType() string } From 898e79fe47dafa598b3f61dd570aa2921628e530 Mon Sep 17 00:00:00 2001 From: Syfaro Date: Fri, 21 Sep 2018 20:20:28 -0500 Subject: [PATCH 14/17] Add initial support for sendMediaGroup. --- bot_test.go | 14 ++++++++++++++ configs.go | 26 ++++++++++++++++++++++++++ helpers.go | 28 ++++++++++++++++++++++++++++ log.go | 3 ++- types.go | 21 +++++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/bot_test.go b/bot_test.go index e7aa7ac..e9cfc79 100644 --- a/bot_test.go +++ b/bot_test.go @@ -519,6 +519,20 @@ func TestUpdatesChan(t *testing.T) { } } +func TestSendWithMediaGroup(t *testing.T) { + bot, _ := getBot(t) + + cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ + tgbotapi.NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), + tgbotapi.NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), + tgbotapi.NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), + }) + _, err := bot.Send(cfg) + if err != nil { + t.Error(err) + } +} + func ExampleNewBotAPI() { bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") if err != nil { diff --git a/configs.go b/configs.go index be12d3a..181d4e4 100644 --- a/configs.go +++ b/configs.go @@ -657,6 +657,32 @@ func (config VoiceConfig) method() string { return "sendVoice" } +// MediaGroupConfig contains information about a sendMediaGroup request. +type MediaGroupConfig struct { + BaseChat + InputMedia []interface{} +} + +func (config MediaGroupConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + data, err := json.Marshal(config.InputMedia) + if err != nil { + return v, err + } + + v.Add("media", string(data)) + + return v, nil +} + +func (config MediaGroupConfig) method() string { + return "sendMediaGroup" +} + // LocationConfig contains information about a SendLocation request. type LocationConfig struct { BaseChat diff --git a/helpers.go b/helpers.go index cdff850..f49cbba 100644 --- a/helpers.go +++ b/helpers.go @@ -18,6 +18,7 @@ func NewMessage(chatID int64, text string) MessageConfig { } } +// NewDeleteMessage creates a request to delete a message. func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig { return DeleteMessageConfig{ ChatID: chatID, @@ -289,6 +290,33 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig { } } +// NewMediaGroup creates a new media group. Files should be an array of +// two to ten InputMediaPhoto or InputMediaVideo. +func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { + return MediaGroupConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + InputMedia: files, + } +} + +// NewInputMediaPhoto creates a new InputMediaPhoto. +func NewInputMediaPhoto(media string) InputMediaPhoto { + return InputMediaPhoto{ + Type: "photo", + Media: media, + } +} + +// NewInputMediaVideo creates a new InputMediaVideo. +func NewInputMediaVideo(media string) InputMediaVideo { + return InputMediaVideo{ + Type: "video", + Media: media, + } +} + // NewContact allows you to send a shared contact. func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { return ContactConfig{ diff --git a/log.go b/log.go index 18e339f..7a99bbd 100644 --- a/log.go +++ b/log.go @@ -1,13 +1,14 @@ package tgbotapi import ( - "os" "errors" stdlog "log" + "os" ) var log = stdlog.New(os.Stderr, "", stdlog.LstdFlags) +// SetLogger specifies the logger that the package should use. func SetLogger(newLog *stdlog.Logger) error { if newLog == nil { return errors.New("logger is nil") diff --git a/types.go b/types.go index cc315f4..b2fc704 100644 --- a/types.go +++ b/types.go @@ -524,6 +524,27 @@ func (info WebhookInfo) IsSet() bool { return info.URL != "" } +// InputMediaPhoto contains a photo for displaying as part of a media group. +type InputMediaPhoto struct { + Type string `json:"type"` + Media string `json:"media"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` +} + +// InputMediaVideo contains a video for displaying as part of a media group. +type InputMediaVideo struct { + Type string `json:"type"` + Media string `json:"media"` + // thumb intentionally missing as it is not currently compatible + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + SupportsStreaming bool `json:"supports_streaming"` +} + // InlineQuery is a Query from Telegram for an inline request. type InlineQuery struct { ID string `json:"id"` From 5f38203a155afa901c7deb70596a088a8424a9ee Mon Sep 17 00:00:00 2001 From: Syfaro Date: Mon, 8 Oct 2018 01:58:33 -0500 Subject: [PATCH 15/17] Remove a broken check for GetUpdatesChan. --- bot.go | 5 +---- passport.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bot.go b/bot.go index 35d1e25..cddf72c 100644 --- a/bot.go +++ b/bot.go @@ -493,10 +493,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { } for _, update := range updates { - if update.UpdateID >= config.Offset { - config.Offset = update.UpdateID + 1 - ch <- update - } + ch <- update } } }() diff --git a/passport.go b/passport.go index ab0ba90..85cb990 100644 --- a/passport.go +++ b/passport.go @@ -291,7 +291,7 @@ type ( MiddleNameNative string `json:"middle_name_native"` } - // IdDocumentData https://core.telegram.org/passport#iddocumentdata + // IDDocumentData https://core.telegram.org/passport#iddocumentdata IDDocumentData struct { DocumentNumber string `json:"document_no"` ExpiryDate string `json:"expiry_date"` From b6441c36ee8feca2d3ddd509fe2e339b0b21df1b Mon Sep 17 00:00:00 2001 From: Syfaro Date: Mon, 8 Oct 2018 02:25:09 -0500 Subject: [PATCH 16/17] A number of small improvements. --- README.md | 11 ++++++++--- bot_test.go | 5 +---- log.go | 17 +++++++++++++---- passport.go | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d9a6873..5e29f88 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ func main() { updates, err := bot.GetUpdatesChan(u) for update := range updates { - if update.Message == nil { + if update.Message == nil { // ignore any non-Message Updates continue } @@ -65,6 +65,11 @@ func main() { } ``` +There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki) +with detailed information on how to do many differen kinds of things. +It's a great place to get started on using keyboards, commands, or other +kinds of reply markup. + If you need to use webhooks (if you wish to run on Google App Engine), you may use a slightly different method. @@ -96,7 +101,7 @@ func main() { log.Fatal(err) } if info.LastErrorDate != 0 { - log.Printf("[Telegram callback failed]%s", info.LastErrorMessage) + log.Printf("Telegram callback failed: %s", info.LastErrorMessage) } updates := bot.ListenForWebhook("/" + bot.Token) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) @@ -114,5 +119,5 @@ properly signed. openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes -Now that [Let's Encrypt](https://letsencrypt.org) has entered public beta, +Now that [Let's Encrypt](https://letsencrypt.org) is available, you may wish to generate your free TLS certificate there. diff --git a/bot_test.go b/bot_test.go index e9cfc79..60f3e65 100644 --- a/bot_test.go +++ b/bot_test.go @@ -473,13 +473,10 @@ func TestSetWebhookWithCert(t *testing.T) { t.Error(err) t.Fail() } - info, err := bot.GetWebhookInfo() + _, err = bot.GetWebhookInfo() if err != nil { t.Error(err) } - if info.LastErrorDate != 0 { - t.Errorf("[Telegram callback failed]%s", info.LastErrorMessage) - } bot.RemoveWebhook() } diff --git a/log.go b/log.go index 7a99bbd..1872551 100644 --- a/log.go +++ b/log.go @@ -6,13 +6,22 @@ import ( "os" ) -var log = stdlog.New(os.Stderr, "", stdlog.LstdFlags) +// BotLogger is an interface that represents the required methods to log data. +// +// Instead of requiring the standard logger, we can just specify the methods we +// use and allow users to pass anything that implements these. +type BotLogger interface { + Println(v ...interface{}) + Printf(format string, v ...interface{}) +} + +var log BotLogger = stdlog.New(os.Stderr, "", stdlog.LstdFlags) // SetLogger specifies the logger that the package should use. -func SetLogger(newLog *stdlog.Logger) error { - if newLog == nil { +func SetLogger(logger BotLogger) error { + if logger == nil { return errors.New("logger is nil") } - log = newLog + log = logger return nil } diff --git a/passport.go b/passport.go index 85cb990..5f55006 100644 --- a/passport.go +++ b/passport.go @@ -8,21 +8,28 @@ type PassportRequestInfoConfig struct { PublicKey string `json:"public_key"` } +// PassportScopeElement supports using one or one of several elements. type PassportScopeElement interface { ScopeType() string } + +// PassportScope is the requested scopes of data. type PassportScope struct { V int `json:"v"` Data []PassportScopeElement `json:"data"` } +// PassportScopeElementOneOfSeveral allows you to request any one of the +// requested documents. type PassportScopeElementOneOfSeveral struct { } +// ScopeType is the scope type. func (eo *PassportScopeElementOneOfSeveral) ScopeType() string { return "one_of" } +// PassportScopeElementOne requires the specified element be provided. type PassportScopeElementOne struct { Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email” Selfie bool `json:"selfie"` @@ -30,6 +37,7 @@ type PassportScopeElementOne struct { NativeNames bool `json:"native_name"` } +// ScopeType is the scope type. func (eo *PassportScopeElementOne) ScopeType() string { return "one" } @@ -146,6 +154,7 @@ type ( // Error message Message string `json:"message"` } + // PassportElementErrorFrontSide represents an issue with the front side of // a document. The error is considered resolved when the file with the front // side of the document changes. @@ -237,12 +246,14 @@ type ( Message string `json:"message"` } + // Credentials contains encrypted data. Credentials struct { Data SecureData `json:"secure_data"` // Nonce the same nonce given in the request Nonce string `json:"nonce"` } + // SecureData is a map of the fields and their encrypted values. SecureData map[string]*SecureValue // PersonalDetails *SecureValue `json:"personal_details"` // Passport *SecureValue `json:"passport"` @@ -256,6 +267,7 @@ type ( // PassportRegistration *SecureValue `json:"passport_registration"` // TemporaryRegistration *SecureValue `json:"temporary_registration"` + // SecureValue contains encrypted values for a SecureData item. SecureValue struct { Data *DataCredentials `json:"data"` FrontSide *FileCredentials `json:"front_side"` @@ -264,6 +276,8 @@ type ( Translation []*FileCredentials `json:"translation"` Files []*FileCredentials `json:"files"` } + + // DataCredentials contains information required to decrypt data. DataCredentials struct { // DataHash checksum of encrypted data DataHash string `json:"data_hash"` @@ -271,12 +285,14 @@ type ( Secret string `json:"secret"` } + // FileCredentials contains information required to decrypt files. FileCredentials struct { // FileHash checksum of encrypted data FileHash string `json:"file_hash"` // Secret of encrypted data Secret string `json:"secret"` } + // PersonalDetails https://core.telegram.org/passport#personaldetails PersonalDetails struct { FirstName string `json:"first_name"` From e2f846d83a091384cafbbf817762de414d57b1f4 Mon Sep 17 00:00:00 2001 From: Syfaro Date: Mon, 8 Oct 2018 02:28:34 -0500 Subject: [PATCH 17/17] Stop using gopkg in README among other minor improvements. --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5e29f88..9325061 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,6 @@ [![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) [![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api) -All methods have been added, and all features should be available. -If you want a feature that hasn't been added yet or something is broken, -open an issue and I'll see what I can do. - All methods are fairly self explanatory, and reading the godoc page should explain everything. If something isn't clear, open an issue or submit a pull request. @@ -16,14 +12,14 @@ without any additional features. There are other projects for creating something with plugins and command handlers without having to design all that yourself. -Use `github.com/go-telegram-bot-api/telegram-bot-api` for the latest -version, or use `gopkg.in/telegram-bot-api.v4` for the stable build. - Join [the development group](https://telegram.me/go_telegram_bot_api) if you want to ask questions or discuss development. ## Example +First, ensure the library is installed and up to date by running +`go get -u github.com/go-telegram-bot-api/telegram-bot-api`. + This is a very simple bot that just displays any gotten updates, then replies it to that chat. @@ -32,7 +28,8 @@ package main import ( "log" - "gopkg.in/telegram-bot-api.v4" + + "github.com/go-telegram-bot-api/telegram-bot-api" ) func main() { @@ -77,9 +74,10 @@ you may use a slightly different method. package main import ( - "gopkg.in/telegram-bot-api.v4" "log" "net/http" + + "github.com/go-telegram-bot-api/telegram-bot-api" ) func main() {