aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Kiryukhin <a.kiryukhin@mail.ru>2019-08-09 02:02:05 +0300
committerAlexander Kiryukhin <a.kiryukhin@mail.ru>2019-08-09 02:02:05 +0300
commit94c96cef29cd83bc60b2eabb8bb89c9fd8c7059b (patch)
tree676b3e2b6ffb171e102931e5fac01f7a4621c20a
parentd13acd7da9bd1c08de33b91120a479df88d21db2 (diff)
0.1.8 Release
-rw-r--r--.gitignore2
-rw-r--r--README.md69
-rw-r--r--api.go582
-rw-r--r--bots.go48
-rw-r--r--chats.go168
-rw-r--r--client.go56
-rw-r--r--examples/example.go53
-rw-r--r--examples/example_longpolling.go (renamed from examples/example_2.go)24
-rw-r--r--examples/example_webhook.go (renamed from examples/example_1.go)25
-rw-r--r--keyboard.go (renamed from utils.go)49
-rw-r--r--messages.go126
-rw-r--r--models.go803
-rw-r--r--subscriptions.go68
-rw-r--r--uploads.go84
14 files changed, 1176 insertions, 981 deletions
diff --git a/.gitignore b/.gitignore
index 11b90db..13bc26f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,5 @@ _testmain.go
*.prof
.idea
+
+tmp \ No newline at end of file
diff --git a/README.md b/README.md
index c2ec0cc..1345f46 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,75 @@
# TamTam Go
-Простая реализация клиента к TamTam Bot API на Go.
+[![Sourcegraph](https://sourcegraph.com/github.com/neonxp/tamtam/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/neonxp/tamtam?badge)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/neonxp/tamtam)
+[![Go Report Card](https://goreportcard.com/badge/github.com/neonxp/tamtam?style=flat-square)](https://goreportcard.com/report/github.com/neonxp/tamtam)
+[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/neonxp/tamtam/master/LICENSE)
-На данном этапе представляет собой 1 в 1 реализованные методы API + HTTPHandler для веб-хуков + сгенерированные модели.
+Простая реализация клиента к TamTam Bot API на Go. Поддерживается получение обновление как с помощью вебхуков, так и лонгполлингом.
+
+Поддерживаемая версия API - 0.1.8
## Документация
+В общем случае, методы повторяют такие из [официальной документации](https://dev.tamtam.chat/)
+Так же добавлены хелпер для создания клавиатуры (`api.Messages.NewKeyboardBuilder()`) и для загрузки вложений (`api.Uploads.UploadMedia(uploadType UploadType, filename string)`). Пример создания клавиатуры см. ниже в примере.
+
+Остальное описано тут http://godoc.org/github.com/neonxp/tamtam/ и в примерах из директории [examples](https://github.com/neonxp/tamtam/tree/master/examples)
+
+## Пример
-Её пока нет :)
+```go
+package main
-Но http://godoc.org/github.com/neonxp/tamtam/ может вам помочь.
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
-## Статус
+ "github.com/neonxp/tamtam"
+)
-API пока крайне неставбильное и обязательно будет меняться в будущем. Учитывайте это!
+func main() {
+ api := tamtam.New(os.Getenv("TOKEN"))
-## Пример
+ info, err := api.Bots.GetBot() // Простой метод
+ log.Printf("Get me: %#v %#v", info, err)
+ go api.UpdatesLoop(context.Background()) // Запуск цикла получения обновлений
+ for upd := range api.GetUpdates() { // Чтение из канала с обновлениями
+ log.Printf("Received: %#v", upd)
+ switch upd := upd.(type) { // Определение типа пришедшего обновления
+ case *tamtam.MessageCreatedUpdate:
+ // Создание клавиатуры
+ keyboard := api.Messages.NewKeyboardBuilder()
+ keyboard.
+ AddRow().
+ AddGeolocation("Прислать геолокацию", true).
+ AddContact("Прислать контакт")
+ keyboard.
+ AddRow().
+ AddLink("Библиотека", tamtam.POSITIVE, "https://github.com/neonxp/tamtam").
+ AddCallback("Колбек 1", tamtam.NEGATIVE, "callback_1").
+ AddCallback("Колбек 2", tamtam.NEGATIVE, "callback_2")
-Простые примеры использования находятся в директории `examples`.
+ // Отправка сообщения с клавиатурой
+ res, err := api.Messages.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
+ Text: fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text),
+ Attachments: []interface{}{
+ tamtam.NewInlineKeyboardAttachmentRequest(keyboard.Build()),
+ },
+ })
+ log.Printf("Answer: %#v %#v", res, err)
+ case *tamtam.MessageCallbackUpdate:
+ res, err := api.Messages.SendMessage(0, upd.Callback.User.UserId, &tamtam.NewMessageBody{
+ Text: "Callback: " + upd.Callback.Payload,
+ })
+ log.Printf("Answer: %#v %#v", res, err)
+ default:
+ log.Printf("Unknown type: %#v", upd)
+ }
+ }
+}
+```
## Автор
diff --git a/api.go b/api.go
index fdc8a24..57ee096 100644
--- a/api.go
+++ b/api.go
@@ -1,499 +1,146 @@
-/*
- * TamTam Bot API
- */
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
package tamtam
import (
- "bytes"
"context"
"encoding/json"
- "fmt"
- "io"
"io/ioutil"
"log"
- "mime/multipart"
"net/http"
"net/url"
- "os"
"strconv"
"time"
)
type Api struct {
- key string
- version string
- url *url.URL
- timeout int
- pause int
- logging bool
+ Bots *bots
+ Chats *chats
+ Messages *messages
+ Subscriptions *subscriptions
+ Uploads *uploads
+ client *client
+ updates chan UpdateInterface
+ timeout int
+ pause int
}
// New TamTam Api object
func New(key string) *Api {
u, _ := url.Parse("https://botapi.tamtam.chat/")
+ cl := newClient(key, "0.1.8", u)
return &Api{
- key: key,
- url: u,
- version: "0.1.8",
- timeout: 30,
- pause: 1,
- logging: false,
+ Bots: newBots(cl),
+ Chats: newChats(cl),
+ Uploads: newUploads(cl),
+ Messages: newMessages(cl),
+ Subscriptions: newSubscriptions(cl),
+ client: cl,
+ updates: make(chan UpdateInterface),
+ timeout: 30,
+ pause: 1,
}
}
-func (a *Api) EnableLogging() {
- a.logging = true
-}
-
-// region Misc methods
-
-func (a *Api) GetMe() (*UserWithPhoto, error) {
- result := new(UserWithPhoto)
- values := url.Values{}
- body, err := a.request(http.MethodGet, "me", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) GetUploadURL(uploadType UploadType) (*UploadEndpoint, error) {
- result := new(UploadEndpoint)
- values := url.Values{}
- values.Set("type", string(uploadType))
- body, err := a.request(http.MethodPost, "uploads", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) UploadMedia(uploadType UploadType, filename string) (*UploadedInfo, error) {
- bodyBuf := &bytes.Buffer{}
- bodyWriter := multipart.NewWriter(bodyBuf)
-
- // this step is very important
- fileWriter, err := bodyWriter.CreateFormFile("data", filename)
- if err != nil {
- return nil, err
- }
-
- // open file handle
- fh, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer fh.Close()
-
- //iocopy
- _, err = io.Copy(fileWriter, fh)
- if err != nil {
- return nil, err
- }
-
- if err := bodyWriter.Close(); err != nil {
- return nil, err
- }
-
- target, err := a.GetUploadURL(uploadType)
- if err != nil {
- return nil, err
- }
- contentType := bodyWriter.FormDataContentType()
-
- resp, err := http.Post(target.Url, contentType, bodyBuf)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- result := new(UploadedInfo)
- return result, json.NewDecoder(resp.Body).Decode(result)
-}
-
-func (a *Api) GetUpdatesLoop(ctx context.Context, updates chan interface{}) error {
- for {
- select {
- case <-ctx.Done():
- return nil
- case <-time.After(time.Duration(a.pause) * time.Second):
- var marker int64
- for {
- upds, err := a.getUpdates(50, a.timeout, marker, []string{})
- if err != nil {
- return err
- }
- if len(upds.Updates) == 0 {
- break
- }
- for _, u := range upds.Updates {
- updates <- a.bytesToProperUpdate(u)
- }
- marker = upds.Marker
- }
- }
- }
-}
-
-func (a *Api) GetHandler(updates chan interface{}) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- defer r.Body.Close()
- b, _ := ioutil.ReadAll(r.Body)
- updates <- a.bytesToProperUpdate(b)
- }
-}
-
-// endregion
-
-// region Chat methods
-func (a *Api) GetChats(count, marker int64) (*ChatList, error) {
- result := new(ChatList)
- values := url.Values{}
- if count > 0 {
- values.Set("count", strconv.Itoa(int(count)))
- }
- if marker > 0 {
- values.Set("marker", strconv.Itoa(int(marker)))
- }
- body, err := a.request(http.MethodGet, "chats", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) GetChat(chatID int64) (*Chat, error) {
- result := new(Chat)
- values := url.Values{}
- body, err := a.request(http.MethodGet, fmt.Sprintf("chats/%d", chatID), values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) GetChatMembership(chatID int64) (*ChatMember, error) {
- result := new(ChatMember)
- values := url.Values{}
- body, err := a.request(http.MethodGet, fmt.Sprintf("chats/%d/members/me", chatID), values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) GetChatMembers(chatID, count, marker int64) (*ChatMembersList, error) {
- result := new(ChatMembersList)
- values := url.Values{}
- if count > 0 {
- values.Set("count", strconv.Itoa(int(count)))
- }
- if marker > 0 {
- values.Set("marker", strconv.Itoa(int(marker)))
- }
- body, err := a.request(http.MethodGet, fmt.Sprintf("chats/%d/members", chatID), values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) LeaveChat(chatID int64) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- body, err := a.request(http.MethodDelete, fmt.Sprintf("chats/%d/members/me", chatID), values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) EditChat(chatID int64, update *ChatPatch) (*Chat, error) {
- result := new(Chat)
- values := url.Values{}
- body, err := a.request(http.MethodPatch, fmt.Sprintf("chats/%d", chatID), values, update)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) AddMember(chatID int64, users UserIdsList) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- body, err := a.request(http.MethodPost, fmt.Sprintf("chats/%d/members", chatID), values, users)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) RemoveMember(chatID int64, userID int64) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- values.Set("user_id", strconv.Itoa(int(userID)))
- body, err := a.request(http.MethodDelete, fmt.Sprintf("chats/%d/members", chatID), values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) SendAction(chatID int64, action SenderAction) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- body, err := a.request(http.MethodPost, fmt.Sprintf("chats/%d/actions", chatID), values, ActionRequestBody{Action: action})
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-// endregion
-
-// region Message methods
-
-func (a *Api) GetMessages(chatID int64, messageIDs []string, from int64, to int64, count int64) (*MessageList, error) {
- result := new(MessageList)
- values := url.Values{}
- if chatID != 0 {
- values.Set("chat_id", strconv.Itoa(int(chatID)))
- }
- if len(messageIDs) > 0 {
- for _, mid := range messageIDs {
- values.Add("message_ids", mid)
- }
- }
- if from != 0 {
- values.Set("from", strconv.Itoa(int(from)))
- }
- if to != 0 {
- values.Set("to", strconv.Itoa(int(to)))
- }
- if count > 0 {
- values.Set("count", strconv.Itoa(int(count)))
- }
- body, err := a.request(http.MethodGet, "messages", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) SendMessage(chatID int64, userID int64, message *NewMessageBody) (*Message, error) {
- result := new(Message)
- values := url.Values{}
- if chatID != 0 {
- values.Set("chat_id", strconv.Itoa(int(chatID)))
- }
- if userID != 0 {
- values.Set("user_id", strconv.Itoa(int(userID)))
- }
- body, err := a.request(http.MethodPost, "messages", values, message)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) EditMessage(messageID int64, message *NewMessageBody) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- values.Set("message_id", strconv.Itoa(int(messageID)))
- body, err := a.request(http.MethodPut, "messages", values, message)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) DeleteMessage(messageID int64) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- values.Set("message_id", strconv.Itoa(int(messageID)))
- body, err := a.request(http.MethodDelete, "messages", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) AnswerOnCallback(callbackID int64, callback *CallbackAnswer) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- values.Set("callback_id", strconv.Itoa(int(callbackID)))
- body, err := a.request(http.MethodPost, "answers", values, callback)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-// endregion
-
-// region Subscriptions
-
-func (a *Api) GetSubscriptions() (*GetSubscriptionsResult, error) {
- result := new(GetSubscriptionsResult)
- values := url.Values{}
- body, err := a.request(http.MethodGet, "subscriptions", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) Subscribe(subscribeURL string, updateTypes []string) (*SimpleQueryResult, error) {
- subscription := &SubscriptionRequestBody{
- Url: subscribeURL,
- UpdateTypes: updateTypes,
- Version: a.version,
- }
- result := new(SimpleQueryResult)
- values := url.Values{}
- body, err := a.request(http.MethodPost, "subscriptions", values, subscription)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-func (a *Api) Unsubscribe(subscriptionURL string) (*SimpleQueryResult, error) {
- result := new(SimpleQueryResult)
- values := url.Values{}
- values.Set("url", subscriptionURL)
- body, err := a.request(http.MethodDelete, "subscriptions", values, nil)
- if err != nil {
- return result, err
- }
- defer body.Close()
- return result, json.NewDecoder(body).Decode(result)
-}
-
-// endregion
-
-// region Internal
-
-func (a *Api) bytesToProperUpdate(b []byte) interface{} {
+func (a *Api) bytesToProperUpdate(b []byte) UpdateInterface {
u := new(Update)
_ = json.Unmarshal(b, u)
- switch u.UpdateType {
- case UpdateTypeMessageCallback:
- upd := UpdateMessageCallback{}
- _ = json.Unmarshal(b, &upd)
+ switch u.GetUpdateType() {
+ case TypeMessageCallback:
+ upd := new(MessageCallbackUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeMessageCreated:
- upd := UpdateMessageCreated{}
- _ = json.Unmarshal(b, &upd)
- for _, att := range upd.Message.Body.RawAttachments {
+ case TypeMessageCreated:
+ upd := new(MessageCreatedUpdate)
+ _ = json.Unmarshal(b, upd)
+ for _, att := range upd.Message.Body.rawAttachments {
upd.Message.Body.Attachments = append(upd.Message.Body.Attachments, a.bytesToProperAttachment(att))
}
return upd
- case UpdateTypeMessageRemoved:
- upd := UpdateMessageRemoved{}
- _ = json.Unmarshal(b, &upd)
+ case TypeMessageRemoved:
+ upd := new(MessageRemovedUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeMessageEdited:
- upd := UpdateMessageEdited{}
- _ = json.Unmarshal(b, &upd)
- for _, att := range upd.Message.Body.RawAttachments {
+ case TypeMessageEdited:
+ upd := new(MessageEditedUpdate)
+ _ = json.Unmarshal(b, upd)
+ for _, att := range upd.Message.Body.rawAttachments {
upd.Message.Body.Attachments = append(upd.Message.Body.Attachments, a.bytesToProperAttachment(att))
}
return upd
- case UpdateTypeMessageRestored:
- upd := UpdateMessageRestored{}
- _ = json.Unmarshal(b, &upd)
+ case TypeBotAdded:
+ upd := new(BotAddedToChatUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeBotAdded:
- upd := UpdateBotAdded{}
- _ = json.Unmarshal(b, &upd)
+ case TypeBotRemoved:
+ upd := new(BotRemovedFromChatUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeBotRemoved:
- upd := UpdateBotRemoved{}
- _ = json.Unmarshal(b, &upd)
+ case TypeUserAdded:
+ upd := new(UserAddedToChatUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeUserAdded:
- upd := UpdateUserAdded{}
- _ = json.Unmarshal(b, &upd)
+ case TypeUserRemoved:
+ upd := new(UserRemovedFromChatUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeUserRemoved:
- upd := UpdateUserRemoved{}
- _ = json.Unmarshal(b, &upd)
+ case TypeBotStarted:
+ upd := new(BotStartedUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
- case UpdateTypeBotStarted:
- upd := UpdateBotStarted{}
- _ = json.Unmarshal(b, &upd)
- return upd
- case UpdateTypeChatTitleChanged:
- upd := UpdateChatTitleChanged{}
- _ = json.Unmarshal(b, &upd)
+ case TypeChatTitleChanged:
+ upd := new(ChatTitleChangedUpdate)
+ _ = json.Unmarshal(b, upd)
return upd
}
return nil
}
-func (a *Api) bytesToProperAttachment(b []byte) interface{} {
+func (a *Api) bytesToProperAttachment(b []byte) AttachmentInterface {
attachment := new(Attachment)
_ = json.Unmarshal(b, attachment)
- switch attachment.Type {
+ switch attachment.GetAttachmentType() {
case AttachmentAudio:
- res := &AudioAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(AudioAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentContact:
- res := &ContactAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(ContactAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentFile:
- res := &FileAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(FileAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentImage:
- res := &PhotoAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(PhotoAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentKeyboard:
- res := &InlineKeyboardAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(InlineKeyboardAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentLocation:
- res := &LocationAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(LocationAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentShare:
- res := &ShareAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(ShareAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentSticker:
- res := &StickerAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(StickerAttachment)
+ _ = json.Unmarshal(b, res)
return res
case AttachmentVideo:
- res := &VideoAttachment{}
- _ = json.Unmarshal(b, &res)
+ res := new(VideoAttachment)
+ _ = json.Unmarshal(b, res)
return res
}
return attachment
}
-func (a *Api) getUpdates(limit int, timeout int, marker int64, types []string) (*UpdateList, error) {
+func (a *Api) getUpdates(limit int, timeout int, marker int, types []string) (*UpdateList, error) {
result := new(UpdateList)
values := url.Values{}
if limit > 0 {
@@ -503,57 +150,62 @@ func (a *Api) getUpdates(limit int, timeout int, marker int64, types []string) (
values.Set("timeout", strconv.Itoa(timeout))
}
if marker > 0 {
- values.Set("marker", strconv.Itoa(int(marker)))
+ values.Set("marker", strconv.Itoa(marker))
}
if len(types) > 0 {
for _, t := range types {
values.Add("types", t)
}
}
- body, err := a.request(http.MethodGet, "updates", values, nil)
+ body, err := a.client.request(http.MethodGet, "updates", values, nil)
if err != nil {
return result, err
}
- defer body.Close()
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
jb, _ := ioutil.ReadAll(body)
- if a.logging {
- log.Printf("Received: %s", string(jb))
- }
return result, json.Unmarshal(jb, result)
}
-func (a *Api) request(method, path string, query url.Values, body interface{}) (io.ReadCloser, error) {
- j, err := json.Marshal(body)
- if err != nil {
- return nil, err
+func (a *Api) UpdatesLoop(ctx context.Context) error {
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-time.After(time.Duration(a.pause) * time.Second):
+ var marker int
+ for {
+ upds, err := a.getUpdates(50, a.timeout, marker, []string{})
+ if err != nil {
+ return err
+ }
+ if len(upds.Updates) == 0 {
+ break
+ }
+ for _, u := range upds.Updates {
+ a.updates <- a.bytesToProperUpdate(u)
+ }
+ marker = *upds.Marker
+ }
+ }
}
- return a.requestReader(method, path, query, bytes.NewReader(j))
}
-func (a *Api) requestReader(method, path string, query url.Values, body io.Reader) (io.ReadCloser, error) {
- c := http.DefaultClient
- u := *a.url
- u.Path = path
- query.Set("access_token", a.key)
- query.Set("v", a.version)
- u.RawQuery = query.Encode()
- if a.logging {
- log.Printf("Sent: [%s %s] Query: %#v", method, path, query)
- }
- req, err := http.NewRequest(method, u.String(), body)
- if err != nil {
- return nil, err
- }
- resp, err := c.Do(req)
- if resp.StatusCode != http.StatusOK {
- errObj := new(Error)
- err = json.NewDecoder(resp.Body).Decode(errObj)
- if err != nil {
- return nil, err
- }
- return nil, fmt.Errorf("code=%s message=%s error=%s", errObj.Code, errObj.Message, errObj.Error)
- }
- return resp.Body, err
+func (a *Api) GetUpdates() chan UpdateInterface {
+ return a.updates
}
-// endregion
+func (a *Api) GetHandler(updates chan interface{}) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ defer func() {
+ if err := r.Body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ b, _ := ioutil.ReadAll(r.Body)
+ updates <- a.bytesToProperUpdate(b)
+ }
+}
diff --git a/bots.go b/bots.go
new file mode 100644
index 0000000..78b3d88
--- /dev/null
+++ b/bots.go
@@ -0,0 +1,48 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "net/url"
+)
+
+type bots struct {
+ client *client
+}
+
+func newBots(client *client) *bots {
+ return &bots{client: client}
+}
+
+func (a *bots) GetBot() (*BotInfo, error) {
+ result := new(BotInfo)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodGet, "me", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *bots) PatchBot(patch *BotPatch) (*BotInfo, error) {
+ result := new(BotInfo)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodPatch, "me", values, patch)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
diff --git a/chats.go b/chats.go
new file mode 100644
index 0000000..ac070e0
--- /dev/null
+++ b/chats.go
@@ -0,0 +1,168 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+type chats struct {
+ client *client
+}
+
+func newChats(client *client) *chats {
+ return &chats{client: client}
+}
+
+func (a *chats) GetChats(count, marker int) (*ChatList, error) {
+ result := new(ChatList)
+ values := url.Values{}
+ if count > 0 {
+ values.Set("count", strconv.Itoa(int(count)))
+ }
+ if marker > 0 {
+ values.Set("marker", strconv.Itoa(int(marker)))
+ }
+ body, err := a.client.request(http.MethodGet, "chats", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) GetChat(chatID int) (*Chat, error) {
+ result := new(Chat)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodGet, fmt.Sprintf("chats/%d", chatID), values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) GetChatMembership(chatID int) (*ChatMember, error) {
+ result := new(ChatMember)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodGet, fmt.Sprintf("chats/%d/members/me", chatID), values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) GetChatMembers(chatID, count, marker int) (*ChatMembersList, error) {
+ result := new(ChatMembersList)
+ values := url.Values{}
+ if count > 0 {
+ values.Set("count", strconv.Itoa(int(count)))
+ }
+ if marker > 0 {
+ values.Set("marker", strconv.Itoa(int(marker)))
+ }
+ body, err := a.client.request(http.MethodGet, fmt.Sprintf("chats/%d/members", chatID), values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) LeaveChat(chatID int) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodDelete, fmt.Sprintf("chats/%d/members/me", chatID), values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) EditChat(chatID int, update *ChatPatch) (*Chat, error) {
+ result := new(Chat)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodPatch, fmt.Sprintf("chats/%d", chatID), values, update)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) AddMember(chatID int, users UserIdsList) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodPost, fmt.Sprintf("chats/%d/members", chatID), values, users)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) RemoveMember(chatID int, userID int) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ values.Set("user_id", strconv.Itoa(int(userID)))
+ body, err := a.client.request(http.MethodDelete, fmt.Sprintf("chats/%d/members", chatID), values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *chats) SendAction(chatID int, action SenderAction) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodPost, fmt.Sprintf("chats/%d/actions", chatID), values, ActionRequestBody{Action: action})
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..39efbe8
--- /dev/null
+++ b/client.go
@@ -0,0 +1,56 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+)
+
+type client struct {
+ key string
+ version string
+ url *url.URL
+}
+
+func newClient(key string, version string, url *url.URL) *client {
+ return &client{key: key, version: version, url: url}
+}
+
+func (cl *client) request(method, path string, query url.Values, body interface{}) (io.ReadCloser, error) {
+ j, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ return cl.requestReader(method, path, query, bytes.NewReader(j))
+}
+
+func (cl *client) requestReader(method, path string, query url.Values, body io.Reader) (io.ReadCloser, error) {
+ c := http.DefaultClient
+ u := *cl.url
+ u.Path = path
+ query.Set("access_token", cl.key)
+ query.Set("v", cl.version)
+ u.RawQuery = query.Encode()
+ req, err := http.NewRequest(method, u.String(), body)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ errObj := new(Error)
+ err = json.NewDecoder(resp.Body).Decode(errObj)
+ if err != nil {
+ return nil, err
+ }
+ return nil, fmt.Errorf("code=%s message=%s error=%s", errObj.Code, errObj.Message, errObj.Error)
+ }
+ return resp.Body, err
+}
diff --git a/examples/example.go b/examples/example.go
new file mode 100644
index 0000000..b4e84ac
--- /dev/null
+++ b/examples/example.go
@@ -0,0 +1,53 @@
+// +build ignore
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/neonxp/tamtam"
+)
+
+func main() {
+ api := tamtam.New(os.Getenv("TOKEN"))
+
+ info, err := api.Bots.GetBot() // Простой метод
+ log.Printf("Get me: %#v %#v", info, err)
+ go api.UpdatesLoop(context.Background()) // Запуск цикла получения обновлений
+ for upd := range api.GetUpdates() { // Чтение из канала с обновлениями
+ log.Printf("Received: %#v", upd)
+ switch upd := upd.(type) { // Определение типа пришедшего обновления
+ case *tamtam.MessageCreatedUpdate:
+ // Создание клавиатуры
+ keyboard := api.Messages.NewKeyboardBuilder()
+ keyboard.
+ AddRow().
+ AddGeolocation("Прислать геолокацию", true).
+ AddContact("Прислать контакт")
+ keyboard.
+ AddRow().
+ AddLink("Библиотека", tamtam.POSITIVE, "https://github.com/neonxp/tamtam").
+ AddCallback("Колбек 1", tamtam.NEGATIVE, "callback_1").
+ AddCallback("Колбек 2", tamtam.NEGATIVE, "callback_2")
+
+ // Отправка сообщения с клавиатурой
+ res, err := api.Messages.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
+ Text: fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text),
+ Attachments: []interface{}{
+ tamtam.NewInlineKeyboardAttachmentRequest(keyboard.Build()),
+ },
+ })
+ log.Printf("Answer: %#v %#v", res, err)
+ case *tamtam.MessageCallbackUpdate:
+ res, err := api.Messages.SendMessage(0, upd.Callback.User.UserId, &tamtam.NewMessageBody{
+ Text: "Callback: " + upd.Callback.Payload,
+ })
+ log.Printf("Answer: %#v %#v", res, err)
+ default:
+ log.Printf("Unknown type: %#v", upd)
+ }
+ }
+}
diff --git a/examples/example_2.go b/examples/example_longpolling.go
index 74547fa..67a0d7c 100644
--- a/examples/example_2.go
+++ b/examples/example_longpolling.go
@@ -8,10 +8,11 @@ package main
import (
"context"
"fmt"
- "github.com/neonxp/tamtam"
"log"
"os"
"os/signal"
+
+ "github.com/neonxp/tamtam"
)
func main() {
@@ -19,28 +20,18 @@ func main() {
api := tamtam.New(os.Getenv("TOKEN"))
// Some methods demo:
- info, err := api.GetMe()
+ info, err := api.Bots.GetBot()
log.Printf("Get me: %#v %#v", info, err)
- chats, err := api.GetChats(0, 0)
- log.Printf("Get chats: %#v %#v", chats, err)
- chat, err := api.GetChat(chats.Chats[0].ChatId)
- log.Printf("Get chat: %#v %#v", chat, err)
- subs, _ := api.GetSubscriptions()
- for _, s := range subs.Subscriptions {
- _, _ = api.Unsubscribe(s.Url)
- }
- ch := make(chan interface{}, 1) // Channel with updates from TamTam
ctx, cancel := context.WithCancel(context.Background())
-
go func() {
for {
select {
- case upd := <-ch:
+ case upd := <-api.GetUpdates():
log.Printf("Received: %#v", upd)
switch upd := upd.(type) {
- case tamtam.UpdateMessageCreated:
- res, err := api.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
+ case *tamtam.MessageCreatedUpdate:
+ res, err := api.Messages.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
Text: fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text),
})
log.Printf("Answer: %#v %#v", res, err)
@@ -53,7 +44,6 @@ func main() {
}
}()
-
go func() {
exit := make(chan os.Signal)
signal.Notify(exit, os.Kill, os.Interrupt)
@@ -65,7 +55,7 @@ func main() {
}
}()
- if err := api.GetUpdatesLoop(ctx, ch); err != nil {
+ if err := api.UpdatesLoop(ctx); err != nil {
log.Fatalln(err)
}
diff --git a/examples/example_1.go b/examples/example_webhook.go
index 5c03063..ad1bdae 100644
--- a/examples/example_1.go
+++ b/examples/example_webhook.go
@@ -1,3 +1,5 @@
+// +build ignore
+
/**
* Webhook example
*/
@@ -5,30 +7,27 @@ package main
import (
"fmt"
- "github.com/neonxp/tamtam"
"log"
"net/http"
"os"
+
+ "github.com/neonxp/tamtam"
)
func main() {
// Initialisation
api := tamtam.New(os.Getenv("TOKEN"))
+ host := os.Getenv("HOST")
// Some methods demo:
- info, err := api.GetMe()
+ info, err := api.Bots.GetBot()
log.Printf("Get me: %#v %#v", info, err)
- chats, err := api.GetChats(0, 0)
- log.Printf("Get chats: %#v %#v", chats, err)
- chat, err := api.GetChat(chats.Chats[0].ChatId)
- log.Printf("Get chat: %#v %#v", chat, err)
- msgs, err := api.GetMessages(chats.Chats[0].ChatId, nil, 0, 0, 0)
- log.Printf("Get messages: %#v %#v", msgs, err)
- subs, _ := api.GetSubscriptions()
+
+ subs, _ := api.Subscriptions.GetSubscriptions()
for _, s := range subs.Subscriptions {
- _, _ = api.Unsubscribe(s.Url)
+ _, _ = api.Subscriptions.Unsubscribe(s.Url)
}
- subscriptionResp, err := api.Subscribe("https://576df2ec.ngrok.io/webhook", []string{})
+ subscriptionResp, err := api.Subscriptions.Subscribe(host+"/webhook", []string{})
log.Printf("Subscription: %#v %#v", subscriptionResp, err)
ch := make(chan interface{}) // Channel with updates from TamTam
@@ -39,8 +38,8 @@ func main() {
upd := <-ch
log.Printf("Received: %#v", upd)
switch upd := upd.(type) {
- case tamtam.UpdateMessageCreated:
- res, err := api.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
+ case tamtam.MessageCreatedUpdate:
+ res, err := api.Messages.SendMessage(0, upd.Message.Sender.UserId, &tamtam.NewMessageBody{
Text: fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text),
})
log.Printf("Answer: %#v %#v", res, err)
diff --git a/utils.go b/keyboard.go
index 0ae24c4..7d79042 100644
--- a/utils.go
+++ b/keyboard.go
@@ -1,15 +1,11 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
package tamtam
type KeyboardBuilder struct {
rows []*KeyboardRow
}
-func NewKeyboardBuilder() *KeyboardBuilder {
- return &KeyboardBuilder{
- rows: make([]*KeyboardRow, 0),
- }
-}
-
func (k *KeyboardBuilder) AddRow() *KeyboardRow {
kr := &KeyboardRow{}
k.rows = append(k.rows, kr)
@@ -17,7 +13,7 @@ func (k *KeyboardBuilder) AddRow() *KeyboardRow {
}
func (k *KeyboardBuilder) Build() Keyboard {
- buttons := make([][]interface{}, 0, len(k.rows))
+ buttons := make([][]ButtonInterface, 0, len(k.rows))
for _, r := range k.rows {
buttons = append(buttons, r.Build())
}
@@ -25,19 +21,20 @@ func (k *KeyboardBuilder) Build() Keyboard {
}
type KeyboardRow struct {
- cols []interface{}
+ cols []ButtonInterface
}
-func (k *KeyboardRow) Build() []interface{} {
+func (k *KeyboardRow) Build() []ButtonInterface {
return k.cols
}
func (k *KeyboardRow) AddLink(text string, intent Intent, url string) *KeyboardRow {
b := LinkButton{
- Text: text,
- Url: url,
- Intent: intent,
- Type: LINK,
+ Url: url,
+ Button: Button{
+ Text: text,
+ Type: LINK,
+ },
}
k.cols = append(k.cols, b)
return k
@@ -45,31 +42,35 @@ func (k *KeyboardRow) AddLink(text string, intent Intent, url string) *KeyboardR
func (k *KeyboardRow) AddCallback(text string, intent Intent, payload string) *KeyboardRow {
b := CallbackButton{
- Text: text,
Payload: payload,
Intent: intent,
- Type: CALLBACK,
+ Button: Button{
+ Text: text,
+ Type: CALLBACK,
+ },
}
k.cols = append(k.cols, b)
return k
}
-func (k *KeyboardRow) AddContact(text string, intent Intent, url string) *KeyboardRow {
+func (k *KeyboardRow) AddContact(text string) *KeyboardRow {
b := RequestContactButton{
- Text: text,
- Intent: intent,
- Type: CONTACT,
+ Button: Button{
+ Text: text,
+ Type: CONTACT,
+ },
}
k.cols = append(k.cols, b)
return k
}
-func (k *KeyboardRow) AddGeolocation(text string, intent Intent, quick bool) *KeyboardRow {
+func (k *KeyboardRow) AddGeolocation(text string, quick bool) *KeyboardRow {
b := RequestGeoLocationButton{
- Text: text,
- Quick: quick,
- Intent: intent,
- Type: GEOLOCATION,
+ Quick: quick,
+ Button: Button{
+ Text: text,
+ Type: GEOLOCATION,
+ },
}
k.cols = append(k.cols, b)
return k
diff --git a/messages.go b/messages.go
new file mode 100644
index 0000000..c7cad45
--- /dev/null
+++ b/messages.go
@@ -0,0 +1,126 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+type messages struct {
+ client *client
+}
+
+func newMessages(client *client) *messages {
+ return &messages{client: client}
+}
+
+func (a *messages) GetMessages(chatID int, messageIDs []string, from int, to int, count int) (*MessageList, error) {
+ result := new(MessageList)
+ values := url.Values{}
+ if chatID != 0 {
+ values.Set("chat_id", strconv.Itoa(int(chatID)))
+ }
+ if len(messageIDs) > 0 {
+ for _, mid := range messageIDs {
+ values.Add("message_ids", mid)
+ }
+ }
+ if from != 0 {
+ values.Set("from", strconv.Itoa(int(from)))
+ }
+ if to != 0 {
+ values.Set("to", strconv.Itoa(int(to)))
+ }
+ if count > 0 {
+ values.Set("count", strconv.Itoa(int(count)))
+ }
+ body, err := a.client.request(http.MethodGet, "messages", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *messages) SendMessage(chatID int, userID int, message *NewMessageBody) (*Message, error) {
+ result := new(Message)
+ values := url.Values{}
+ if chatID != 0 {
+ values.Set("chat_id", strconv.Itoa(int(chatID)))
+ }
+ if userID != 0 {
+ values.Set("user_id", strconv.Itoa(int(userID)))
+ }
+ body, err := a.client.request(http.MethodPost, "messages", values, message)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *messages) EditMessage(messageID int, message *NewMessageBody) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ values.Set("message_id", strconv.Itoa(int(messageID)))
+ body, err := a.client.request(http.MethodPut, "messages", values, message)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *messages) DeleteMessage(messageID int) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ values.Set("message_id", strconv.Itoa(int(messageID)))
+ body, err := a.client.request(http.MethodDelete, "messages", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *messages) AnswerOnCallback(callbackID int, callback *CallbackAnswer) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ values.Set("callback_id", strconv.Itoa(int(callbackID)))
+ body, err := a.client.request(http.MethodPost, "answers", values, callback)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *messages) NewKeyboardBuilder() *KeyboardBuilder {
+ return &KeyboardBuilder{
+ rows: make([]*KeyboardRow, 0),
+ }
+}
diff --git a/models.go b/models.go
index 55a04eb..10def22 100644
--- a/models.go
+++ b/models.go
@@ -1,9 +1,11 @@
-/*
- * TamTam Bot API
- */
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
package tamtam
-import "encoding/json"
+import (
+ "encoding/json"
+ "time"
+)
type ActionRequestBody struct {
Action SenderAction `json:"action"`
@@ -25,128 +27,115 @@ const (
// Generic schema representing message attachment
type Attachment struct {
- Type AttachmentType `json:"type,omitempty"`
+ Type AttachmentType `json:"type"`
+}
+
+func (a Attachment) GetAttachmentType() AttachmentType {
+ return a.Type
+}
+
+type AttachmentInterface interface {
+ GetAttachmentType() AttachmentType
}
type AttachmentPayload struct {
// Media attachment URL
- Token string `json:"token"`
+ Url string `json:"url"`
}
// Request to attach some data to message
type AttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ Type AttachmentType `json:"type"`
}
type AudioAttachment struct {
- Type string `json:"type,omitempty"`
- Payload AttachmentPayload `json:"payload"`
+ Attachment
+ Payload MediaAttachmentPayload `json:"payload"`
}
// Request to attach audio to message. MUST be the only attachment in message
type AudioAttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ AttachmentRequest
Payload UploadedInfo `json:"payload"`
}
-// You will receive this update when bot has been added to chat
-type BotAddedToChatUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Chat id where bot was added
- ChatId int64 `json:"chat_id"`
- // User id who added bot to chat
- UserId int64 `json:"user_id"`
+func NewAudioAttachmentRequest(payload UploadedInfo) *AudioAttachmentRequest {
+ return &AudioAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentAudio}}
}
-// You will receive this update when bot has been removed from chat
-type BotRemovedFromChatUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Chat identifier bot removed from
- ChatId int64 `json:"chat_id"`
- // User id who removed bot from chat
- UserId int64 `json:"user_id"`
+type BotCommand struct {
+ Name string `json:"name"` // Command name
+ Description string `json:"description,omitempty"` // Optional command description
}
-// Bot gets this type of update as soon as user pressed `Start` button
-type BotStartedUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Dialog identifier where event has occurred
- ChatId int64 `json:"chat_id"`
- // User pressed the 'Start' button
- UserId int64 `json:"user_id"`
+type BotInfo struct {
+ UserId int `json:"user_id"` // Users identifier
+ Name string `json:"name"` // Users visible name
+ Username string `json:"username,omitempty"` // Unique public user name. Can be `null` if user is not accessible or it is not set
+ AvatarUrl string `json:"avatar_url,omitempty"` // URL of avatar
+ FullAvatarUrl string `json:"full_avatar_url,omitempty"` // URL of avatar of a bigger size
+ Commands []BotCommand `json:"commands,omitempty"` // Commands supported by bots
+ Description string `json:"description,omitempty"` // Bot description
+}
+
+type BotPatch struct {
+ Name string `json:"name,omitempty"` // Visible name of bots
+ Username string `json:"username,omitempty"` // Bot unique identifier. It can be any string 4-64 characters long containing any digit, letter or special symbols: \"-\" or \"_\". It **must** starts with a letter
+ Description string `json:"description,omitempty"` // Bot description up to 16k characters long
+ Commands []BotCommand `json:"commands,omitempty"` // Commands supported by bots. Pass empty list if you want to remove commands
+ Photo *PhotoAttachmentRequestPayload `json:"photo,omitempty"` // Request to set bots photo
}
type Button struct {
- Type string `json:"type,omitempty"`
- // Visible text of button
- Text string `json:"text"`
- // Intent of button. Affects clients representation.
- Intent Intent `json:"intent"`
+ Type ButtonType `json:"type"`
+ Text string `json:"text"` // Visible text of button
}
-// Object sent to bot when user presses button
-type Callback struct {
- // Unix-time when user pressed the button
- Timestamp int64 `json:"timestamp"`
- // Current keyboard identifier
- CallbackId string `json:"callback_id"`
- // Button payload
- Payload string `json:"payload"`
- // User pressed the button
- User User `json:"user"`
+func (b Button) GetType() ButtonType {
+ return b.Type
+}
+
+func (b Button) GetText() string {
+ return b.Text
}
-// Send this object when your bot wants to react to when a button is pressed
+type ButtonInterface interface {
+ GetType() ButtonType
+ GetText() string
+}
+
+// Send this object when your bots wants to react to when a button is pressed
type CallbackAnswer struct {
- UserId int64 `json:"user_id,omitempty"`
- // Fill this if you want to modify current message
- Message NewMessageBody `json:"message,omitempty"`
- // Fill this if you just want to send one-time notification to user
- Notification string `json:"notification,omitempty"`
+ UserId int `json:"user_id,omitempty"`
+ Message *NewMessageBody `json:"message,omitempty"` // Fill this if you want to modify current message
+ Notification string `json:"notification,omitempty"` // Fill this if you just want to send one-time notification to user
}
// After pressing this type of button client sends to server payload it contains
type CallbackButton struct {
- Type ButtonType `json:"type,omitempty"`
- // Visible text of button
- Text string `json:"text"`
- // Intent of button. Affects clients representation.
- Intent Intent `json:"intent"`
- // Button payload
- Payload string `json:"payload"`
+ Button
+ Payload string `json:"payload"` // Button payload
+ Intent Intent `json:"intent,omitempty"` // Intent of button. Affects clients representation
+}
+
+type CallbackButtonAllOf struct {
+ Payload string `json:"payload"` // Button payload
+ Intent Intent `json:"intent,omitempty"` // Intent of button. Affects clients representation
}
type Chat struct {
- // Chats identifier
- ChatId int64 `json:"chat_id"`
- // Type of chat. One of: dialog, chat, channel
- Type ChatType `json:"type"`
- // Chat status. One of: - active: bot is active member of chat - removed: bot was kicked - left: bot intentionally left chat - closed: chat was closed
- Status ChatStatus `json:"status"`
- // Visible title of chat
- Title string `json:"title"`
- // Icon of chat
- Icon Image `json:"icon"`
- // Time of last event occured in chat
- LastEventTime int64 `json:"last_event_time"`
- // Number of people in chat. Always 2 for `dialog` chat type
- ParticipantsCount int32 `json:"participants_count"`
- // Identifier of chat owner. Visible only for chat admins
- OwnerId int64 `json:"owner_id,omitempty"`
- // Participants in chat with time of last activity. Can be *null* when you request list of chats. Visible for chat admins only
- Participants map[string]int64 `json:"participants,omitempty"`
- // Is current chat publicly available. Always `false` for dialogs
- IsPublic bool `json:"is_public"`
- // Link on chat if it is public
- Link string `json:"link,omitempty"`
- // Chat description
- Description map[string]interface{} `json:"description"`
+ ChatId int `json:"chat_id"` // Chats identifier
+ Type ChatType `json:"type"` // Type of chat. One of: dialog, chat, channel
+ Status ChatStatus `json:"status"` // Chat status. One of: - active: bots is active member of chat - removed: bots was kicked - left: bots intentionally left chat - closed: chat was closed
+ Title string `json:"title,omitempty"` // Visible title of chat. Can be null for dialogs
+ Icon *Image `json:"icon"` // Icon of chat
+ LastEventTime int `json:"last_event_time"` // Time of last event occurred in chat
+ ParticipantsCount int `json:"participants_count"` // Number of people in chat. Always 2 for `dialog` chat type
+ OwnerId int `json:"owner_id,omitempty"` // Identifier of chat owner. Visible only for chat admins
+ Participants *map[string]int `json:"participants,omitempty"` // Participants in chat with time of last activity. Can be *null* when you request list of chats. Visible for chat admins only
+ IsPublic bool `json:"is_public"` // Is current chat publicly available. Always `false` for dialogs
+ Link string `json:"link,omitempty"` // Link on chat if it is public
+ Description *map[string]interface{} `json:"description"` // Chat description
}
// ChatAdminPermission : Chat admin permissions
@@ -163,44 +152,34 @@ const (
)
type ChatList struct {
- // List of requested chats
- Chats []Chat `json:"chats"`
- // Reference to the next page of requested chats
- Marker int64 `json:"marker"`
+ Chats []Chat `json:"chats"` // List of requested chats
+ Marker *int `json:"marker"` // Reference to the next page of requested chats
}
type ChatMember struct {
- // Users identifier
- UserId int64 `json:"user_id"`
- // Users visible name
- Name string `json:"name"`
- // Unique public user name. Can be `null` if user is not accessible or it is not set
- Username string `json:"username"`
- // URL of avatar
- AvatarUrl string `json:"avatar_url"`
- // URL of avatar of a bigger size
- FullAvatarUrl string `json:"full_avatar_url"`
- LastAccessTime int64 `json:"last_access_time"`
- IsOwner bool `json:"is_owner"`
- IsAdmin bool `json:"is_admin"`
- JoinTime int64 `json:"join_time"`
- // Permissions in chat if member is admin. `null` otherwise
- Permissions []ChatAdminPermission `json:"permissions"`
+ UserId int `json:"user_id"` // Users identifier
+ Name string `json:"name"` // Users visible name
+ Username string `json:"username,omitempty"` // Unique public user name. Can be `null` if user is not accessible or it is not set
+ AvatarUrl string `json:"avatar_url,omitempty"` // URL of avatar
+ FullAvatarUrl string `json:"full_avatar_url,omitempty"` // URL of avatar of a bigger size
+ LastAccessTime int `json:"last_access_time"`
+ IsOwner bool `json:"is_owner"`
+ IsAdmin bool `json:"is_admin"`
+ JoinTime int `json:"join_time"`
+ Permissions []ChatAdminPermission `json:"permissions,omitempty"` // Permissions in chat if member is admin. `null` otherwise
}
type ChatMembersList struct {
- // Participants in chat with time of last activity. Visible only for chat admins
- Members []ChatMember `json:"members"`
- // Pointer to the next data page
- Marker int64 `json:"marker"`
+ Members []ChatMember `json:"members"` // Participants in chat with time of last activity. Visible only for chat admins
+ Marker *int `json:"marker"` // Pointer to the next data page
}
type ChatPatch struct {
- Icon PhotoAttachmentRequestPayload `json:"icon,omitempty"`
- Title string `json:"title,omitempty"`
+ Icon *PhotoAttachmentRequestPayload `json:"icon,omitempty"`
+ Title string `json:"title,omitempty"`
}
-// ChatStatus : Chat status for current bot
+// ChatStatus : Chat status for current bots
type ChatStatus string
// List of ChatStatus
@@ -212,19 +191,6 @@ const (
SUSPENDED ChatStatus = "suspended"
)
-// Bot gets this type of update as soon as title has been changed in chat
-type ChatTitleChangedUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Chat identifier where event has occurred
- ChatId int64 `json:"chat_id"`
- // User who changed title
- UserId int64 `json:"user_id"`
- // New title
- Title string `json:"title"`
-}
-
// ChatType : Type of chat. Dialog (one-on-one), chat or channel
type ChatType string
@@ -236,86 +202,86 @@ const (
)
type ContactAttachment struct {
- Type string `json:"type,omitempty"`
+ Attachment
Payload ContactAttachmentPayload `json:"payload"`
}
type ContactAttachmentPayload struct {
- // User info in VCF format
- VcfInfo string `json:"vcfInfo"`
- // User info
- TamInfo User `json:"tamInfo"`
+ VcfInfo string `json:"vcfInfo,omitempty"` // User info in VCF format
+ TamInfo *User `json:"tamInfo"` // User info
}
// Request to attach contact card to message. MUST be the only attachment in message
type ContactAttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ AttachmentRequest
Payload ContactAttachmentRequestPayload `json:"payload"`
}
+func NewContactAttachmentRequest(payload ContactAttachmentRequestPayload) *ContactAttachmentRequest {
+ return &ContactAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentContact}}
+}
+
type ContactAttachmentRequestPayload struct {
- // Contact name
- Name string `json:"name"`
- // Contact identifier
- ContactId int64 `json:"contactId"`
- // Full information about contact in VCF format
- VcfInfo string `json:"vcfInfo"`
- // Contact phone in VCF format
- VcfPhone string `json:"vcfPhone"`
+ Name string `json:"name,omitempty"` // Contact name
+ ContactId int `json:"contactId,omitempty"` // Contact identifier
+ VcfInfo string `json:"vcfInfo,omitempty"` // Full information about contact in VCF format
+ VcfPhone string `json:"vcfPhone,omitempty"` // Contact phone in VCF format
}
// Server returns this if there was an exception to your request
type Error struct {
- // Error
- Error string `json:"error,omitempty"`
- // Error code
- Code string `json:"code"`
- // Human-readable description
- Message string `json:"message"`
+ Error string `json:"error,omitempty"` // Error
+ Code string `json:"code"` // Error code
+ Message string `json:"message"` // Human-readable description
}
type FileAttachment struct {
- Type string `json:"type,omitempty"`
- Payload AttachmentPayload `json:"payload"`
- Filename string `json:"filename"`
- Size int64 `json:"size"`
+ Attachment
+ Payload FileAttachmentPayload `json:"payload"`
+ Filename string `json:"filename"` // Uploaded file name
+ Size int `json:"size"` // File size in bytes
+}
+
+type FileAttachmentPayload struct {
+ Url string `json:"url"` // Media attachment URL
+ Token string `json:"token"` // Use `token` in case when you are trying to reuse the same attachment in other message
}
// Request to attach file to message. MUST be the only attachment in message
type FileAttachmentRequest struct {
- Type string `json:"type,omitempty"`
- Payload UploadedFileInfo `json:"payload"`
+ AttachmentRequest
+ Payload UploadedInfo `json:"payload"`
+}
+
+func NewFileAttachmentRequest(payload UploadedInfo) *FileAttachmentRequest {
+ return &FileAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentFile}}
}
// List of all WebHook subscriptions
type GetSubscriptionsResult struct {
- // Current suscriptions
- Subscriptions []Subscription `json:"subscriptions"`
+ Subscriptions []Subscription `json:"subscriptions"` // Current subscriptions
}
// Generic schema describing image object
type Image struct {
- // URL of image
- Url string `json:"url"`
+ Url string `json:"url"` // URL of image
}
// Buttons in messages
type InlineKeyboardAttachment struct {
- Type string `json:"type,omitempty"`
- // Unique identifier of keyboard
- //CallbackId string `json:"callback_id"`
- Payload Keyboard `json:"payload"`
+ Attachment
+ CallbackId string `json:"callback_id"` // Unique identifier of keyboard
+ Payload Keyboard `json:"payload"`
}
// Request to attach keyboard to message
type InlineKeyboardAttachmentRequest struct {
- Type string `json:"type,omitempty"`
- Payload InlineKeyboardAttachmentRequestPayload `json:"payload"`
+ AttachmentRequest
+ Payload Keyboard `json:"payload"`
}
-type InlineKeyboardAttachmentRequestPayload struct {
- // Two-dimensional array of buttons
- Buttons [][]Button `json:"buttons"`
+func NewInlineKeyboardAttachmentRequest(payload Keyboard) *InlineKeyboardAttachmentRequest {
+ return &InlineKeyboardAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentKeyboard}}
}
type ButtonType string
@@ -339,99 +305,78 @@ const (
// Keyboard is two-dimension array of buttons
type Keyboard struct {
- Buttons interface{} `json:"buttons"`
+ Buttons [][]ButtonInterface `json:"buttons"`
}
// After pressing this type of button user follows the link it contains
type LinkButton struct {
- Type ButtonType `json:"type,omitempty"`
- // Visible text of button
- Text string `json:"text"`
- // Intent of button. Affects clients representation.
- Intent Intent `json:"intent"`
- Url string `json:"url"`
+ Button
+ Url string `json:"url"`
}
type LinkedMessage struct {
- // Type of linked message
- Type MessageLinkType `json:"type"`
- // User sent this message
- Sender *User `json:"sender,omitempty"`
- // Chat where message was originally posted
- ChatId *int64 `json:"chat_id,omitempty"`
- Message MessageBody `json:"message"`
+ Type MessageLinkType `json:"type"` // Type of linked message
+ Sender User `json:"sender,omitempty"` // User sent this message. Can be `null` if message has been posted on behalf of a channel
+ ChatId int `json:"chat_id,omitempty"` // Chat where message has been originally posted. For forwarded messages only
+ Message MessageBody `json:"message"`
}
type LocationAttachment struct {
- Type string `json:"type,omitempty"`
+ Attachment
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// Request to attach keyboard to message
type LocationAttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ AttachmentRequest
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
+func NewLocationAttachmentRequest(latitude float64, longitude float64) *LocationAttachmentRequest {
+ return &LocationAttachmentRequest{Latitude: latitude, Longitude: longitude, AttachmentRequest: AttachmentRequest{Type: AttachmentLocation}}
+}
+
+type MediaAttachmentPayload struct {
+ Url string `json:"url"` // Media attachment URL
+ Token string `json:"token"` // Use `token` in case when you are trying to reuse the same attachment in other message
+}
+
// Message in chat
type Message struct {
- // User that sent this message
- Sender *User `json:"sender,omitempty"`
- // Message recipient. Could be user or chat
- Recipient Recipient `json:"recipient"`
- // Unix-time when message was created
- Timestamp int64 `json:"timestamp"`
- // Forwarder or replied message
- Link LinkedMessage `json:"link,omitempty"`
- // Body of created message. Text + attachments. Could be null if message contains only forwarded message.
- Body MessageBody `json:"body"`
+ Sender User `json:"sender,omitempty"` // User that sent this message. Can be `null` if message has been posted on behalf of a channel
+ Recipient Recipient `json:"recipient"` // Message recipient. Could be user or chat
+ Timestamp int `json:"timestamp"` // Unix-time when message was created
+ Link *LinkedMessage `json:"link,omitempty"` // Forwarder or replied message
+ Body MessageBody `json:"body"` // Body of created message. Text + attachments. Could be null if message contains only forwarded message
+ Stat *MessageStat `json:"stat,omitempty"` // Message statistics. Available only for channels in [GET:/messages](#operation/getMessages) context
}
// Schema representing body of message
type MessageBody struct {
- // Unique identifier of message
- Mid string `json:"mid"`
- // Sequence identifier of message in chat
- Seq int64 `json:"seq"`
- // Message text
- Text string `json:"text"`
- // Message attachments. Could be one of `Attachment` type. See description of this schema
- RawAttachments []json.RawMessage `json:"attachments"`
-
- Attachments []interface{}
- // In case this message is repled to, it is the unique identifier of the replied message
- ReplyTo string `json:"reply_to,omitempty"`
+ Mid string `json:"mid"` // Unique identifier of message
+ Seq int `json:"seq"` // Sequence identifier of message in chat
+ Text string `json:"text,omitempty"` // Message text
+ rawAttachments []json.RawMessage `json:"attachments"` // Message attachments. Could be one of `Attachment` type. See description of this schema
+ Attachments []interface{}
+ ReplyTo string `json:"reply_to,omitempty"` // In case this message is reply to another, it is the unique identifier of the replied message
}
-// You will get this `update` as soon as user presses button
-type MessageCallbackUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- Callback Callback `json:"callback"`
- // Original message containing inline keyboard. Can be `null` in case it had been deleted by the moment a bot got this update.
- Message Message `json:"message"`
-}
-
-// You will get this `update` as soon as message is created
-type MessageCreatedUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Newly created message
- Message Message `json:"message"`
-}
+type UpdateType string
-// You will get this `update` as soon as message is edited
-type MessageEditedUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Edited message
- Message Message `json:"message"`
-}
+const (
+ TypeMessageCallback UpdateType = "message_callback"
+ TypeMessageCreated = "message_created"
+ TypeMessageRemoved = "message_removed"
+ TypeMessageEdited = "message_edited"
+ TypeBotAdded = "bot_added"
+ TypeBotRemoved = "bot_removed"
+ TypeUserAdded = "user_added"
+ TypeUserRemoved = "user_removed"
+ TypeBotStarted = "bot_started"
+ TypeChatTitleChanged = "chat_title_changed"
+)
// MessageLinkType : Type of linked message
type MessageLinkType string
@@ -444,104 +389,83 @@ const (
// Paginated list of messages
type MessageList struct {
- // List of messages
- Messages []Message `json:"messages"`
+ Messages []Message `json:"messages"` // List of messages
}
-// You will get this `update` as soon as message is removed
-type MessageRemovedUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Identifier of removed message
- MessageId string `json:"message_id"`
+// Message statistics
+type MessageStat struct {
+ Views int `json:"views"`
}
-// You will get this `update` as soon as message is restored
-type MessageRestoredUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Restored message identifier
- MessageId string `json:"message_id"`
+type NewMessageBody struct {
+ Text string `json:"text,omitempty"` // Message text
+ Attachments []interface{} `json:"attachments,omitempty"` // Message attachments. See `AttachmentRequest` and it's inheritors for full information
+ Link *NewMessageLink `json:"link"` // Link to Message
+ Notify bool `json:"notify,omitempty"` // If false, chat participants wouldn't be notified
}
-type NewMessageBody struct {
- // Message text
- Text string `json:"text"`
- // Message attachments. See `AttachmentRequest` and it's inheritors for full information.
- Attachments []interface{} `json:"attachments,omitempty"`
- // If false, chat participants wouldn't be notified
- Notify bool `json:"notify,omitempty"`
+type NewMessageLink struct {
+ Type MessageLinkType `json:"type"` // Type of message link
+ Mid string `json:"mid"` // Message identifier of original message
}
// Image attachment
type PhotoAttachment struct {
- Type string `json:"type,omitempty"`
+ Attachment
Payload PhotoAttachmentPayload `json:"payload"`
}
type PhotoAttachmentPayload struct {
- // Unique identifier of this image
- PhotoId *int64 `json:"photo_id,omitempty"`
- Token *string `json:"token,omitempty"`
- // Image URL
- Url *string `json:"url,omitempty"`
+ PhotoId int `json:"photo_id"` // Unique identifier of this image
+ Token string `json:"token"`
+ Url string `json:"url"` // Image URL
}
type PhotoAttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ AttachmentRequest
Payload PhotoAttachmentRequestPayload `json:"payload"`
}
-// Request to attach image. All fields are mutually exclusive.
+func NewPhotoAttachmentRequest(payload PhotoAttachmentRequestPayload) *PhotoAttachmentRequest {
+ return &PhotoAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentImage}}
+}
+
+type PhotoAttachmentRequestAllOf struct {
+ Payload PhotoAttachmentRequestPayload `json:"payload"`
+}
+
+// Request to attach image. All fields are mutually exclusive
type PhotoAttachmentRequestPayload struct {
- // If specified, given URL will be attached to message as image
- Url string `json:"url,omitempty"`
- // Token of any existing attachment
- Token string `json:"token,omitempty"`
- // Tokens were obtained after uploading images
- Photos map[string]PhotoToken `json:"photos,omitempty"`
+ Url string `json:"url,omitempty"` // Any external image URL you want to attach
+ Token string `json:"token,omitempty"` // Token of any existing attachment
+ Photos *map[string]PhotoToken `json:"photos,omitempty"` // Tokens were obtained after uploading images
}
type PhotoToken struct {
- // Encoded information of uploaded image
- Token string `json:"token"`
+ Token string `json:"token"` // Encoded information of uploaded image
}
-// This is information you will recieve as soon as an image uploaded
+// This is information you will receive as soon as an image uploaded
type PhotoTokens struct {
Photos map[string]PhotoToken `json:"photos"`
}
-// New message recepient. Could be user or chat
+// New message recipient. Could be user or chat
type Recipient struct {
- // Chat identifier
- ChatId int64 `json:"chat_id"`
- // Chat type
- ChatType ChatType `json:"chat_type"`
- // User identifier, if message was sent to user
- UserId int64 `json:"user_id"`
+ ChatId int `json:"chat_id,omitempty"` // Chat identifier
+ ChatType ChatType `json:"chat_type"` // Chat type
+ UserId int `json:"user_id,omitempty"` // User identifier, if message was sent to user
}
-// After pressing this type of button client sends new message with attachment of curent user contact
+// After pressing this type of button client sends new message with attachment of current user contact
type RequestContactButton struct {
- Type ButtonType `json:"type,omitempty"`
- // Visible text of button
- Text string `json:"text"`
- // Intent of button. Affects clients representation.
- Intent Intent `json:"intent"`
+ Button
}
// After pressing this type of button client sends new message with attachment of current user geo location
type RequestGeoLocationButton struct {
- Type ButtonType `json:"type,omitempty"`
- // Visible text of button
- Text string `json:"text"`
- // Intent of button. Affects clients representation.
- Intent Intent `json:"intent"`
- // If *true*, sends location without asking user's confirmation
- Quick bool `json:"quick,omitempty"`
+ Button
+ Quick bool `json:"quick,omitempty"` // If *true*, sends location without asking user's confirmation
}
type SendMessageResult struct {
@@ -562,239 +486,210 @@ const (
)
type ShareAttachment struct {
- Type string `json:"type,omitempty"`
+ Attachment
Payload AttachmentPayload `json:"payload"`
}
// Simple response to request
type SimpleQueryResult struct {
- // `true` if request was successful. `false` otherwise
- Success bool `json:"success"`
- Message string `json:"message,omitempty"`
+ Success bool `json:"success"` // `true` if request was successful. `false` otherwise
+ Message string `json:"message,omitempty"` // Explanatory message if the result is not successful
}
type StickerAttachment struct {
- Type string `json:"type,omitempty"`
- Payload AttachmentPayload `json:"payload"`
- Width int `json:"width"`
- Height int `json:"height"`
+ Attachment
+ Payload StickerAttachmentPayload `json:"payload"`
+ Width int `json:"width"` // Sticker width
+ Height int `json:"height"` // Sticker height
+}
+
+type StickerAttachmentPayload struct {
+ Url string `json:"url"` // Media attachment URL
+ Code string `json:"code"` // Sticker identifier
}
// Request to attach sticker. MUST be the only attachment request in message
type StickerAttachmentRequest struct {
- Type string `json:"type,omitempty"`
+ AttachmentRequest
Payload StickerAttachmentRequestPayload `json:"payload"`
}
+func NewStickerAttachmentRequest(payload StickerAttachmentRequestPayload) *StickerAttachmentRequest {
+ return &StickerAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentSticker}}
+}
+
type StickerAttachmentRequestPayload struct {
- // Sticker code
- Code string `json:"code"`
+ Code string `json:"code"` // Sticker code
}
// Schema to describe WebHook subscription
type Subscription struct {
- // WebHook URL
- Url string `json:"url"`
- // Unix-time when subscription was created
- Time int64 `json:"time"`
- // Update types bot subscribed for
- UpdateTypes []string `json:"update_types"`
- Version string `json:"version"`
+ Url string `json:"url"` // Webhook URL
+ Time int `json:"time"` // Unix-time when subscription was created
+ UpdateTypes []string `json:"update_types,omitempty"` // Update types bots subscribed for
+ Version string `json:"version,omitempty"`
}
// Request to set up WebHook subscription
type SubscriptionRequestBody struct {
- // URL of HTTP(S)-endpoint of your bot
- Url string `json:"url"`
- // List of update types your bot want to receive. See `Update` object for a complete list of types
- UpdateTypes []string `json:"update_types,omitempty"`
- // Version of API. Affects model representation
- Version string `json:"version,omitempty"`
+ Url string `json:"url"` // URL of HTTP(S)-endpoint of your bots. Must starts with http(s)://
+ UpdateTypes []string `json:"update_types,omitempty"` // List of update types your bots want to receive. See `Update` object for a complete list of types
+ Version string `json:"version,omitempty"` // Version of API. Affects model representation
}
-type UpdateType string
+// List of all updates in chats your bots participated in
+type UpdateList struct {
+ Updates []json.RawMessage `json:"updates"` // Page of updates
+ Marker *int `json:"marker"` // Pointer to the next data page
+}
+
+// Endpoint you should upload to your binaries
+type UploadEndpoint struct {
+ Url string `json:"url"` // URL to upload
+}
+// UploadType : Type of file uploading
+type UploadType string
+
+// List of UploadType
const (
- UpdateTypeMessageCallback UpdateType = "message_callback"
- UpdateTypeMessageCreated = "message_created"
- UpdateTypeMessageRemoved = "message_removed"
- UpdateTypeMessageEdited = "message_edited"
- UpdateTypeMessageRestored = "message_restored"
- UpdateTypeBotAdded = "bot_added"
- UpdateTypeBotRemoved = "bot_removed"
- UpdateTypeUserAdded = "user_added"
- UpdateTypeUserRemoved = "user_removed"
- UpdateTypeBotStarted = "bot_started"
- UpdateTypeChatTitleChanged = "chat_title_changed"
+ PHOTO UploadType = "photo"
+ VIDEO UploadType = "video"
+ AUDIO UploadType = "audio"
+ FILE UploadType = "file"
)
-// `Update` object repsesents different types of events that happened in chat. See its inheritors
-type Update struct {
- UpdateType UpdateType `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
+// This is information you will receive as soon as audio/video is uploaded
+type UploadedInfo struct {
+ Token string `json:"token,omitempty"` // Token is unique uploaded media identfier
}
-type UpdateMessageCallback struct {
- Update
- Callback Callback `json:"callback"`
- Message Message `json:"message"`
+type User struct {
+ UserId int `json:"user_id"` // Users identifier
+ Name string `json:"name"` // Users visible name
+ Username string `json:"username,omitempty"` // Unique public user name. Can be `null` if user is not accessible or it is not set
}
-type UpdateMessageCreated struct {
- Update
- Message Message `json:"message"`
+type UserIdsList struct {
+ UserIds []int `json:"user_ids"`
}
-type UpdateMessageEdited struct {
- Update
- Message Message `json:"message"`
+type UserWithPhoto struct {
+ UserId int `json:"user_id"` // Users identifier
+ Name string `json:"name"` // Users visible name
+ Username string `json:"username,omitempty"` // Unique public user name. Can be `null` if user is not accessible or it is not set
+ AvatarUrl string `json:"avatar_url,omitempty"` // URL of avatar
+ FullAvatarUrl string `json:"full_avatar_url,omitempty"` // URL of avatar of a bigger size
}
-type UpdateMessageRestored struct {
- Update
- MessageID string `json:"message_id"`
+type VideoAttachment struct {
+ Attachment
+ Payload MediaAttachmentPayload `json:"payload"`
}
-type UpdateMessageRemoved struct {
- Update
- MessageID string `json:"message_id"`
+// Request to attach video to message
+type VideoAttachmentRequest struct {
+ AttachmentRequest
+ Payload UploadedInfo `json:"payload"`
}
-type UpdateBotAdded struct {
- Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
+func NewVideoAttachmentRequest(payload UploadedInfo) *VideoAttachmentRequest {
+ return &VideoAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentVideo}}
}
-type UpdateBotRemoved struct {
- Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
+// `Update` object represents different types of events that happened in chat. See its inheritors
+type Update struct {
+ UpdateType UpdateType `json:"update_type"`
+ Timestamp int `json:"timestamp"` // Unix-time when event has occurred
}
-type UpdateBotStarted struct {
- Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
+func (u Update) GetUpdateType() UpdateType {
+ return u.UpdateType
}
-type UpdateChatTitleChanged struct {
- Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
- Title string `json:"title"`
+func (u Update) GetUpdateTime() time.Time {
+ return time.Unix(int64(u.Timestamp), 0)
}
-type UpdateUserAdded struct {
- Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
- InviterID int64 `json:"inviter_id"`
+type UpdateInterface interface {
+ GetUpdateType() UpdateType
+ GetUpdateTime() time.Time
}
-type UpdateUserRemoved struct {
+// You will receive this update when bots has been added to chat
+type BotAddedToChatUpdate struct {
Update
- ChatID int64 `json:"chat_id"`
- User User `json:"user"`
- AdminID int64 `json:"admin_id"`
-}
-
-// List of all updates in chats your bot participated in
-type UpdateList struct {
- // Page of updates
- Updates []json.RawMessage `json:"updates"`
- // Pointer to the next data page
- Marker int64 `json:"marker"`
+ ChatId int `json:"chat_id"` // Chat id where bots was added
+ User User `json:"user"` // User who added bots to chat
}
-// Endpoint you should upload to your binaries
-type UploadEndpoint struct {
- // URL to upload
- Url string `json:"url"`
+// You will receive this update when bots has been removed from chat
+type BotRemovedFromChatUpdate struct {
+ Update
+ ChatId int `json:"chat_id"` // Chat identifier bots removed from
+ User User `json:"user"` // User who removed bots from chat
}
-// UploadType : Type of file uploading
-type UploadType string
-
-// List of UploadType
-const (
- PHOTO UploadType = "photo"
- VIDEO UploadType = "video"
- AUDIO UploadType = "audio"
- FILE UploadType = "file"
-)
-
-// This is information you will recieve as soon as a file is uploaded
-type UploadedFileInfo struct {
- // Unique file identifier
- FileId int64 `json:"fileId"`
+// Bot gets this type of update as soon as user pressed `Start` button
+type BotStartedUpdate struct {
+ Update
+ ChatId int `json:"chat_id"` // Dialog identifier where event has occurred
+ User User `json:"user"` // User pressed the 'Start' button
}
-// This is information you will recieve as soon as audio/video is uploaded
-type UploadedInfo struct {
- Id int64 `json:"id"`
+// Object sent to bots when user presses button
+type Callback struct {
+ Timestamp int `json:"timestamp"` // Unix-time when event has occurred
+ CallbackID string `json:"callback_id"`
+ Payload string `json:"payload,omitempty"` // Button payload
+ User User `json:"user"` // User pressed the button
}
-type User struct {
- // Users identifier
- UserId int64 `json:"user_id"`
- // Users visible name
- Name string `json:"name"`
- // Unique public user name. Can be `null` if user is not accessible or it is not set
- Username string `json:"username"`
+// Bot gets this type of update as soon as title has been changed in chat
+type ChatTitleChangedUpdate struct {
+ Update
+ ChatId int `json:"chat_id"` // Chat identifier where event has occurred
+ User User `json:"user"` // User who changed title
+ Title string `json:"title"` // New title
}
-// You will receive this update when user has been added to chat where bot is administrator
-type UserAddedToChatUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Chat identifier where event has occured
- ChatId int64 `json:"chat_id"`
- // User added to chat
- UserId int64 `json:"user_id"`
- // User who added user to chat
- InviterId int64 `json:"inviter_id"`
+// You will get this `update` as soon as user presses button
+type MessageCallbackUpdate struct {
+ Update
+ Callback Callback `json:"callback"`
+ Message *Message `json:"message"` // Original message containing inline keyboard. Can be `null` in case it had been deleted by the moment a bots got this update
}
-type UserIdsList struct {
- UserIds []int64 `json:"user_ids"`
+// You will get this `update` as soon as message is created
+type MessageCreatedUpdate struct {
+ Update
+ Message Message `json:"message"` // Newly created message
}
-// You will receive this update when user has been removed from chat where bot is administrator
-type UserRemovedFromChatUpdate struct {
- UpdateType string `json:"update_type,omitempty"`
- // Unix-time when event has occured
- Timestamp int64 `json:"timestamp"`
- // Chat identifier where event has occured
- ChatId int64 `json:"chat_id"`
- // User removed from chat
- UserId int64 `json:"user_id"`
- // Administrator who removed user from chat
- AdminId int64 `json:"admin_id"`
+// You will get this `update` as soon as message is edited
+type MessageEditedUpdate struct {
+ Update
+ Message Message `json:"message"` // Edited message
}
-type UserWithPhoto struct {
- // Users identifier
- UserId int64 `json:"user_id"`
- // Users visible name
- Name string `json:"name"`
- // Unique public user name. Can be `null` if user is not accessible or it is not set
- Username string `json:"username"`
- // URL of avatar
- AvatarUrl *string `json:"avatar_url,omitempty"`
- // URL of avatar of a bigger size
- FullAvatarUrl *string `json:"full_avatar_url,omitempty"`
+// You will get this `update` as soon as message is removed
+type MessageRemovedUpdate struct {
+ Update
+ MessageId string `json:"message_id"` // Identifier of removed message
}
-type VideoAttachment struct {
- Type string `json:"type,omitempty"`
- Payload AttachmentPayload `json:"payload"`
+// You will receive this update when user has been added to chat where bots is administrator
+type UserAddedToChatUpdate struct {
+ Update
+ ChatId int `json:"chat_id"` // Chat identifier where event has occurred
+ User User `json:"user"` // User added to chat
+ InviterId int `json:"inviter_id"` // User who added user to chat
}
-// Request to attach video to message
-type VideoAttachmentRequest struct {
- Type string `json:"type,omitempty"`
- Payload UploadedInfo `json:"payload"`
+// You will receive this update when user has been removed from chat where bots is administrator
+type UserRemovedFromChatUpdate struct {
+ Update
+ ChatId int `json:"chat_id"` // Chat identifier where event has occurred
+ User User `json:"user"` // User removed from chat
+ AdminId int `json:"admin_id"` // Administrator who removed user from chat
}
diff --git a/subscriptions.go b/subscriptions.go
new file mode 100644
index 0000000..d32d985
--- /dev/null
+++ b/subscriptions.go
@@ -0,0 +1,68 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "net/url"
+)
+
+type subscriptions struct {
+ client *client
+}
+
+func newSubscriptions(client *client) *subscriptions {
+ return &subscriptions{client: client}
+}
+func (a *subscriptions) GetSubscriptions() (*GetSubscriptionsResult, error) {
+ result := new(GetSubscriptionsResult)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodGet, "subscriptions", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *subscriptions) Subscribe(subscribeURL string, updateTypes []string) (*SimpleQueryResult, error) {
+ subscription := &SubscriptionRequestBody{
+ Url: subscribeURL,
+ UpdateTypes: updateTypes,
+ Version: a.client.version,
+ }
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ body, err := a.client.request(http.MethodPost, "subscriptions", values, subscription)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *subscriptions) Unsubscribe(subscriptionURL string) (*SimpleQueryResult, error) {
+ result := new(SimpleQueryResult)
+ values := url.Values{}
+ values.Set("url", subscriptionURL)
+ body, err := a.client.request(http.MethodDelete, "subscriptions", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
diff --git a/uploads.go b/uploads.go
new file mode 100644
index 0000000..57b2ce8
--- /dev/null
+++ b/uploads.go
@@ -0,0 +1,84 @@
+// Package tamtam implements TamTam Bot API
+// Copyright (c) 2019 Alexander Kiryukhin <a.kiryukhin@mail.ru>
+package tamtam
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "log"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+)
+
+type uploads struct {
+ client *client
+}
+
+func newUploads(client *client) *uploads {
+ return &uploads{client: client}
+}
+
+func (a *uploads) GetUploadURL(uploadType UploadType) (*UploadEndpoint, error) {
+ result := new(UploadEndpoint)
+ values := url.Values{}
+ values.Set("type", string(uploadType))
+ body, err := a.client.request(http.MethodPost, "uploads", values, nil)
+ if err != nil {
+ return result, err
+ }
+ defer func() {
+ if err := body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ return result, json.NewDecoder(body).Decode(result)
+}
+
+func (a *uploads) UploadMedia(uploadType UploadType, filename string) (*UploadedInfo, error) {
+ bodyBuf := &bytes.Buffer{}
+ bodyWriter := multipart.NewWriter(bodyBuf)
+
+ fileWriter, err := bodyWriter.CreateFormFile("data", filename)
+ if err != nil {
+ return nil, err
+ }
+
+ fh, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := fh.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ _, err = io.Copy(fileWriter, fh)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := bodyWriter.Close(); err != nil {
+ return nil, err
+ }
+
+ target, err := a.GetUploadURL(uploadType)
+ if err != nil {
+ return nil, err
+ }
+ contentType := bodyWriter.FormDataContentType()
+
+ resp, err := http.Post(target.Url, contentType, bodyBuf)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ log.Println(err)
+ }
+ }()
+ result := new(UploadedInfo)
+ return result, json.NewDecoder(resp.Body).Decode(result)
+}