Merge branch 'master' into master

pull/208/head
Nix 7 years ago committed by GitHub
commit e4b13509e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 15
      README.md
  3. 16
      bot.go
  4. 19
      bot_test.go
  5. 79
      configs.go
  6. 59
      helpers.go
  7. 20
      log.go
  8. 315
      passport.go
  9. 35
      types.go

1
.gitignore vendored

@ -1,2 +1,3 @@
.idea/
coverage.out
tmp/

@ -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() {
@ -51,4 +48,4 @@ func main() {
updates, err := bot.GetUpdatesChan(u)
for update := range updates {
if

@ -28,6 +28,7 @@ type BotAPI struct {
Self User `json:"-"`
Client *http.Client `json:"-"`
shutdownChannel chan interface{}
}
// NewBotAPI creates a new BotAPI instance.
@ -67,6 +68,7 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
Token: token,
Client: client,
Buffer: 100,
shutdownChannel: make(chan interface{}),
}
self, err := bot.GetMe()
@ -505,6 +507,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)
@ -526,6 +534,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)

@ -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()
}
@ -519,6 +516,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 {

@ -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
@ -604,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

@ -13,11 +13,12 @@ func NewMessage(chatID int64, text string) MessageConfig {
ChatID: chatID,
ReplyToMessageID: 0,
},
Text: text,
Text: text,
DisableWebPagePreview: false,
}
}
// NewDeleteMessage creates a request to delete a message.
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
return DeleteMessageConfig{
ChatID: chatID,
@ -200,6 +201,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,
@ -260,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{

@ -1,17 +1,27 @@
package tgbotapi
import (
"os"
"errors"
stdlog "log"
"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)
func SetLogger(newLog *stdlog.Logger) error {
if newLog == nil {
// SetLogger specifies the logger that the package should use.
func SetLogger(logger BotLogger) error {
if logger == nil {
return errors.New("logger is nil")
}
log = newLog
log = logger
return nil
}

@ -0,0 +1,315 @@
package tgbotapi
// 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"`
}
// 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"`
Translation bool `json:"translation"`
NativeNames bool `json:"native_name"`
}
// ScopeType is the scope type.
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 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"`
// 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 contains encrypted values for a SecureData item.
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 contains information required to decrypt data.
DataCredentials struct {
// DataHash checksum of encrypted data
DataHash string `json:"data_hash"`
// Secret of encrypted data
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"`
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"`
}
)

@ -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
@ -167,6 +168,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.
@ -293,6 +295,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"`
@ -511,6 +525,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"`