diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README.md | 69 | ||||
-rw-r--r-- | api.go | 582 | ||||
-rw-r--r-- | bots.go | 48 | ||||
-rw-r--r-- | chats.go | 168 | ||||
-rw-r--r-- | client.go | 56 | ||||
-rw-r--r-- | examples/example.go | 53 | ||||
-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.go | 126 | ||||
-rw-r--r-- | models.go | 803 | ||||
-rw-r--r-- | subscriptions.go | 68 | ||||
-rw-r--r-- | uploads.go | 84 |
14 files changed, 1176 insertions, 981 deletions
@@ -24,3 +24,5 @@ _testmain.go *.prof .idea + +tmp
\ No newline at end of file @@ -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) + } + } +} +``` ## Автор @@ -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) + } +} @@ -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) @@ -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), + } +} @@ -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) +} |