summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWim <wim@42.be>2016-11-20 17:02:17 +0100
committerWim <wim@42.be>2016-11-20 17:02:17 +0100
commit94ea775232eabfd3945ef9b5d6e93e3505fd4a99 (patch)
tree9930dd9d68cee5e7b8153d3e3345539f21fe1fbe
parent449ed31e25f2c21ac9db258999a5a893889983eb (diff)
parent2e4b7fac1146c186f28599d62379d7e9912fda81 (diff)
downloadmatterbridge-msglm-94ea775232eabfd3945ef9b5d6e93e3505fd4a99.tar.gz
matterbridge-msglm-94ea775232eabfd3945ef9b5d6e93e3505fd4a99.tar.bz2
matterbridge-msglm-94ea775232eabfd3945ef9b5d6e93e3505fd4a99.zip
Merge branch 'telegram'
Add telegram support
-rw-r--r--README.md5
-rw-r--r--bridge/bridge.go4
-rw-r--r--bridge/config/config.go1
-rw-r--r--bridge/telegram/telegram.go74
-rw-r--r--matterbridge.toml.sample31
-rw-r--r--vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt21
-rw-r--r--vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go650
-rw-r--r--vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go694
-rw-r--r--vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go598
-rw-r--r--vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go550
-rw-r--r--vendor/github.com/technoweenie/multipartstreamer/LICENSE7
-rw-r--r--vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go31
-rw-r--r--vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go27
-rw-r--r--vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go101
-rw-r--r--vendor/manifest16
15 files changed, 2808 insertions, 2 deletions
diff --git a/README.md b/README.md
index 80b6a679..28ecdc8a 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# matterbridge
-Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord
+Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord and Telegram.
-* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack and Discord. Pick and mix.
+* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord and Telegram. Pick and mix.
* Supports multiple channels.
* Matterbridge can also work with private groups on your mattermost.
* Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts.
@@ -21,6 +21,7 @@ Accounts to one of the supported bridges
* [Gitter] (https://gitter.im)
* [Slack] (https://slack.com)
* [Discord] (https://discordapp.com)
+* [Telegram] (https://telegram.org)
## Docker
Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml```
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/matterbridge.toml.sample b/matterbridge.toml.sample
index 831e99f2..2e5f8186 100644
--- a/matterbridge.toml.sample
+++ b/matterbridge.toml.sample
@@ -343,6 +343,36 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
ShowJoinPart=false
###################################################################
+#telegram section
+###################################################################
+[telegram]
+
+#You can configure multiple servers "[telegram.name]" or "[telegram.name2]"
+#In this example we use [telegram.secure]
+#REQUIRED
+[telegram.secure]
+#Token to connect with telegram API
+#REQUIRED
+Token="Yourtokenhere"
+
+#Nicks you want to ignore.
+#Messages from those users will not be sent to other bridges.
+#OPTIONAL
+IgnoreNicks="spammer1 spammer2"
+
+#RemoteNickFormat defines how remote users appear on this bridge
+#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
+#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
+#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
+#OPTIONAL (default empty)
+RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
+
+#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
+#OPTIONAL (default false)
+ShowJoinPart=false
+
+
+###################################################################
#Gateway configuration
###################################################################
@@ -381,6 +411,7 @@ enable=true
#discord - channel (without the #)
# - ID:123456789 (where 123456789 is the channel ID)
# (https://github.com/42wim/matterbridge/issues/57)
+ #telegram - chatid (a large negative number, eg -123456789)
#REQUIRED
channel="#testing"
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/github.com/technoweenie/multipartstreamer/LICENSE b/vendor/github.com/technoweenie/multipartstreamer/LICENSE
new file mode 100644
index 00000000..20d92fba
--- /dev/null
+++ b/vendor/github.com/technoweenie/multipartstreamer/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2013-* rick olson
+
+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/technoweenie/multipartstreamer/examples/multipart.go b/vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
new file mode 100644
index 00000000..971078be
--- /dev/null
+++ b/vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "os"
+ "path/filepath"
+)
+
+func main() {
+ defaultPath, _ := os.Getwd()
+ defaultFile := filepath.Join(defaultPath, "streamer.go")
+ fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.")
+ flag.Parse()
+
+ buffer := bytes.NewBufferString("")
+ writer := multipart.NewWriter(buffer)
+
+ fmt.Println("Adding the file to the multipart writer")
+ fileWriter, _ := writer.CreateFormFile("file", *fullpath)
+ fileData, _ := os.Open(*fullpath)
+ io.Copy(fileWriter, fileData)
+ writer.Close()
+
+ fmt.Println("Writing the multipart data to a file")
+ output, _ := os.Create("multiparttest")
+ io.Copy(output, buffer)
+}
diff --git a/vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go b/vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
new file mode 100644
index 00000000..324f7e12
--- /dev/null
+++ b/vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "github.com/technoweenie/multipartstreamer"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+func main() {
+ defaultPath, _ := os.Getwd()
+ defaultFile := filepath.Join(defaultPath, "streamer.go")
+ fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.")
+ flag.Parse()
+
+ ms := multipartstreamer.New()
+
+ fmt.Println("Adding the file to the multipart writer")
+ ms.WriteFile("file", *fullpath)
+ reader := ms.GetReader()
+
+ fmt.Println("Writing the multipart data to a file")
+ file, _ := os.Create("streamtest")
+ io.Copy(file, reader)
+}
diff --git a/vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go b/vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
new file mode 100644
index 00000000..26d8e850
--- /dev/null
+++ b/vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
@@ -0,0 +1,101 @@
+/*
+Package multipartstreamer helps you encode large files in MIME multipart format
+without reading the entire content into memory. It uses io.MultiReader to
+combine an inner multipart.Reader with a file handle.
+*/
+package multipartstreamer
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "os"
+ "path/filepath"
+)
+
+type MultipartStreamer struct {
+ ContentType string
+ bodyBuffer *bytes.Buffer
+ bodyWriter *multipart.Writer
+ closeBuffer *bytes.Buffer
+ reader io.Reader
+ contentLength int64
+}
+
+// New initializes a new MultipartStreamer.
+func New() (m *MultipartStreamer) {
+ m = &MultipartStreamer{bodyBuffer: new(bytes.Buffer)}
+
+ m.bodyWriter = multipart.NewWriter(m.bodyBuffer)
+ boundary := m.bodyWriter.Boundary()
+ m.ContentType = "multipart/form-data; boundary=" + boundary
+
+ closeBoundary := fmt.Sprintf("\r\n--%s--\r\n", boundary)
+ m.closeBuffer = bytes.NewBufferString(closeBoundary)
+
+ return
+}
+
+// WriteFields writes multiple form fields to the multipart.Writer.
+func (m *MultipartStreamer) WriteFields(fields map[string]string) error {
+ var err error
+
+ for key, value := range fields {
+ err = m.bodyWriter.WriteField(key, value)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// WriteReader adds an io.Reader to get the content of a file. The reader is
+// not accessed until the multipart.Reader is copied to some output writer.
+func (m *MultipartStreamer) WriteReader(key, filename string, size int64, reader io.Reader) (err error) {
+ m.reader = reader
+ m.contentLength = size
+
+ _, err = m.bodyWriter.CreateFormFile(key, filename)
+ return
+}
+
+// WriteFile is a shortcut for adding a local file as an io.Reader.
+func (m *MultipartStreamer) WriteFile(key, filename string) error {
+ fh, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+
+ stat, err := fh.Stat()
+ if err != nil {
+ return err
+ }
+
+ return m.WriteReader(key, filepath.Base(filename), stat.Size(), fh)
+}
+
+// SetupRequest sets up the http.Request body, and some crucial HTTP headers.
+func (m *MultipartStreamer) SetupRequest(req *http.Request) {
+ req.Body = m.GetReader()
+ req.Header.Add("Content-Type", m.ContentType)
+ req.ContentLength = m.Len()
+}
+
+func (m *MultipartStreamer) Boundary() string {
+ return m.bodyWriter.Boundary()
+}
+
+// Len calculates the byte size of the multipart content.
+func (m *MultipartStreamer) Len() int64 {
+ return m.contentLength + int64(m.bodyBuffer.Len()) + int64(m.closeBuffer.Len())
+}
+
+// GetReader gets an io.ReadCloser for passing to an http.Request.
+func (m *MultipartStreamer) GetReader() io.ReadCloser {
+ reader := io.MultiReader(m.bodyBuffer, m.reader, m.closeBuffer)
+ return ioutil.NopCloser(reader)
+}
diff --git a/vendor/manifest b/vendor/manifest
index fd4a0969..4e5f5d11 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -60,6 +60,14 @@
"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",
"vcs": "",
@@ -167,6 +175,14 @@
"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",
"vcs": "git",