Merge branch 'telegram'

Add telegram support
This commit is contained in:
Wim 2016-11-20 17:02:17 +01:00
commit 94ea775232
15 changed files with 2808 additions and 2 deletions

View File

@ -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```

View File

@ -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
}

View File

@ -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
}

View File

@ -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}
}
}

View File

@ -342,6 +342,36 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#OPTIONAL (default false)
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"

View File

@ -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.

View File

@ -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)
}

View File

@ -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
}

View File

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

View File

@ -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"`
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

16
vendor/manifest vendored
View File

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