diff --git a/bridge/bridge.go b/bridge/bridge.go index 4a422c75..8c715369 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -7,6 +7,7 @@ import ( "github.com/42wim/matterbridge/bridge/irc" "github.com/42wim/matterbridge/bridge/mattermost" "github.com/42wim/matterbridge/bridge/slack" + "github.com/42wim/matterbridge/bridge/telegram" "github.com/42wim/matterbridge/bridge/xmpp" "strings" ) @@ -55,6 +56,9 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid case "discord": b.Config = cfg.Discord[name] b.Bridger = bdiscord.New(cfg.Discord[name], bridge.Account, c) + case "telegram": + b.Config = cfg.Telegram[name] + b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c) } return b } diff --git a/bridge/config/config.go b/bridge/config/config.go index d3d88115..5fed2f0a 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -78,6 +78,7 @@ type Config struct { Gitter map[string]Protocol Xmpp map[string]Protocol Discord map[string]Protocol + Telegram map[string]Protocol Gateway []Gateway SameChannelGateway []SameChannelGateway } diff --git a/bridge/telegram/telegram.go b/bridge/telegram/telegram.go new file mode 100644 index 00000000..aaffe839 --- /dev/null +++ b/bridge/telegram/telegram.go @@ -0,0 +1,74 @@ +package btelegram + +import ( + "github.com/42wim/matterbridge/bridge/config" + log "github.com/Sirupsen/logrus" + "github.com/go-telegram-bot-api/telegram-bot-api" + "strconv" +) + +type Btelegram struct { + c *tgbotapi.BotAPI + Config *config.Protocol + Remote chan config.Message + Account string +} + +var flog *log.Entry +var protocol = "telegram" + +func init() { + flog = log.WithFields(log.Fields{"module": protocol}) +} + +func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram { + b := &Btelegram{} + b.Config = &cfg + b.Remote = c + b.Account = account + return b +} + +func (b *Btelegram) Connect() error { + var err error + flog.Info("Connecting") + b.c, err = tgbotapi.NewBotAPI(b.Config.Token) + if err != nil { + flog.Debugf("%#v", err) + return err + } + updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0)) + if err != nil { + flog.Debugf("%#v", err) + return err + } + flog.Info("Connection succeeded") + go b.handleRecv(updates) + return nil +} + +func (b *Btelegram) JoinChannel(channel string) error { + return nil +} + +func (b *Btelegram) Send(msg config.Message) error { + flog.Debugf("Receiving %#v", msg) + chatid, err := strconv.ParseInt(msg.Channel, 10, 64) + if err != nil { + return err + } + m := tgbotapi.NewMessage(chatid, msg.Text) + _, err = b.c.Send(m) + return err +} + +func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { + for update := range updates { + if update.Message == nil { + continue + } + flog.Debugf("Sending message from %s on %s to gateway", update.Message.From.UserName, b.Account) + b.Remote <- config.Message{Username: update.Message.From.UserName, Text: update.Message.Text, Channel: strconv.FormatInt(update.Message.Chat.ID, 10), Account: b.Account} + + } +} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt new file mode 100644 index 00000000..b1fef93b --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Syfaro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go new file mode 100644 index 00000000..c7e473fb --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go @@ -0,0 +1,650 @@ +// Package tgbotapi has functions and types used for interacting with +// the Telegram Bot API. +package tgbotapi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/technoweenie/multipartstreamer" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" +) + +// BotAPI allows you to interact with the Telegram Bot API. +type BotAPI struct { + Token string `json:"token"` + Debug bool `json:"debug"` + Self User `json:"-"` + Client *http.Client `json:"-"` +} + +// NewBotAPI creates a new BotAPI instance. +// +// It requires a token, provided by @BotFather on Telegram. +func NewBotAPI(token string) (*BotAPI, error) { + return NewBotAPIWithClient(token, &http.Client{}) +} + +// NewBotAPIWithClient creates a new BotAPI instance +// and allows you to pass a http.Client. +// +// It requires a token, provided by @BotFather on Telegram. +func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { + bot := &BotAPI{ + Token: token, + Client: client, + } + + self, err := bot.GetMe() + if err != nil { + return &BotAPI{}, err + } + + bot.Self = self + + return bot, nil +} + +// MakeRequest makes a request to a specific endpoint with our token. +func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) { + method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) + + resp, err := bot.Client.PostForm(method, params) + if err != nil { + return APIResponse{}, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusForbidden { + return APIResponse{}, errors.New(ErrAPIForbidden) + } + + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return APIResponse{}, err + } + + if bot.Debug { + log.Println(endpoint, string(bytes)) + } + + var apiResp APIResponse + json.Unmarshal(bytes, &apiResp) + + if !apiResp.Ok { + return APIResponse{}, errors.New(apiResp.Description) + } + + return apiResp, nil +} + +// makeMessageRequest makes a request to a method that returns a Message. +func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Message, error) { + resp, err := bot.MakeRequest(endpoint, params) + if err != nil { + return Message{}, err + } + + var message Message + json.Unmarshal(resp.Result, &message) + + bot.debugLog(endpoint, params, message) + + return message, nil +} + +// UploadFile makes a request to the API with a file. +// +// Requires the parameter to hold the file not be in the params. +// File should be a string to a file path, a FileBytes struct, +// or a FileReader struct. +// +// Note that if your FileReader has a size set to -1, it will read +// the file into memory to calculate a size. +func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) { + ms := multipartstreamer.New() + ms.WriteFields(params) + + switch f := file.(type) { + case string: + fileHandle, err := os.Open(f) + if err != nil { + return APIResponse{}, err + } + defer fileHandle.Close() + + fi, err := os.Stat(f) + if err != nil { + return APIResponse{}, err + } + + ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) + case FileBytes: + buf := bytes.NewBuffer(f.Bytes) + ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) + case FileReader: + if f.Size != -1 { + ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) + + break + } + + data, err := ioutil.ReadAll(f.Reader) + if err != nil { + return APIResponse{}, err + } + + buf := bytes.NewBuffer(data) + + ms.WriteReader(fieldname, f.Name, int64(len(data)), buf) + default: + return APIResponse{}, errors.New(ErrBadFileType) + } + + method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) + + req, err := http.NewRequest("POST", method, nil) + if err != nil { + return APIResponse{}, err + } + + ms.SetupRequest(req) + + res, err := bot.Client.Do(req) + if err != nil { + return APIResponse{}, err + } + defer res.Body.Close() + + bytes, err := ioutil.ReadAll(res.Body) + if err != nil { + return APIResponse{}, err + } + + if bot.Debug { + log.Println(string(bytes)) + } + + var apiResp APIResponse + json.Unmarshal(bytes, &apiResp) + + if !apiResp.Ok { + return APIResponse{}, errors.New(apiResp.Description) + } + + return apiResp, nil +} + +// GetFileDirectURL returns direct URL to file +// +// It requires the FileID. +func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) { + file, err := bot.GetFile(FileConfig{fileID}) + + if err != nil { + return "", err + } + + return file.Link(bot.Token), nil +} + +// GetMe fetches the currently authenticated bot. +// +// This method is called upon creation to validate the token, +// and so you may get this data from BotAPI.Self without the need for +// another request. +func (bot *BotAPI) GetMe() (User, error) { + resp, err := bot.MakeRequest("getMe", nil) + if err != nil { + return User{}, err + } + + var user User + json.Unmarshal(resp.Result, &user) + + bot.debugLog("getMe", nil, user) + + return user, nil +} + +// IsMessageToMe returns true if message directed to this bot. +// +// It requires the Message. +func (bot *BotAPI) IsMessageToMe(message Message) bool { + return strings.Contains(message.Text, "@"+bot.Self.UserName) +} + +// Send will send a Chattable item to Telegram. +// +// It requires the Chattable to send. +func (bot *BotAPI) Send(c Chattable) (Message, error) { + switch c.(type) { + case Fileable: + return bot.sendFile(c.(Fileable)) + default: + return bot.sendChattable(c) + } +} + +// debugLog checks if the bot is currently running in debug mode, and if +// so will display information about the request and response in the +// debug log. +func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) { + if bot.Debug { + log.Printf("%s req : %+v\n", context, v) + log.Printf("%s resp: %+v\n", context, message) + } +} + +// sendExisting will send a Message with an existing file to Telegram. +func (bot *BotAPI) sendExisting(method string, config Fileable) (Message, error) { + v, err := config.values() + + if err != nil { + return Message{}, err + } + + message, err := bot.makeMessageRequest(method, v) + if err != nil { + return Message{}, err + } + + return message, nil +} + +// uploadAndSend will send a Message with a new file to Telegram. +func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error) { + params, err := config.params() + if err != nil { + return Message{}, err + } + + file := config.getFile() + + resp, err := bot.UploadFile(method, params, config.name(), file) + if err != nil { + return Message{}, err + } + + var message Message + json.Unmarshal(resp.Result, &message) + + bot.debugLog(method, nil, message) + + return message, nil +} + +// sendFile determines if the file is using an existing file or uploading +// a new file, then sends it as needed. +func (bot *BotAPI) sendFile(config Fileable) (Message, error) { + if config.useExistingFile() { + return bot.sendExisting(config.method(), config) + } + + return bot.uploadAndSend(config.method(), config) +} + +// sendChattable sends a Chattable. +func (bot *BotAPI) sendChattable(config Chattable) (Message, error) { + v, err := config.values() + if err != nil { + return Message{}, err + } + + message, err := bot.makeMessageRequest(config.method(), v) + + if err != nil { + return Message{}, err + } + + return message, nil +} + +// GetUserProfilePhotos gets a user's profile photos. +// +// It requires UserID. +// Offset and Limit are optional. +func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { + v := url.Values{} + v.Add("user_id", strconv.Itoa(config.UserID)) + if config.Offset != 0 { + v.Add("offset", strconv.Itoa(config.Offset)) + } + if config.Limit != 0 { + v.Add("limit", strconv.Itoa(config.Limit)) + } + + resp, err := bot.MakeRequest("getUserProfilePhotos", v) + if err != nil { + return UserProfilePhotos{}, err + } + + var profilePhotos UserProfilePhotos + json.Unmarshal(resp.Result, &profilePhotos) + + bot.debugLog("GetUserProfilePhoto", v, profilePhotos) + + return profilePhotos, nil +} + +// GetFile returns a File which can download a file from Telegram. +// +// Requires FileID. +func (bot *BotAPI) GetFile(config FileConfig) (File, error) { + v := url.Values{} + v.Add("file_id", config.FileID) + + resp, err := bot.MakeRequest("getFile", v) + if err != nil { + return File{}, err + } + + var file File + json.Unmarshal(resp.Result, &file) + + bot.debugLog("GetFile", v, file) + + return file, nil +} + +// GetUpdates fetches updates. +// If a WebHook is set, this will not return any data! +// +// Offset, Limit, and Timeout are optional. +// To avoid stale items, set Offset to one higher than the previous item. +// Set Timeout to a large number to reduce requests so you can get updates +// instantly instead of having to wait between requests. +func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { + v := url.Values{} + if config.Offset != 0 { + v.Add("offset", strconv.Itoa(config.Offset)) + } + if config.Limit > 0 { + v.Add("limit", strconv.Itoa(config.Limit)) + } + if config.Timeout > 0 { + v.Add("timeout", strconv.Itoa(config.Timeout)) + } + + resp, err := bot.MakeRequest("getUpdates", v) + if err != nil { + return []Update{}, err + } + + var updates []Update + json.Unmarshal(resp.Result, &updates) + + bot.debugLog("getUpdates", v, updates) + + return updates, nil +} + +// RemoveWebhook unsets the webhook. +func (bot *BotAPI) RemoveWebhook() (APIResponse, error) { + return bot.MakeRequest("setWebhook", url.Values{}) +} + +// SetWebhook sets a webhook. +// +// If this is set, GetUpdates will not get any data! +// +// If you do not have a legitimate TLS certificate, you need to include +// your self signed certificate with the config. +func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) { + if config.Certificate == nil { + v := url.Values{} + v.Add("url", config.URL.String()) + + return bot.MakeRequest("setWebhook", v) + } + + params := make(map[string]string) + params["url"] = config.URL.String() + + resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate) + if err != nil { + return APIResponse{}, err + } + + var apiResp APIResponse + json.Unmarshal(resp.Result, &apiResp) + + if bot.Debug { + log.Printf("setWebhook resp: %+v\n", apiResp) + } + + return apiResp, nil +} + +// GetUpdatesChan starts and returns a channel for getting updates. +func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { + updatesChan := make(chan Update, 100) + + go func() { + for { + updates, err := bot.GetUpdates(config) + if err != nil { + log.Println(err) + log.Println("Failed to get updates, retrying in 3 seconds...") + time.Sleep(time.Second * 3) + + continue + } + + for _, update := range updates { + if update.UpdateID >= config.Offset { + config.Offset = update.UpdateID + 1 + updatesChan <- update + } + } + } + }() + + return updatesChan, nil +} + +// ListenForWebhook registers a http handler for a webhook. +func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { + updatesChan := make(chan Update, 100) + + http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { + bytes, _ := ioutil.ReadAll(r.Body) + + var update Update + json.Unmarshal(bytes, &update) + + updatesChan <- update + }) + + return updatesChan +} + +// AnswerInlineQuery sends a response to an inline query. +// +// Note that you must respond to an inline query within 30 seconds. +func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) { + v := url.Values{} + + v.Add("inline_query_id", config.InlineQueryID) + v.Add("cache_time", strconv.Itoa(config.CacheTime)) + v.Add("is_personal", strconv.FormatBool(config.IsPersonal)) + v.Add("next_offset", config.NextOffset) + data, err := json.Marshal(config.Results) + if err != nil { + return APIResponse{}, err + } + v.Add("results", string(data)) + v.Add("switch_pm_text", config.SwitchPMText) + v.Add("switch_pm_parameter", config.SwitchPMParameter) + + bot.debugLog("answerInlineQuery", v, nil) + + return bot.MakeRequest("answerInlineQuery", v) +} + +// AnswerCallbackQuery sends a response to an inline query callback. +func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, error) { + v := url.Values{} + + v.Add("callback_query_id", config.CallbackQueryID) + v.Add("text", config.Text) + v.Add("show_alert", strconv.FormatBool(config.ShowAlert)) + + bot.debugLog("answerCallbackQuery", v, nil) + + return bot.MakeRequest("answerCallbackQuery", v) +} + +// KickChatMember kicks a user from a chat. Note that this only will work +// in supergroups, and requires the bot to be an admin. Also note they +// will be unable to rejoin until they are unbanned. +func (bot *BotAPI) KickChatMember(config ChatMemberConfig) (APIResponse, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + v.Add("user_id", strconv.Itoa(config.UserID)) + + bot.debugLog("kickChatMember", v, nil) + + return bot.MakeRequest("kickChatMember", v) +} + +// LeaveChat makes the bot leave the chat. +func (bot *BotAPI) LeaveChat(config ChatConfig) (APIResponse, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + + bot.debugLog("leaveChat", v, nil) + + return bot.MakeRequest("leaveChat", v) +} + +// GetChat gets information about a chat. +func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + + resp, err := bot.MakeRequest("getChat", v) + if err != nil { + return Chat{}, err + } + + var chat Chat + err = json.Unmarshal(resp.Result, &chat) + + bot.debugLog("getChat", v, chat) + + return chat, err +} + +// GetChatAdministrators gets a list of administrators in the chat. +// +// If none have been appointed, only the creator will be returned. +// Bots are not shown, even if they are an administrator. +func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + + resp, err := bot.MakeRequest("getChatAdministrators", v) + if err != nil { + return []ChatMember{}, err + } + + var members []ChatMember + err = json.Unmarshal(resp.Result, &members) + + bot.debugLog("getChatAdministrators", v, members) + + return members, err +} + +// GetChatMembersCount gets the number of users in a chat. +func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + + resp, err := bot.MakeRequest("getChatMembersCount", v) + if err != nil { + return -1, err + } + + var count int + err = json.Unmarshal(resp.Result, &count) + + bot.debugLog("getChatMembersCount", v, count) + + return count, err +} + +// GetChatMember gets a specific chat member. +func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + v.Add("user_id", strconv.Itoa(config.UserID)) + + resp, err := bot.MakeRequest("getChatMember", v) + if err != nil { + return ChatMember{}, err + } + + var member ChatMember + err = json.Unmarshal(resp.Result, &member) + + bot.debugLog("getChatMember", v, member) + + return member, err +} + +// UnbanChatMember unbans a user from a chat. Note that this only will work +// in supergroups, and requires the bot to be an admin. +func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + v.Add("user_id", strconv.Itoa(config.UserID)) + + bot.debugLog("unbanChatMember", v, nil) + + return bot.MakeRequest("unbanChatMember", v) +} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go new file mode 100644 index 00000000..b91d0704 --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go @@ -0,0 +1,694 @@ +package tgbotapi + +import ( + "encoding/json" + "io" + "net/url" + "strconv" +) + +// Telegram constants +const ( + // APIEndpoint is the endpoint for all API methods, + // with formatting for Sprintf. + APIEndpoint = "https://api.telegram.org/bot%s/%s" + // FileEndpoint is the endpoint for downloading a file from Telegram. + FileEndpoint = "https://api.telegram.org/file/bot%s/%s" +) + +// Constant values for ChatActions +const ( + ChatTyping = "typing" + ChatUploadPhoto = "upload_photo" + ChatRecordVideo = "record_video" + ChatUploadVideo = "upload_video" + ChatRecordAudio = "record_audio" + ChatUploadAudio = "upload_audio" + ChatUploadDocument = "upload_document" + ChatFindLocation = "find_location" +) + +// API errors +const ( + // ErrAPIForbidden happens when a token is bad + ErrAPIForbidden = "forbidden" +) + +// Constant values for ParseMode in MessageConfig +const ( + ModeMarkdown = "Markdown" + ModeHTML = "HTML" +) + +// Library errors +const ( + // ErrBadFileType happens when you pass an unknown type + ErrBadFileType = "bad file type" + ErrBadURL = "bad or empty url" +) + +// Chattable is any config type that can be sent. +type Chattable interface { + values() (url.Values, error) + method() string +} + +// Fileable is any config type that can be sent that includes a file. +type Fileable interface { + Chattable + params() (map[string]string, error) + name() string + getFile() interface{} + useExistingFile() bool +} + +// BaseChat is base type for all chat config types. +type BaseChat struct { + ChatID int64 // required + ChannelUsername string + ReplyToMessageID int + ReplyMarkup interface{} + DisableNotification bool +} + +// values returns url.Values representation of BaseChat +func (chat *BaseChat) values() (url.Values, error) { + v := url.Values{} + if chat.ChannelUsername != "" { + v.Add("chat_id", chat.ChannelUsername) + } else { + v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10)) + } + + if chat.ReplyToMessageID != 0 { + v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID)) + } + + if chat.ReplyMarkup != nil { + data, err := json.Marshal(chat.ReplyMarkup) + if err != nil { + return v, err + } + + v.Add("reply_markup", string(data)) + } + + v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification)) + + return v, nil +} + +// BaseFile is a base type for all file config types. +type BaseFile struct { + BaseChat + File interface{} + FileID string + UseExisting bool + MimeType string + FileSize int +} + +// params returns a map[string]string representation of BaseFile. +func (file BaseFile) params() (map[string]string, error) { + params := make(map[string]string) + + if file.ChannelUsername != "" { + params["chat_id"] = file.ChannelUsername + } else { + params["chat_id"] = strconv.FormatInt(file.ChatID, 10) + } + + if file.ReplyToMessageID != 0 { + params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID) + } + + if file.ReplyMarkup != nil { + data, err := json.Marshal(file.ReplyMarkup) + if err != nil { + return params, err + } + + params["reply_markup"] = string(data) + } + + if file.MimeType != "" { + params["mime_type"] = file.MimeType + } + + if file.FileSize > 0 { + params["file_size"] = strconv.Itoa(file.FileSize) + } + + params["disable_notification"] = strconv.FormatBool(file.DisableNotification) + + return params, nil +} + +// getFile returns the file. +func (file BaseFile) getFile() interface{} { + return file.File +} + +// useExistingFile returns if the BaseFile has already been uploaded. +func (file BaseFile) useExistingFile() bool { + return file.UseExisting +} + +// BaseEdit is base type of all chat edits. +type BaseEdit struct { + ChatID int64 + ChannelUsername string + MessageID int + InlineMessageID string + ReplyMarkup *InlineKeyboardMarkup +} + +func (edit BaseEdit) values() (url.Values, error) { + v := url.Values{} + + if edit.InlineMessageID == "" { + if edit.ChannelUsername != "" { + v.Add("chat_id", edit.ChannelUsername) + } else { + v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10)) + } + v.Add("message_id", strconv.Itoa(edit.MessageID)) + } else { + v.Add("inline_message_id", edit.InlineMessageID) + } + + if edit.ReplyMarkup != nil { + data, err := json.Marshal(edit.ReplyMarkup) + if err != nil { + return v, err + } + v.Add("reply_markup", string(data)) + } + + return v, nil +} + +// MessageConfig contains information about a SendMessage request. +type MessageConfig struct { + BaseChat + Text string + ParseMode string + DisableWebPagePreview bool +} + +// values returns a url.Values representation of MessageConfig. +func (config MessageConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + v.Add("text", config.Text) + v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } + + return v, nil +} + +// method returns Telegram API method name for sending Message. +func (config MessageConfig) method() string { + return "sendMessage" +} + +// ForwardConfig contains information about a ForwardMessage request. +type ForwardConfig struct { + BaseChat + FromChatID int64 // required + FromChannelUsername string + MessageID int // required +} + +// values returns a url.Values representation of ForwardConfig. +func (config ForwardConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) + v.Add("message_id", strconv.Itoa(config.MessageID)) + return v, nil +} + +// method returns Telegram API method name for sending Forward. +func (config ForwardConfig) method() string { + return "forwardMessage" +} + +// PhotoConfig contains information about a SendPhoto request. +type PhotoConfig struct { + BaseFile + Caption string +} + +// Params returns a map[string]string representation of PhotoConfig. +func (config PhotoConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Caption != "" { + params["caption"] = config.Caption + } + + return params, nil +} + +// Values returns a url.Values representation of PhotoConfig. +func (config PhotoConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add(config.name(), config.FileID) + if config.Caption != "" { + v.Add("caption", config.Caption) + } + return v, nil +} + +// name returns the field name for the Photo. +func (config PhotoConfig) name() string { + return "photo" +} + +// method returns Telegram API method name for sending Photo. +func (config PhotoConfig) method() string { + return "sendPhoto" +} + +// AudioConfig contains information about a SendAudio request. +type AudioConfig struct { + BaseFile + Duration int + Performer string + Title string +} + +// values returns a url.Values representation of AudioConfig. +func (config AudioConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add(config.name(), config.FileID) + if config.Duration != 0 { + v.Add("duration", strconv.Itoa(config.Duration)) + } + + if config.Performer != "" { + v.Add("performer", config.Performer) + } + if config.Title != "" { + v.Add("title", config.Title) + } + + return v, nil +} + +// params returns a map[string]string representation of AudioConfig. +func (config AudioConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Duration != 0 { + params["duration"] = strconv.Itoa(config.Duration) + } + + if config.Performer != "" { + params["performer"] = config.Performer + } + if config.Title != "" { + params["title"] = config.Title + } + + return params, nil +} + +// name returns the field name for the Audio. +func (config AudioConfig) name() string { + return "audio" +} + +// method returns Telegram API method name for sending Audio. +func (config AudioConfig) method() string { + return "sendAudio" +} + +// DocumentConfig contains information about a SendDocument request. +type DocumentConfig struct { + BaseFile +} + +// values returns a url.Values representation of DocumentConfig. +func (config DocumentConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add(config.name(), config.FileID) + + return v, nil +} + +// params returns a map[string]string representation of DocumentConfig. +func (config DocumentConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + return params, nil +} + +// name returns the field name for the Document. +func (config DocumentConfig) name() string { + return "document" +} + +// method returns Telegram API method name for sending Document. +func (config DocumentConfig) method() string { + return "sendDocument" +} + +// StickerConfig contains information about a SendSticker request. +type StickerConfig struct { + BaseFile +} + +// values returns a url.Values representation of StickerConfig. +func (config StickerConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add(config.name(), config.FileID) + + return v, nil +} + +// params returns a map[string]string representation of StickerConfig. +func (config StickerConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + return params, nil +} + +// name returns the field name for the Sticker. +func (config StickerConfig) name() string { + return "sticker" +} + +// method returns Telegram API method name for sending Sticker. +func (config StickerConfig) method() string { + return "sendSticker" +} + +// VideoConfig contains information about a SendVideo request. +type VideoConfig struct { + BaseFile + Duration int + Caption string +} + +// values returns a url.Values representation of VideoConfig. +func (config VideoConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + 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) + } + + return v, nil +} + +// params returns a map[string]string representation of VideoConfig. +func (config VideoConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + return params, nil +} + +// name returns the field name for the Video. +func (config VideoConfig) name() string { + return "video" +} + +// method returns Telegram API method name for sending Video. +func (config VideoConfig) method() string { + return "sendVideo" +} + +// VoiceConfig contains information about a SendVoice request. +type VoiceConfig struct { + BaseFile + Duration int +} + +// values returns a url.Values representation of VoiceConfig. +func (config VoiceConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add(config.name(), config.FileID) + if config.Duration != 0 { + v.Add("duration", strconv.Itoa(config.Duration)) + } + + return v, nil +} + +// params returns a map[string]string representation of VoiceConfig. +func (config VoiceConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Duration != 0 { + params["duration"] = strconv.Itoa(config.Duration) + } + + return params, nil +} + +// name returns the field name for the Voice. +func (config VoiceConfig) name() string { + return "voice" +} + +// method returns Telegram API method name for sending Voice. +func (config VoiceConfig) method() string { + return "sendVoice" +} + +// LocationConfig contains information about a SendLocation request. +type LocationConfig struct { + BaseChat + Latitude float64 // required + Longitude float64 // required +} + +// values returns a url.Values representation of LocationConfig. +func (config LocationConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) + v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) + + return v, nil +} + +// method returns Telegram API method name for sending Location. +func (config LocationConfig) method() string { + return "sendLocation" +} + +// VenueConfig contains information about a SendVenue request. +type VenueConfig struct { + BaseChat + Latitude float64 // required + Longitude float64 // required + Title string // required + Address string // required + FoursquareID string +} + +func (config VenueConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) + v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) + v.Add("title", config.Title) + v.Add("address", config.Address) + if config.FoursquareID != "" { + v.Add("foursquare_id", config.FoursquareID) + } + + return v, nil +} + +func (config VenueConfig) method() string { + return "sendVenue" +} + +// ContactConfig allows you to send a contact. +type ContactConfig struct { + BaseChat + PhoneNumber string + FirstName string + LastName string +} + +func (config ContactConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + + v.Add("phone_number", config.PhoneNumber) + v.Add("first_name", config.FirstName) + v.Add("last_name", config.LastName) + + return v, nil +} + +func (config ContactConfig) method() string { + return "sendContact" +} + +// ChatActionConfig contains information about a SendChatAction request. +type ChatActionConfig struct { + BaseChat + Action string // required +} + +// values returns a url.Values representation of ChatActionConfig. +func (config ChatActionConfig) values() (url.Values, error) { + v, _ := config.BaseChat.values() + v.Add("action", config.Action) + return v, nil +} + +// method returns Telegram API method name for sending ChatAction. +func (config ChatActionConfig) method() string { + return "sendChatAction" +} + +// EditMessageTextConfig allows you to modify the text in a message. +type EditMessageTextConfig struct { + BaseEdit + Text string + ParseMode string + DisableWebPagePreview bool +} + +func (config EditMessageTextConfig) values() (url.Values, error) { + v, _ := config.BaseEdit.values() + + v.Add("text", config.Text) + v.Add("parse_mode", config.ParseMode) + v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) + + return v, nil +} + +func (config EditMessageTextConfig) method() string { + return "editMessageText" +} + +// EditMessageCaptionConfig allows you to modify the caption of a message. +type EditMessageCaptionConfig struct { + BaseEdit + Caption string +} + +func (config EditMessageCaptionConfig) values() (url.Values, error) { + v, _ := config.BaseEdit.values() + + v.Add("caption", config.Caption) + + return v, nil +} + +func (config EditMessageCaptionConfig) method() string { + return "editMessageCaption" +} + +// EditMessageReplyMarkupConfig allows you to modify the reply markup +// of a message. +type EditMessageReplyMarkupConfig struct { + BaseEdit +} + +func (config EditMessageReplyMarkupConfig) values() (url.Values, error) { + return config.BaseEdit.values() +} + +func (config EditMessageReplyMarkupConfig) method() string { + return "editMessageReplyMarkup" +} + +// UserProfilePhotosConfig contains information about a +// GetUserProfilePhotos request. +type UserProfilePhotosConfig struct { + UserID int + Offset int + Limit int +} + +// FileConfig has information about a file hosted on Telegram. +type FileConfig struct { + FileID string +} + +// UpdateConfig contains information about a GetUpdates request. +type UpdateConfig struct { + Offset int + Limit int + Timeout int +} + +// WebhookConfig contains information about a SetWebhook request. +type WebhookConfig struct { + URL *url.URL + Certificate interface{} +} + +// FileBytes contains information about a set of bytes to upload +// as a File. +type FileBytes struct { + Name string + Bytes []byte +} + +// FileReader contains information about a reader to upload as a File. +// If Size is -1, it will read the entire Reader into memory to +// calculate a Size. +type FileReader struct { + Name string + Reader io.Reader + Size int64 +} + +// InlineConfig contains information on making an InlineQuery response. +type InlineConfig struct { + InlineQueryID string `json:"inline_query_id"` + Results []interface{} `json:"results"` + CacheTime int `json:"cache_time"` + IsPersonal bool `json:"is_personal"` + NextOffset string `json:"next_offset"` + SwitchPMText string `json:"switch_pm_text"` + SwitchPMParameter string `json:"switch_pm_parameter"` +} + +// CallbackConfig contains information on making a CallbackQuery response. +type CallbackConfig struct { + CallbackQueryID string `json:"callback_query_id"` + Text string `json:"text"` + ShowAlert bool `json:"show_alert"` +} + +// ChatMemberConfig contains information about a user in a chat for use +// with administrative functions such as kicking or unbanning a user. +type ChatMemberConfig struct { + ChatID int64 + SuperGroupUsername string + UserID int +} + +// ChatConfig contains information about getting information on a chat. +type ChatConfig struct { + ChatID int64 + SuperGroupUsername string +} + +// ChatConfigWithUser contains information about getting information on +// a specific user within a chat. +type ChatConfigWithUser struct { + ChatID int64 + SuperGroupUsername string + UserID int +} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go new file mode 100644 index 00000000..53e50052 --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go @@ -0,0 +1,598 @@ +package tgbotapi + +import ( + "net/url" +) + +// NewMessage creates a new Message. +// +// chatID is where to send it, text is the message text. +func NewMessage(chatID int64, text string) MessageConfig { + return MessageConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + ReplyToMessageID: 0, + }, + Text: text, + DisableWebPagePreview: false, + } +} + +// NewMessageToChannel creates a new Message that is sent to a channel +// by username. +// username is the username of the channel, text is the message text. +func NewMessageToChannel(username string, text string) MessageConfig { + return MessageConfig{ + BaseChat: BaseChat{ + ChannelUsername: username, + }, + Text: text, + } +} + +// NewForward creates a new forward. +// +// chatID is where to send it, fromChatID is the source chat, +// and messageID is the ID of the original message. +func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig { + return ForwardConfig{ + BaseChat: BaseChat{ChatID: chatID}, + FromChatID: fromChatID, + MessageID: messageID, + } +} + +// NewPhotoUpload creates a new photo uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +// +// Note that you must send animated GIFs as a document. +func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig { + return PhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewPhotoShare shares an existing photo. +// You may use this to reshare an existing photo without reuploading it. +// +// chatID is where to send it, fileID is the ID of the file +// already uploaded. +func NewPhotoShare(chatID int64, fileID string) PhotoConfig { + return PhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewAudioUpload creates a new audio uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewAudioUpload(chatID int64, file interface{}) AudioConfig { + return AudioConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewAudioShare shares an existing audio file. +// You may use this to reshare an existing audio file without +// reuploading it. +// +// chatID is where to send it, fileID is the ID of the audio +// already uploaded. +func NewAudioShare(chatID int64, fileID string) AudioConfig { + return AudioConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewDocumentUpload creates a new document uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig { + return DocumentConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewDocumentShare shares an existing document. +// You may use this to reshare an existing document without +// reuploading it. +// +// chatID is where to send it, fileID is the ID of the document +// already uploaded. +func NewDocumentShare(chatID int64, fileID string) DocumentConfig { + return DocumentConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewStickerUpload creates a new sticker uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewStickerUpload(chatID int64, file interface{}) StickerConfig { + return StickerConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewStickerShare shares an existing sticker. +// You may use this to reshare an existing sticker without +// reuploading it. +// +// chatID is where to send it, fileID is the ID of the sticker +// already uploaded. +func NewStickerShare(chatID int64, fileID string) StickerConfig { + return StickerConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewVideoUpload creates a new video uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewVideoUpload(chatID int64, file interface{}) VideoConfig { + return VideoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewVideoShare shares an existing video. +// You may use this to reshare an existing video without reuploading it. +// +// chatID is where to send it, fileID is the ID of the video +// already uploaded. +func NewVideoShare(chatID int64, fileID string) VideoConfig { + return VideoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewVoiceUpload creates a new voice uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig { + return VoiceConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewVoiceShare shares an existing voice. +// You may use this to reshare an existing voice without reuploading it. +// +// chatID is where to send it, fileID is the ID of the video +// already uploaded. +func NewVoiceShare(chatID int64, fileID string) VoiceConfig { + return VoiceConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + +// NewContact allows you to send a shared contact. +func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { + return ContactConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + PhoneNumber: phoneNumber, + FirstName: firstName, + } +} + +// NewLocation shares your location. +// +// chatID is where to send it, latitude and longitude are coordinates. +func NewLocation(chatID int64, latitude float64, longitude float64) LocationConfig { + return LocationConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewVenue allows you to send a venue and its location. +func NewVenue(chatID int64, title, address string, latitude, longitude float64) VenueConfig { + return VenueConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + Title: title, + Address: address, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewChatAction sets a chat action. +// Actions last for 5 seconds, or until your next action. +// +// chatID is where to send it, action should be set via Chat constants. +func NewChatAction(chatID int64, action string) ChatActionConfig { + return ChatActionConfig{ + BaseChat: BaseChat{ChatID: chatID}, + Action: action, + } +} + +// NewUserProfilePhotos gets user profile photos. +// +// userID is the ID of the user you wish to get profile photos from. +func NewUserProfilePhotos(userID int) UserProfilePhotosConfig { + return UserProfilePhotosConfig{ + UserID: userID, + Offset: 0, + Limit: 0, + } +} + +// NewUpdate gets updates since the last Offset. +// +// offset is the last Update ID to include. +// You likely want to set this to the last Update ID plus 1. +func NewUpdate(offset int) UpdateConfig { + return UpdateConfig{ + Offset: offset, + Limit: 0, + Timeout: 0, + } +} + +// NewWebhook creates a new webhook. +// +// link is the url parsable link you wish to get the updates. +func NewWebhook(link string) WebhookConfig { + u, _ := url.Parse(link) + + return WebhookConfig{ + URL: u, + } +} + +// NewWebhookWithCert creates a new webhook with a certificate. +// +// link is the url you wish to get webhooks, +// file contains a string to a file, FileReader, or FileBytes. +func NewWebhookWithCert(link string, file interface{}) WebhookConfig { + u, _ := url.Parse(link) + + return WebhookConfig{ + URL: u, + Certificate: file, + } +} + +// NewInlineQueryResultArticle creates a new inline query article. +func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + }, + } +} + +// NewInlineQueryResultArticleMarkdown creates a new inline query article with Markdown parsing. +func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + ParseMode: "Markdown", + }, + } +} + +// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing. +func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + ParseMode: "HTML", + }, + } +} + +// NewInlineQueryResultGIF creates a new inline query GIF. +func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF { + return InlineQueryResultGIF{ + Type: "gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. +func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { + return InlineQueryResultMPEG4GIF{ + Type: "mpeg4_gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultPhoto creates a new inline query photo. +func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { + return InlineQueryResultPhoto{ + Type: "photo", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultPhotoWithThumb creates a new inline query photo. +func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResultPhoto { + return InlineQueryResultPhoto{ + Type: "photo", + ID: id, + URL: url, + ThumbURL: thumb, + } +} + +// NewInlineQueryResultVideo creates a new inline query video. +func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { + return InlineQueryResultVideo{ + Type: "video", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultAudio creates a new inline query audio. +func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { + return InlineQueryResultAudio{ + Type: "audio", + ID: id, + URL: url, + Title: title, + } +} + +// NewInlineQueryResultVoice creates a new inline query voice. +func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { + return InlineQueryResultVoice{ + Type: "voice", + ID: id, + URL: url, + Title: title, + } +} + +// NewInlineQueryResultDocument creates a new inline query document. +func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument { + return InlineQueryResultDocument{ + Type: "document", + ID: id, + URL: url, + Title: title, + MimeType: mimeType, + } +} + +// NewInlineQueryResultLocation creates a new inline query location. +func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation { + return InlineQueryResultLocation{ + Type: "location", + ID: id, + Title: title, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewEditMessageText allows you to edit the text of a message. +func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig { + return EditMessageTextConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + }, + Text: text, + } +} + +// NewEditMessageCaption allows you to edit the caption of a message. +func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig { + return EditMessageCaptionConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + }, + Caption: caption, + } +} + +// NewEditMessageReplyMarkup allows you to edit the inline +// keyboard markup. +func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig { + return EditMessageReplyMarkupConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + ReplyMarkup: &replyMarkup, + }, + } +} + +// NewHideKeyboard hides the keyboard, with the option for being selective +// or hiding for everyone. +func NewHideKeyboard(selective bool) ReplyKeyboardHide { + return ReplyKeyboardHide{ + HideKeyboard: true, + Selective: selective, + } +} + +// NewKeyboardButton creates a regular keyboard button. +func NewKeyboardButton(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + } +} + +// NewKeyboardButtonContact creates a keyboard button that requests +// user contact information upon click. +func NewKeyboardButtonContact(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + RequestContact: true, + } +} + +// NewKeyboardButtonLocation creates a keyboard button that requests +// user location information upon click. +func NewKeyboardButtonLocation(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + RequestLocation: true, + } +} + +// NewKeyboardButtonRow creates a row of keyboard buttons. +func NewKeyboardButtonRow(buttons ...KeyboardButton) []KeyboardButton { + var row []KeyboardButton + + row = append(row, buttons...) + + return row +} + +// NewReplyKeyboard creates a new regular keyboard with sane defaults. +func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup { + var keyboard [][]KeyboardButton + + keyboard = append(keyboard, rows...) + + return ReplyKeyboardMarkup{ + ResizeKeyboard: true, + Keyboard: keyboard, + } +} + +// NewInlineKeyboardButtonData creates an inline keyboard button with text +// and data for a callback. +func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + CallbackData: &data, + } +} + +// NewInlineKeyboardButtonURL creates an inline keyboard button with text +// which goes to a URL. +func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + URL: &url, + } +} + +// NewInlineKeyboardButtonSwitch creates an inline keyboard button with +// text which allows the user to switch to a chat or return to a chat. +func NewInlineKeyboardButtonSwitch(text, sw string) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + SwitchInlineQuery: &sw, + } +} + +// NewInlineKeyboardRow creates an inline keyboard row with buttons. +func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton { + var row []InlineKeyboardButton + + row = append(row, buttons...) + + return row +} + +// NewInlineKeyboardMarkup creates a new inline keyboard. +func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) InlineKeyboardMarkup { + var keyboard [][]InlineKeyboardButton + + keyboard = append(keyboard, rows...) + + return InlineKeyboardMarkup{ + InlineKeyboard: keyboard, + } +} + +// NewCallback creates a new callback message. +func NewCallback(id, text string) CallbackConfig { + return CallbackConfig{ + CallbackQueryID: id, + Text: text, + ShowAlert: false, + } +} + +// NewCallbackWithAlert creates a new callback message that alerts +// the user. +func NewCallbackWithAlert(id, text string) CallbackConfig { + return CallbackConfig{ + CallbackQueryID: id, + Text: text, + ShowAlert: true, + } +} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go new file mode 100644 index 00000000..1c7b1b0f --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go @@ -0,0 +1,550 @@ +package tgbotapi + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "strings" + "time" +) + +// APIResponse is a response from the Telegram API with the result +// stored raw. +type APIResponse struct { + Ok bool `json:"ok"` + Result json.RawMessage `json:"result"` + ErrorCode int `json:"error_code"` + Description string `json:"description"` +} + +// Update is an update response, from GetUpdates. +type Update struct { + UpdateID int `json:"update_id"` + Message *Message `json:"message"` + EditedMessage *Message `json:"edited_message"` + InlineQuery *InlineQuery `json:"inline_query"` + ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` + CallbackQuery *CallbackQuery `json:"callback_query"` +} + +// User is a user on Telegram. +type User struct { + ID int `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` // optional + UserName string `json:"username"` // optional +} + +// String displays a simple text version of a user. +// +// It is normally a user's username, but falls back to a first/last +// name as available. +func (u *User) String() string { + if u.UserName != "" { + return u.UserName + } + + name := u.FirstName + if u.LastName != "" { + name += " " + u.LastName + } + + return name +} + +// GroupChat is a group chat. +type GroupChat struct { + ID int `json:"id"` + Title string `json:"title"` +} + +// Chat contains information about the place a message was sent. +type Chat struct { + ID int64 `json:"id"` + Type string `json:"type"` + Title string `json:"title"` // optional + UserName string `json:"username"` // optional + FirstName string `json:"first_name"` // optional + LastName string `json:"last_name"` // optional +} + +// IsPrivate returns if the Chat is a private conversation. +func (c Chat) IsPrivate() bool { + return c.Type == "private" +} + +// IsGroup returns if the Chat is a group. +func (c Chat) IsGroup() bool { + return c.Type == "group" +} + +// IsSuperGroup returns if the Chat is a supergroup. +func (c Chat) IsSuperGroup() bool { + return c.Type == "supergroup" +} + +// IsChannel returns if the Chat is a channel. +func (c Chat) IsChannel() bool { + return c.Type == "channel" +} + +// ChatConfig returns a ChatConfig struct for chat related methods. +func (c Chat) ChatConfig() ChatConfig { + return ChatConfig{ChatID: c.ID} +} + +// Message is returned by almost every request, and contains data about +// almost anything. +type Message struct { + MessageID int `json:"message_id"` + From *User `json:"from"` // optional + Date int `json:"date"` + Chat *Chat `json:"chat"` + ForwardFrom *User `json:"forward_from"` // optional + ForwardFromChat *Chat `json:"forward_from_chat"` // optional + ForwardDate int `json:"forward_date"` // optional + ReplyToMessage *Message `json:"reply_to_message"` // optional + EditDate int `json:"edit_date"` // optional + Text string `json:"text"` // optional + Entities *[]MessageEntity `json:"entities"` // optional + Audio *Audio `json:"audio"` // optional + Document *Document `json:"document"` // optional + Photo *[]PhotoSize `json:"photo"` // optional + Sticker *Sticker `json:"sticker"` // optional + Video *Video `json:"video"` // optional + Voice *Voice `json:"voice"` // optional + Caption string `json:"caption"` // optional + Contact *Contact `json:"contact"` // optional + Location *Location `json:"location"` // optional + Venue *Venue `json:"venue"` // optional + NewChatMember *User `json:"new_chat_member"` // optional + LeftChatMember *User `json:"left_chat_member"` // optional + NewChatTitle string `json:"new_chat_title"` // optional + NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional + DeleteChatPhoto bool `json:"delete_chat_photo"` // optional + GroupChatCreated bool `json:"group_chat_created"` // optional + SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional + ChannelChatCreated bool `json:"channel_chat_created"` // optional + MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional + MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional + PinnedMessage *Message `json:"pinned_message"` // optional +} + +// Time converts the message timestamp into a Time. +func (m *Message) Time() time.Time { + return time.Unix(int64(m.Date), 0) +} + +// IsCommand returns true if message starts with '/'. +func (m *Message) IsCommand() bool { + return m.Text != "" && m.Text[0] == '/' +} + +// Command checks if the message was a command and if it was, returns the +// command. If the Message was not a command, it returns an empty string. +// +// If the command contains the at bot syntax, it removes the bot name. +func (m *Message) Command() string { + if !m.IsCommand() { + return "" + } + + command := strings.SplitN(m.Text, " ", 2)[0][1:] + + if i := strings.Index(command, "@"); i != -1 { + command = command[:i] + } + + return command +} + +// CommandArguments checks if the message was a command and if it was, +// returns all text after the command name. If the Message was not a +// command, it returns an empty string. +func (m *Message) CommandArguments() string { + if !m.IsCommand() { + return "" + } + + split := strings.SplitN(m.Text, " ", 2) + if len(split) != 2 { + return "" + } + + return strings.SplitN(m.Text, " ", 2)[1] +} + +// MessageEntity contains information about data in a Message. +type MessageEntity struct { + Type string `json:"type"` + Offset int `json:"offset"` + Length int `json:"length"` + URL string `json:"url"` // optional + User *User `json:"user"` // optional +} + +// ParseURL attempts to parse a URL contained within a MessageEntity. +func (entity MessageEntity) ParseURL() (*url.URL, error) { + if entity.URL == "" { + return nil, errors.New(ErrBadURL) + } + + return url.Parse(entity.URL) +} + +// PhotoSize contains information about photos. +type PhotoSize struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + FileSize int `json:"file_size"` // optional +} + +// Audio contains information about audio. +type Audio struct { + FileID string `json:"file_id"` + Duration int `json:"duration"` + Performer string `json:"performer"` // optional + Title string `json:"title"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + +// Document contains information about a document. +type Document struct { + FileID string `json:"file_id"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileName string `json:"file_name"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + +// Sticker contains information about a sticker. +type Sticker struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Thumbnail *PhotoSize `json:"thumb"` // optional + Emoji string `json:"emoji"` // optional + FileSize int `json:"file_size"` // optional +} + +// Video contains information about a video. +type Video struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + +// Voice contains information about a voice. +type Voice struct { + FileID string `json:"file_id"` + Duration int `json:"duration"` + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + +// Contact contains information about a contact. +// +// Note that LastName and UserID may be empty. +type Contact struct { + PhoneNumber string `json:"phone_number"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` // optional + UserID int `json:"user_id"` // optional +} + +// Location contains information about a place. +type Location struct { + Longitude float64 `json:"longitude"` + Latitude float64 `json:"latitude"` +} + +// Venue contains information about a venue, including its Location. +type Venue struct { + Location Location `json:"location"` + Title string `json:"title"` + Address string `json:"address"` + FoursquareID string `json:"foursquare_id"` // optional +} + +// UserProfilePhotos contains a set of user profile photos. +type UserProfilePhotos struct { + TotalCount int `json:"total_count"` + Photos [][]PhotoSize `json:"photos"` +} + +// File contains information about a file to download from Telegram. +type File struct { + FileID string `json:"file_id"` + FileSize int `json:"file_size"` // optional + FilePath string `json:"file_path"` // optional +} + +// Link returns a full path to the download URL for a File. +// +// It requires the Bot Token to create the link. +func (f *File) Link(token string) string { + return fmt.Sprintf(FileEndpoint, token, f.FilePath) +} + +// ReplyKeyboardMarkup allows the Bot to set a custom keyboard. +type ReplyKeyboardMarkup struct { + Keyboard [][]KeyboardButton `json:"keyboard"` + ResizeKeyboard bool `json:"resize_keyboard"` // optional + OneTimeKeyboard bool `json:"one_time_keyboard"` // optional + Selective bool `json:"selective"` // optional +} + +// KeyboardButton is a button within a custom keyboard. +type KeyboardButton struct { + Text string `json:"text"` + RequestContact bool `json:"request_contact"` + RequestLocation bool `json:"request_location"` +} + +// ReplyKeyboardHide allows the Bot to hide a custom keyboard. +type ReplyKeyboardHide struct { + HideKeyboard bool `json:"hide_keyboard"` + Selective bool `json:"selective"` // optional +} + +// InlineKeyboardMarkup is a custom keyboard presented for an inline bot. +type InlineKeyboardMarkup struct { + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` +} + +// InlineKeyboardButton is a button within a custom keyboard for +// inline query responses. +// +// Note that some values are references as even an empty string +// will change behavior. +type InlineKeyboardButton struct { + Text string `json:"text"` + URL *string `json:"url,omitempty"` // optional + CallbackData *string `json:"callback_data,omitempty"` // optional + SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional +} + +// CallbackQuery is data sent when a keyboard button with callback data +// is clicked. +type CallbackQuery struct { + ID string `json:"id"` + From *User `json:"from"` + Message *Message `json:"message"` // optional + InlineMessageID string `json:"inline_message_id"` // optional + Data string `json:"data"` // optional +} + +// ForceReply allows the Bot to have users directly reply to it without +// additional interaction. +type ForceReply struct { + ForceReply bool `json:"force_reply"` + Selective bool `json:"selective"` // optional +} + +// ChatMember is information about a member in a chat. +type ChatMember struct { + User *User `json:"user"` + Status string `json:"status"` +} + +// IsCreator returns if the ChatMember was the creator of the chat. +func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" } + +// IsAdministrator returns if the ChatMember is a chat administrator. +func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" } + +// IsMember returns if the ChatMember is a current member of the chat. +func (chat ChatMember) IsMember() bool { return chat.Status == "member" } + +// HasLeft returns if the ChatMember left the chat. +func (chat ChatMember) HasLeft() bool { return chat.Status == "left" } + +// WasKicked returns if the ChatMember was kicked from the chat. +func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" } + +// InlineQuery is a Query from Telegram for an inline request. +type InlineQuery struct { + ID string `json:"id"` + From *User `json:"from"` + Location *Location `json:"location"` // optional + Query string `json:"query"` + Offset string `json:"offset"` +} + +// InlineQueryResultArticle is an inline query response article. +type InlineQueryResultArticle struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + Title string `json:"title"` // required + InputMessageContent interface{} `json:"input_message_content,omitempty"` // required + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + URL string `json:"url"` + HideURL bool `json:"hide_url"` + Description string `json:"description"` + ThumbURL string `json:"thumb_url"` + ThumbWidth int `json:"thumb_width"` + ThumbHeight int `json:"thumb_height"` +} + +// InlineQueryResultPhoto is an inline query response photo. +type InlineQueryResultPhoto struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"photo_url"` // required + MimeType string `json:"mime_type"` + Width int `json:"photo_width"` + Height int `json:"photo_height"` + ThumbURL string `json:"thumb_url"` + Title string `json:"title"` + Description string `json:"description"` + Caption string `json:"caption"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultGIF is an inline query response GIF. +type InlineQueryResultGIF struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"gif_url"` // required + Width int `json:"gif_width"` + Height int `json:"gif_height"` + ThumbURL string `json:"thumb_url"` + Title string `json:"title"` + Caption string `json:"caption"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF. +type InlineQueryResultMPEG4GIF struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"mpeg4_url"` // required + Width int `json:"mpeg4_width"` + Height int `json:"mpeg4_height"` + ThumbURL string `json:"thumb_url"` + Title string `json:"title"` + Caption string `json:"caption"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultVideo is an inline query response video. +type InlineQueryResultVideo struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"video_url"` // required + MimeType string `json:"mime_type"` // required + ThumbURL string `json:"thumb_url"` + Title string `json:"title"` + Caption string `json:"caption"` + Width int `json:"video_width"` + Height int `json:"video_height"` + Duration int `json:"video_duration"` + Description string `json:"description"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultAudio is an inline query response audio. +type InlineQueryResultAudio struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"audio_url"` // required + Title string `json:"title"` // required + Performer string `json:"performer"` + Duration int `json:"audio_duration"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultVoice is an inline query response voice. +type InlineQueryResultVoice struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"voice_url"` // required + Title string `json:"title"` // required + Duration int `json:"voice_duration"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultDocument is an inline query response document. +type InlineQueryResultDocument struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + Title string `json:"title"` // required + Caption string `json:"caption"` + URL string `json:"document_url"` // required + MimeType string `json:"mime_type"` // required + Description string `json:"description"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` + ThumbURL string `json:"thumb_url"` + ThumbWidth int `json:"thumb_width"` + ThumbHeight int `json:"thumb_height"` +} + +// InlineQueryResultLocation is an inline query response location. +type InlineQueryResultLocation struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + Latitude float64 `json:"latitude"` // required + Longitude float64 `json:"longitude"` // required + Title string `json:"title"` // required + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` + ThumbURL string `json:"thumb_url"` + ThumbWidth int `json:"thumb_width"` + ThumbHeight int `json:"thumb_height"` +} + +// ChosenInlineResult is an inline query result chosen by a User +type ChosenInlineResult struct { + ResultID string `json:"result_id"` + From *User `json:"from"` + Location *Location `json:"location"` + InlineMessageID string `json:"inline_message_id"` + Query string `json:"query"` +} + +// InputTextMessageContent contains text for displaying +// as an inline query result. +type InputTextMessageContent struct { + Text string `json:"message_text"` + ParseMode string `json:"parse_mode"` + DisableWebPagePreview bool `json:"disable_web_page_preview"` +} + +// InputLocationMessageContent contains a location for displaying +// as an inline query result. +type InputLocationMessageContent struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// InputVenueMessageContent contains a venue for displaying +// as an inline query result. +type InputVenueMessageContent struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Title string `json:"title"` + Address string `json:"address"` + FoursquareID string `json:"foursquare_id"` +} + +// InputContactMessageContent contains a contact for displaying +// as an inline query result. +type InputContactMessageContent struct { + PhoneNumber string `json:"phone_number"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` +} diff --git a/vendor/manifest b/vendor/manifest index fd4a0969..4e5f5d11 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -59,6 +59,14 @@ "branch": "master", "notests": true }, + { + "importpath": "github.com/go-telegram-bot-api/telegram-bot-api", + "repository": "https://github.com/go-telegram-bot-api/telegram-bot-api", + "vcs": "git", + "revision": "a7f48eb2dd301356942677e65bebe0c9aef07013", + "branch": "master", + "notests": true + }, { "importpath": "github.com/gorilla/schema", "repository": "https://github.com/gorilla/schema", @@ -166,6 +174,14 @@ "branch": "master", "notests": true }, + { + "importpath": "github.com/technoweenie/multipartstreamer", + "repository": "https://github.com/technoweenie/multipartstreamer", + "vcs": "git", + "revision": "a90a01d73ae432e2611d178c18367fbaa13e0154", + "branch": "master", + "notests": true + }, { "importpath": "github.com/thoj/go-ircevent", "repository": "https://github.com/thoj/go-ircevent",