Merge pull request #29 from zhulik/master

Major overhaul to make everything easier to use
bot-api-6.1
Syfaro 2015-11-22 08:49:36 -06:00
commit b034326d85
19 changed files with 1477 additions and 1363 deletions

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
.idea/
coverage.out

5
.travis.yml 100644
View File

@ -0,0 +1,5 @@
language: go
go:
- 1.4
- tip

View File

@ -1,6 +1,7 @@
# Golang bindings for the Telegram Bot API
[![GoDoc](https://godoc.org/github.com/Syfaro/telegram-bot-api?status.svg)](http://godoc.org/github.com/Syfaro/telegram-bot-api)
[![Travis](https://travis-ci.org/Syfaro/telegram-bot-api.svg)](https://travis-ci.org/Syfaro/telegram-bot-api)
All methods have been added, and all features should be available.
If you want a feature that hasn't been added yet or something is broken, open an issue and I'll see what I can do.
@ -34,18 +35,15 @@ func main() {
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
err = bot.UpdatesChan(u)
if err != nil {
log.Panic(err)
}
updates, err := bot.GetUpdatesChan(u)
for update := range bot.Updates {
for update := range updates {
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
msg.ReplyToMessageID = update.Message.MessageID
bot.SendMessage(msg)
bot.Send(msg)
}
}
```
@ -76,10 +74,10 @@ func main() {
log.Fatal(err)
}
bot.ListenForWebhook("/"+bot.Token)
updates, _ := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
for update := range bot.Updates {
for update := range updates {
log.Printf("%+v\n", update)
}
}
@ -87,4 +85,4 @@ func main() {
If you need, you may generate a self signed certficate, as this requires HTTPS / TLS. The above example tells Telegram that this is your certificate and that it should be trusted, even though it is not properly signed.
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj -nodes
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes

424
bot.go
View File

@ -2,16 +2,27 @@
package tgbotapi
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/technoweenie/multipartstreamer"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
// BotAPI has methods for interacting with all of Telegram's Bot API endpoints.
type BotAPI struct {
Token string `json:"token"`
Debug bool `json:"debug"`
Self User `json:"-"`
Updates chan Update `json:"-"`
Client *http.Client `json:"-"`
Token string `json:"token"`
Debug bool `json:"debug"`
Self User `json:"-"`
Client *http.Client `json:"-"`
}
// NewBotAPI creates a new BotAPI instance.
@ -37,3 +48,406 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
return bot, nil
}
// MakeRequest makes a request to a specific endpoint with our token.
// All requests are POSTs because Telegram doesn't care, and it's easier.
func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
resp, err := bot.Client.PostForm(fmt.Sprintf(APIEndpoint, bot.Token, endpoint), params)
if err != nil {
return APIResponse{}, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden {
return APIResponse{}, errors.New(APIForbidden)
}
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
}
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.
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 {
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)
break
}
ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
default:
return APIResponse{}, errors.New("bad file type")
}
req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), nil)
ms.SetupRequest(req)
if err != nil {
return APIResponse{}, err
}
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
//
// Requires 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.
//
// There are no parameters for this method.
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)
if bot.Debug {
log.Printf("getMe: %+v\n", user)
}
return user, nil
}
// IsMessageToMe returns true if message directed to this bot
//
// Requires message
func (bot *BotAPI) IsMessageToMe(message Message) bool {
return strings.Contains(message.Text, "@"+bot.Self.UserName)
}
// Send will send event(Message, Photo, Audio, ChatAction, anything) to Telegram
//
// Requires Chattable
func (bot *BotAPI) Send(c Chattable) (Message, error) {
switch c.(type) {
case Fileable:
return bot.sendFile(c.(Fileable))
default:
return bot.sendChattable(c)
}
}
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)
}
}
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
}
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)
if bot.Debug {
log.Printf("%s resp: %+v\n", method, message)
}
return message, nil
}
func (bot *BotAPI) sendFile(config Fileable) (Message, error) {
if config.UseExistingFile() {
return bot.sendExisting(config.Method(), config)
}
return bot.uploadAndSend(config.Method(), config)
}
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.
//
// 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_id required to download a file.
//
// 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 not get old items, set Offset to one higher than the previous item.
// Set Timeout to a large number to reduce requests and get responses instantly.
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)
if bot.Debug {
log.Printf("getUpdates: %+v\n", updates)
}
return updates, nil
}
// RemoveWebhook removes webhook
//
// There are no parameters for this method.
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!
//
// Requires URL OR to set Clear to true.
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.
//
// Requires UpdateConfig
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, http.Handler) {
updatesChan := make(chan Update, 100)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body)
var update Update
json.Unmarshal(bytes, &update)
updatesChan <- update
})
http.HandleFunc(pattern, handler)
return updatesChan, handler
}

View File

@ -1,63 +1,406 @@
package tgbotapi_test
import (
"github.com/syfaro/telegram-bot-api"
"github.com/Syfaro/telegram-bot-api"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func TestMain(m *testing.M) {
botToken := os.Getenv("TELEGRAM_API_TOKEN")
const (
TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I"
ChatID = 76918703
ReplyToMessageID = 35
ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
ExistingDocumentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag"
ExistingAudioFileID = "BQADAgADRgADjMcoCdXg3lSIN49lAg"
ExistingVoiceFileID = "AwADAgADWQADjMcoCeul6r_q52IyAg"
ExistingVideoFileID = "BAADAgADZgADjMcoCav432kYe0FRAg"
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
)
if botToken == "" {
log.Panic("You must provide a TELEGRAM_API_TOKEN env variable to test!")
func getBot(t *testing.T) (*tgbotapi.BotAPI, error) {
bot, err := tgbotapi.NewBotAPI(TestToken)
if err != nil {
t.Fail()
}
os.Exit(m.Run())
return bot, err
}
func TestNewBotAPI_notoken(t *testing.T) {
_, err := tgbotapi.NewBotAPI("")
if err.Error() != tgbotapi.APIForbidden {
t.Fail()
}
}
func TestNewBotAPI_token(t *testing.T) {
_, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
if err != nil {
if err == nil {
t.Fail()
}
}
func TestGetUpdates(t *testing.T) {
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
if err != nil {
t.Fail()
}
bot, _ := getBot(t)
u := tgbotapi.NewUpdate(0)
_, err = bot.GetUpdates(u)
_, err := bot.GetUpdates(u)
if err != nil {
t.Fail()
}
}
func TestSendMessage(t *testing.T) {
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
func TestSendWithMessage(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
msg := tgbotapi.NewMessage(36529758, "A test message from the test library in telegram-bot-api")
bot.SendMessage(msg)
func TestSendWithMessageReply(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithMessageForward(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewForward(ChatID, ChatID, ReplyToMessageID)
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewPhoto(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg")
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewPhotoWithFileBytes(t *testing.T) {
bot, _ := getBot(t)
data, _ := ioutil.ReadFile("tests/image.jpg")
b := tgbotapi.FileBytes{Name: "image.jpg", Bytes: data}
msg := tgbotapi.NewPhotoUpload(ChatID, b)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewPhotoWithFileReader(t *testing.T) {
bot, _ := getBot(t)
f, _ := os.Open("tests/image.jpg")
reader := tgbotapi.FileReader{Name: "image.jpg", Reader: f, Size: -1}
msg := tgbotapi.NewPhotoUpload(ChatID, reader)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewPhotoReply(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg")
msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingPhoto(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewPhotoShare(ChatID, ExistingPhotoFileID)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewDocument(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewDocumentUpload(ChatID, "tests/image.jpg")
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingDocument(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewDocumentShare(ChatID, ExistingDocumentFileID)
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewAudio(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewAudioUpload(ChatID, "tests/audio.mp3")
msg.Title = "TEST"
msg.Duration = 10
msg.Performer = "TEST"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingAudio(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewAudioShare(ChatID, ExistingAudioFileID)
msg.Title = "TEST"
msg.Duration = 10
msg.Performer = "TEST"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewVoice(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewVoiceUpload(ChatID, "tests/voice.ogg")
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingVoice(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewVoiceShare(ChatID, ExistingVoiceFileID)
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithLocation(t *testing.T) {
bot, _ := getBot(t)
_, err := bot.Send(tgbotapi.NewLocation(ChatID, 40, 40))
if err != nil {
t.Fail()
}
}
func TestSendWithNewVideo(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewVideoUpload(ChatID, "tests/video.mp4")
msg.Duration = 10
msg.Caption = "TEST"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingVideo(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewVideoShare(ChatID, ExistingVideoFileID)
msg.Duration = 10
msg.Caption = "TEST"
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewSticker(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg")
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingSticker(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID)
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg")
msg.ReplyMarkup = tgbotapi.ReplyKeyboardHide{true, false}
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID)
msg.ReplyMarkup = tgbotapi.ReplyKeyboardHide{true, false}
_, err := bot.Send(msg)
if err != nil {
t.Fail()
}
}
func TestGetFile(t *testing.T) {
bot, _ := getBot(t)
file := tgbotapi.FileConfig{ExistingPhotoFileID}
_, err := bot.GetFile(file)
if err != nil {
t.Fail()
}
}
func TestSendChatConfig(t *testing.T) {
bot, _ := getBot(t)
_, err := bot.Send(tgbotapi.NewChatAction(ChatID, tgbotapi.ChatTyping))
if err != nil {
t.Fail()
}
}
func TestGetUserProfilePhotos(t *testing.T) {
bot, _ := getBot(t)
_, err := bot.GetUserProfilePhotos(tgbotapi.NewUserProfilePhotos(ChatID))
if err != nil {
t.Fail()
}
}
func TestListenForWebhook(t *testing.T) {
bot, _ := getBot(t)
_, handler := bot.ListenForWebhook("/")
req, _ := http.NewRequest("GET", "", strings.NewReader("{}"))
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Home page didn't return %v", http.StatusOK)
}
}
func TestSetWebhookWithCert(t *testing.T) {
bot, _ := getBot(t)
bot.RemoveWebhook()
wh := tgbotapi.NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem")
_, err := bot.SetWebhook(wh)
if err != nil {
t.Fail()
}
bot.RemoveWebhook()
}
func TestSetWebhookWithoutCert(t *testing.T) {
bot, _ := getBot(t)
bot.RemoveWebhook()
wh := tgbotapi.NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
_, err := bot.SetWebhook(wh)
if err != nil {
t.Fail()
}
bot.RemoveWebhook()
}
func TestUpdatesChan(t *testing.T) {
bot, _ := getBot(t)
var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0)
ucfg.Timeout = 60
_, err := bot.GetUpdatesChan(ucfg)
if err != nil {
t.Fail()
}
}
func ExampleNewBotAPI() {
@ -73,14 +416,37 @@ func ExampleNewBotAPI() {
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
err = bot.UpdatesChan(u)
updates, err := bot.GetUpdatesChan(u)
for update := range bot.Updates {
for update := range updates {
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
msg.ReplyToMessageID = update.Message.MessageID
bot.SendMessage(msg)
bot.Send(msg)
}
}
func ExampleNewWebhook() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
if err != nil {
log.Fatal(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem"))
if err != nil {
log.Fatal(err)
}
updates, _ := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
for update := range updates {
log.Printf("%+v\n", update)
}
}

498
configs.go 100644
View File

@ -0,0 +1,498 @@
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 (
// APIForbidden happens when a token is bad
APIForbidden = "forbidden"
)
// Constant values for ParseMode in MessageConfig
const (
ModeMarkdown = "Markdown"
)
//Chattable represents any event in chat(MessageConfig, PhotoConfig, ChatActionConfig and others)
type Chattable interface {
Values() (url.Values, error)
Method() string
}
//Fileable represents any file event(PhotoConfig, DocumentConfig, AudioConfig, VoiceConfig, VideoConfig, StickerConfig)
type Fileable interface {
Chattable
Params() (map[string]string, error)
Name() string
GetFile() interface{}
UseExistingFile() bool
}
// BaseChat is base struct for all chat event(Message, Photo and so on)
type BaseChat struct {
ChatID int
ChannelUsername string
ReplyToMessageID int
ReplyMarkup interface{}
}
// 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.Itoa(chat.ChatID))
}
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))
}
return v, nil
}
// BaseFile is base struct for all file events(PhotoConfig, DocumentConfig, AudioConfig, VoiceConfig, VideoConfig, StickerConfig)
type BaseFile struct {
BaseChat
FilePath string
File interface{}
FileID string
UseExisting bool
}
// Params returns 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.Itoa(file.ChatID)
}
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)
}
return params, nil
}
// GetFile returns abstract representation of File inside BaseFile
func (file BaseFile) GetFile() interface{} {
var result interface{}
if file.FilePath == "" {
result = file.File
} else {
result = file.FilePath
}
return result
}
// UseExistingFile returns true if BaseFile contains already uploaded file by FileID
func (file BaseFile) UseExistingFile() bool {
return file.UseExisting
}
// MessageConfig contains information about a SendMessage request.
type MessageConfig struct {
BaseChat
Text string
ParseMode string
DisableWebPagePreview bool
ReplyMarkup interface{}
}
// Values returns 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 int
FromChannelUsername string
MessageID int
}
// Values returns url.Values representation of ForwardConfig
func (config ForwardConfig) Values() (url.Values, error) {
v, _ := config.BaseChat.Values()
v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
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 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 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 return field name for uploading file
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 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 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 return field name for uploading file
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 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 map[string]string representation of DocumentConfig
func (config DocumentConfig) Params() (map[string]string, error) {
params, _ := config.BaseFile.Params()
return params, nil
}
// Name return field name for uploading file
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 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 map[string]string representation of StickerConfig
func (config StickerConfig) Params() (map[string]string, error) {
params, _ := config.BaseFile.Params()
return params, nil
}
// Name return field name for uploading file
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 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 map[string]string representation of VideoConfig
func (config VideoConfig) Params() (map[string]string, error) {
params, _ := config.BaseFile.Params()
return params, nil
}
// Name return field name for uploading file
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 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 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 return field name for uploading file
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
Longitude float64
}
// Values returns 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"
}
// ChatActionConfig contains information about a SendChatAction request.
type ChatActionConfig struct {
BaseChat
Action string
}
// Values returns 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"
}
// 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
}

View File

@ -10,10 +10,9 @@ import (
// chatID is where to send it, text is the message text.
func NewMessage(chatID int, text string) MessageConfig {
return MessageConfig{
ChatID: chatID,
Text: text,
BaseChat: BaseChat{ChatID: chatID, ReplyToMessageID: 0},
Text: text,
DisableWebPagePreview: false,
ReplyToMessageID: 0,
}
}
@ -23,7 +22,7 @@ func NewMessage(chatID int, text string) MessageConfig {
// and messageID is the ID of the original message.
func NewForward(chatID int, fromChatID int, messageID int) ForwardConfig {
return ForwardConfig{
ChatID: chatID,
BaseChat: BaseChat{ChatID: chatID},
FromChatID: fromChatID,
MessageID: messageID,
}
@ -36,9 +35,7 @@ func NewForward(chatID int, fromChatID int, messageID int) ForwardConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewPhotoUpload(chatID int, file interface{}) PhotoConfig {
return PhotoConfig{
ChatID: chatID,
UseExistingPhoto: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -48,9 +45,7 @@ func NewPhotoUpload(chatID int, file interface{}) PhotoConfig {
// chatID is where to send it, fileID is the ID of the file already uploaded.
func NewPhotoShare(chatID int, fileID string) PhotoConfig {
return PhotoConfig{
ChatID: chatID,
UseExistingPhoto: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -61,9 +56,7 @@ func NewPhotoShare(chatID int, fileID string) PhotoConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewAudioUpload(chatID int, file interface{}) AudioConfig {
return AudioConfig{
ChatID: chatID,
UseExistingAudio: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -73,9 +66,7 @@ func NewAudioUpload(chatID int, file interface{}) AudioConfig {
// chatID is where to send it, fileID is the ID of the audio already uploaded.
func NewAudioShare(chatID int, fileID string) AudioConfig {
return AudioConfig{
ChatID: chatID,
UseExistingAudio: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -86,9 +77,7 @@ func NewAudioShare(chatID int, fileID string) AudioConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewDocumentUpload(chatID int, file interface{}) DocumentConfig {
return DocumentConfig{
ChatID: chatID,
UseExistingDocument: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -98,9 +87,7 @@ func NewDocumentUpload(chatID int, file interface{}) DocumentConfig {
// chatID is where to send it, fileID is the ID of the document already uploaded.
func NewDocumentShare(chatID int, fileID string) DocumentConfig {
return DocumentConfig{
ChatID: chatID,
UseExistingDocument: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -110,9 +97,7 @@ func NewDocumentShare(chatID int, fileID string) DocumentConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewStickerUpload(chatID int, file interface{}) StickerConfig {
return StickerConfig{
ChatID: chatID,
UseExistingSticker: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -122,9 +107,7 @@ func NewStickerUpload(chatID int, file interface{}) StickerConfig {
// chatID is where to send it, fileID is the ID of the sticker already uploaded.
func NewStickerShare(chatID int, fileID string) StickerConfig {
return StickerConfig{
ChatID: chatID,
UseExistingSticker: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -135,9 +118,7 @@ func NewStickerShare(chatID int, fileID string) StickerConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewVideoUpload(chatID int, file interface{}) VideoConfig {
return VideoConfig{
ChatID: chatID,
UseExistingVideo: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -147,9 +128,7 @@ func NewVideoUpload(chatID int, file interface{}) VideoConfig {
// chatID is where to send it, fileID is the ID of the video already uploaded.
func NewVideoShare(chatID int, fileID string) VideoConfig {
return VideoConfig{
ChatID: chatID,
UseExistingVideo: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -160,9 +139,7 @@ func NewVideoShare(chatID int, fileID string) VideoConfig {
// chatID is where to send it, file is a string path to the file, or FileReader or FileBytes.
func NewVoiceUpload(chatID int, file interface{}) VoiceConfig {
return VoiceConfig{
ChatID: chatID,
UseExistingVoice: false,
File: file,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, File: file, UseExisting: false},
}
}
@ -172,9 +149,7 @@ func NewVoiceUpload(chatID int, file interface{}) VoiceConfig {
// chatID is where to send it, fileID is the ID of the video already uploaded.
func NewVoiceShare(chatID int, fileID string) VoiceConfig {
return VoiceConfig{
ChatID: chatID,
UseExistingVoice: true,
FileID: fileID,
BaseFile: BaseFile{BaseChat: BaseChat{ChatID: chatID}, FileID: fileID, UseExisting: true},
}
}
@ -184,11 +159,9 @@ func NewVoiceShare(chatID int, fileID string) VoiceConfig {
// chatID is where to send it, latitude and longitude are coordinates.
func NewLocation(chatID int, latitude float64, longitude float64) LocationConfig {
return LocationConfig{
ChatID: chatID,
Latitude: latitude,
Longitude: longitude,
ReplyToMessageID: 0,
ReplyMarkup: nil,
BaseChat: BaseChat{ChatID: chatID, ReplyToMessageID: 0, ReplyMarkup: nil},
Latitude: latitude,
Longitude: longitude,
}
}
@ -198,8 +171,8 @@ func NewLocation(chatID int, latitude float64, longitude float64) LocationConfig
// chatID is where to send it, action should be set via CHAT constants.
func NewChatAction(chatID int, action string) ChatActionConfig {
return ChatActionConfig{
ChatID: chatID,
Action: action,
BaseChat: BaseChat{ChatID: chatID},
Action: action,
}
}
@ -233,8 +206,7 @@ func NewWebhook(link string) WebhookConfig {
u, _ := url.Parse(link)
return WebhookConfig{
URL: u,
Clear: false,
URL: u,
}
}
@ -247,7 +219,6 @@ func NewWebhookWithCert(link string, file interface{}) WebhookConfig {
return WebhookConfig{
URL: u,
Clear: false,
Certificate: file,
}
}

1125
methods.go

File diff suppressed because it is too large Load Diff

BIN
tests/audio.mp3 100644

Binary file not shown.

18
tests/cert.pem 100644
View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC0zCCAbugAwIBAgIJAPYfllX657axMA0GCSqGSIb3DQEBCwUAMAAwHhcNMTUx
MTIxMTExMDQxWhcNMjUwODIwMTExMDQxWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAoMMSIIgYx8pT8Kz1O8Ukd/JVyqBQYRSo0enqEzo7295VROXq
TUthbEbdi0OczUfl4IsAWppOSRrDwEguJZ0cJ/r6IxGsbrCdQr2MjgiomYtAXKKQ
GAGL5Wls+AzcRNV0OszVJzkDNFYZzgNejyitGJSNEQMyU8r2gyPyIWP9MQKQst8y
Mg91R/7l9jwf6AWwNxykZlYZurtsQ6XsBPZpF9YOFL7vZYPhKUFiNEm+74RpojC7
Gt6nztYAUI2V/F+1uoXAr8nLpbj9SD0VSwyZLRG1uIVLBzhb0lfOIzAvJ45EKki9
nejyoXfH1U5+iMzdSAdcy3MCBhpEZwJPqhDqeQIDAQABo1AwTjAdBgNVHQ4EFgQU
JE0RLM+ohLnlDz0Qk0McCxtDK2MwHwYDVR0jBBgwFoAUJE0RLM+ohLnlDz0Qk0Mc
CxtDK2MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEmgME00JYuYZ
4wNaGrJskZ05ZnP+TXJusmBui9ToQ4UoykuyY5rsdGQ3SdzXPLdmd2nfMsw63iK2
D7rjcH/rmn6fRccZqN0o0SXd/EuHeIoeW1Xnnivbt71b6mcOAeNg1UsMYxnMAVl0
ywdkta8gURltagSfXoUbqlnSxn/zCwqaxxcQXA/CnunvRsFtQrwWjDBPg/BPULHX
DEh2AactGtnGqEZ5iap/VCOVnmL6iPdJ1x5UIF/gS6U96wL+GHfcs1jCvPg+GEwR
3inh9oTXG9L21ge4lbGiPUIMBjtVcB3bXuQbOfec9Cr3ZhcQeZj680BIRxD/pNpA
O/XeCfjfkw==
-----END CERTIFICATE-----

BIN
tests/image.jpg 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

28
tests/key.pem 100644
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCgwxIgiBjHylPw
rPU7xSR38lXKoFBhFKjR6eoTOjvb3lVE5epNS2FsRt2LQ5zNR+XgiwBamk5JGsPA
SC4lnRwn+vojEaxusJ1CvYyOCKiZi0BcopAYAYvlaWz4DNxE1XQ6zNUnOQM0VhnO
A16PKK0YlI0RAzJTyvaDI/IhY/0xApCy3zIyD3VH/uX2PB/oBbA3HKRmVhm6u2xD
pewE9mkX1g4Uvu9lg+EpQWI0Sb7vhGmiMLsa3qfO1gBQjZX8X7W6hcCvyculuP1I
PRVLDJktEbW4hUsHOFvSV84jMC8njkQqSL2d6PKhd8fVTn6IzN1IB1zLcwIGGkRn
Ak+qEOp5AgMBAAECggEBAJ/dPCJzlEjhL5XPONLmGXzZ1Gx5/VR86eBMv0O9jhb3
wk2QYO3aPxggZGD/rGcKz1L6hzCR77WM0wpb/N/Um1I6pxHGmnU8VjYvLh10CM0f
h7JWyfnFV+ubagxFJamhpkJuvKyTaldaI7EU8qxj47Xky18Wka53z6nbTgXcW8Sm
V4CJy9OHNgKJQnylX6zOAaxVngSGde3xLslLjsYK4w9b2+OkCSUST2XXdo+ZLXxl
cs0lEPFRM1Xh9/E6UrDrJMHHzio53L/W/+a8sIar1upgSY52pyD/tA7VSrAJ9nYC
RezOU81VTLfMO+TYmgZzSUQJYh0cR4yqJe+wgl4U550CgYEA1EcS6Z+PO5Pr3u2+
XevawSAal6y9ONkkdOoASC977W37nn0E1wlQo41dR6DESCJfiSMeN0KbmXj5Wnc/
ADu+73iGwC90G9Qs9sjD7KAFBJvuj0V8hxvpWRdIBBbf7rlOj3CV0iXRYjkJbyJa
cxuR0kiv4gTWmm5Cq+5ir8t1Oc8CgYEAwd+xOaDerNR481R+QmENFw+oR2EVMq3Q
B/vinLK0PemQWrh32iBcd+vhSilOSQtUm1nko1jLK8C4s8X2vZYua4m5tcK9VqCt
maCCq/ffxzsoW/GN8japnduz+qA+hKWJzW/aYR8tsOeqzjVqj4iIqPI4HuokrDi/
UD/QLgq5UTcCgYEAk2ZC0Kx15dXB7AtDq63xOTcUoAtXXRkSgohV58npEKXVGWkQ
Kk0SjG7Fvc35XWlY0z3qZk6/AuOIqfOxcHUMEPatAtgwlH5RNo+T1EQNF/U6wotq
e9q6vp026XgEyJwt29Y+giy2ZrDaRywgiFs1d0H3t0bKyXMUopQmPJFXdesCgYEA
psCxXcDpZjxGX/zPsGZrbOdxtRtisTlg0k0rp93pO8tV90HtDHeDMT54g2ItzJPr
TMev6XOpJNPZyf6+8GhpOuO2EQkT85u2VYoCeslz95gBabvFfIzZrUZYcnw76bm8
YjAP5DN+CEfq2PyG0Df+W1ojPSvlKSCSJQMOG1vr81cCgYEAkjPY5WR99uJxYBNI
OTFMSkETgDUbPXBu/E/h5Dtn79v8Moj9FvC7+q6sg9qXhrGhfK2xDev3/sTrbS/E
Gcf8UNIne3AXsoAS8MtkOwJXHkYaTIboIYgDX4LlDmbGQlIRaWgyh2POI6VtjLBT
ms6AdsdpIB6As9xNUBUwj/RnTZQ=
-----END PRIVATE KEY-----

BIN
tests/video.mp4 100644

Binary file not shown.

BIN
tests/voice.ogg 100644

Binary file not shown.

View File

@ -1,92 +0,0 @@
// Package tgutils provides extra functions to make certain tasks easier.
package tgutils
import (
"github.com/syfaro/telegram-bot-api"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"time"
)
var rand uint32
var randmu sync.Mutex
func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
}
func nextSuffix() string {
randmu.Lock()
r := rand
if r == 0 {
r = reseed()
}
r = r*1664525 + 1013904223 // constants from Numerical Recipes
rand = r
randmu.Unlock()
return strconv.Itoa(int(1e9 + r%1e9))[1:]
}
// this function ripped from ioutils.TempFile, except with a suffix, instead of prefix.
func tempFileWithSuffix(dir, suffix string) (f *os.File, err error) {
if dir == "" {
dir = os.TempDir()
}
nconflict := 0
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, nextSuffix()+suffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
if nconflict++; nconflict > 10 {
randmu.Lock()
rand = reseed()
randmu.Unlock()
}
continue
}
break
}
return
}
// EncodeAudio takes a file and attempts to convert it to a .ogg for Telegram.
// It then updates the path to the audio file in the AudioConfig.
//
// This function requires ffmpeg and opusenc to be installed on the system!
func EncodeAudio(audio *tgbotapi.AudioConfig) error {
f, err := tempFileWithSuffix(os.TempDir(), "_tgutils.ogg")
if err != nil {
return err
}
defer f.Close()
ffmpegArgs := []string{
"-i",
audio.FilePath,
"-f",
"wav",
"-",
}
opusArgs := []string{
"--bitrate",
"256",
"-",
f.Name(),
}
c1 := exec.Command("ffmpeg", ffmpegArgs...)
c2 := exec.Command("opusenc", opusArgs...)
c2.Stdin, _ = c1.StdoutPipe()
c2.Stdout = os.Stdout
c2.Start()
c1.Run()
c2.Wait()
return nil
}

View File

@ -3,6 +3,7 @@ package tgbotapi
import (
"encoding/json"
"fmt"
"strings"
"time"
)
@ -112,6 +113,16 @@ func (m *Message) IsGroup() bool {
return m.From.ID != m.Chat.ID
}
// IsCommand returns true if message starts from /
func (m *Message) IsCommand() bool {
return m.Text != "" && m.Text[0] == '/'
}
// Command returns first word from message
func (m *Message) Command() string {
return strings.Split(m.Text, " ")[0]
}
// PhotoSize contains information about photos, including ID and Width and Height.
type PhotoSize struct {
FileID string `json:"file_id"`

74
types_test.go 100644
View File

@ -0,0 +1,74 @@
package tgbotapi_test
import (
"github.com/Syfaro/telegram-bot-api"
"testing"
"time"
)
func TestUserStringWith(t *testing.T) {
user := tgbotapi.User{0, "Test", "Test", ""}
if user.String() != "Test Test" {
t.Fail()
}
}
func TestUserStringWithUserName(t *testing.T) {
user := tgbotapi.User{0, "Test", "Test", "@test"}
if user.String() != "@test" {
t.Fail()
}
}
func TestMessageIsGroup(t *testing.T) {
from := tgbotapi.User{ID: 0}
chat := tgbotapi.Chat{ID: 10}
message := tgbotapi.Message{From: from, Chat: chat}
if message.IsGroup() != true {
t.Fail()
}
}
func TestMessageTime(t *testing.T) {
message := tgbotapi.Message{Date: 0}
date := time.Unix(0, 0)
if message.Time() != date {
t.Fail()
}
}
func TestChatIsPrivate(t *testing.T) {
chat := tgbotapi.Chat{ID: 10, Type: "private"}
if chat.IsPrivate() != true {
t.Fail()
}
}
func TestChatIsGroup(t *testing.T) {
chat := tgbotapi.Chat{ID: 10, Type: "group"}
if chat.IsGroup() != true {
t.Fail()
}
}
func TestChatIsChannel(t *testing.T) {
chat := tgbotapi.Chat{ID: 10, Type: "channel"}
if chat.IsChannel() != true {
t.Fail()
}
}
func TestFileLink(t *testing.T) {
file := tgbotapi.File{FilePath: "test/test.txt"}
if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" {
t.Fail()
}
}

View File

@ -1,33 +0,0 @@
package tgbotapi
import (
"log"
"time"
)
// UpdatesChan starts a channel for getting updates.
func (bot *BotAPI) UpdatesChan(config UpdateConfig) error {
bot.Updates = 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
bot.Updates <- update
}
}
}
}()
return nil
}

View File

@ -1,21 +0,0 @@
package tgbotapi
import (
"encoding/json"
"io/ioutil"
"net/http"
)
// ListenForWebhook registers a http handler for a webhook.
func (bot *BotAPI) ListenForWebhook(pattern string) {
bot.Updates = 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)
bot.Updates <- update
})
}