From 6e4ade909d9b3a0a4a8dd8c11a4b1984b84f09e4 Mon Sep 17 00:00:00 2001 From: Alexander Kiryukhin Date: Thu, 5 Sep 2019 22:43:32 +0300 Subject: Fully refactored high level API --- README.md | 63 +--- api.go | 94 ++--- bots.go | 10 +- chats.go | 40 +- client.go | 4 +- examples/example.go | 45 +-- examples/example_longpolling.go | 8 +- examples/example_webhook.go | 10 +- keyboard.go | 46 +-- message.go | 81 +++++ messages.go | 100 +++-- models.go | 788 ---------------------------------------- schemes/schemes.go | 788 ++++++++++++++++++++++++++++++++++++++++ subscriptions.go | 16 +- uploads.go | 154 +++----- 15 files changed, 1125 insertions(+), 1122 deletions(-) create mode 100644 message.go delete mode 100644 models.go create mode 100644 schemes/schemes.go diff --git a/README.md b/README.md index 1c3f3d2..60124ec 100644 --- a/README.md +++ b/README.md @@ -19,66 +19,9 @@ ## Пример -```go -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - - "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) - ctx, cancel := context.WithCancel(context.Background()) - go func() { - exit := make(chan os.Signal) - signal.Notify(exit, os.Kill, os.Interrupt) - <-exit - cancel() - }() - for upd := range api.GetUpdates(ctx) { // Чтение из канала с обновлениями - 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) - } - } -} -``` +* [Пример с отправкой фото](https://github.com/neonxp/tamtam/blob/master/examples/example.go) +* [Пример с longpolling](https://github.com/neonxp/tamtam/blob/master/examples/example_longpolling.go) +* [Пример с webhook](https://github.com/neonxp/tamtam/blob/master/examples/example_webhook.go) ## Автор diff --git a/api.go b/api.go index aa91a48..4be13e3 100644 --- a/api.go +++ b/api.go @@ -11,6 +11,8 @@ import ( "net/url" "strconv" "time" + + "github.com/neonxp/tamtam/schemes" ) //Api implements main part of TamTam API @@ -42,106 +44,106 @@ func New(key string) *Api { } } -func (a *Api) bytesToProperUpdate(b []byte) UpdateInterface { - u := new(Update) +func (a *Api) bytesToProperUpdate(b []byte) schemes.UpdateInterface { + u := new(schemes.Update) _ = json.Unmarshal(b, u) switch u.GetUpdateType() { - case TypeMessageCallback: - upd := new(MessageCallbackUpdate) + case schemes.TypeMessageCallback: + upd := new(schemes.MessageCallbackUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeMessageCreated: - upd := new(MessageCreatedUpdate) + case schemes.TypeMessageCreated: + upd := new(schemes.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 TypeMessageRemoved: - upd := new(MessageRemovedUpdate) + case schemes.TypeMessageRemoved: + upd := new(schemes.MessageRemovedUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeMessageEdited: - upd := new(MessageEditedUpdate) + case schemes.TypeMessageEdited: + upd := new(schemes.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 TypeBotAdded: - upd := new(BotAddedToChatUpdate) + case schemes.TypeBotAdded: + upd := new(schemes.BotAddedToChatUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeBotRemoved: - upd := new(BotRemovedFromChatUpdate) + case schemes.TypeBotRemoved: + upd := new(schemes.BotRemovedFromChatUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeUserAdded: - upd := new(UserAddedToChatUpdate) + case schemes.TypeUserAdded: + upd := new(schemes.UserAddedToChatUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeUserRemoved: - upd := new(UserRemovedFromChatUpdate) + case schemes.TypeUserRemoved: + upd := new(schemes.UserRemovedFromChatUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeBotStarted: - upd := new(BotStartedUpdate) + case schemes.TypeBotStarted: + upd := new(schemes.BotStartedUpdate) _ = json.Unmarshal(b, upd) return upd - case TypeChatTitleChanged: - upd := new(ChatTitleChangedUpdate) + case schemes.TypeChatTitleChanged: + upd := new(schemes.ChatTitleChangedUpdate) _ = json.Unmarshal(b, upd) return upd } return nil } -func (a *Api) bytesToProperAttachment(b []byte) AttachmentInterface { - attachment := new(Attachment) +func (a *Api) bytesToProperAttachment(b []byte) schemes.AttachmentInterface { + attachment := new(schemes.Attachment) _ = json.Unmarshal(b, attachment) switch attachment.GetAttachmentType() { - case AttachmentAudio: - res := new(AudioAttachment) + case schemes.AttachmentAudio: + res := new(schemes.AudioAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentContact: - res := new(ContactAttachment) + case schemes.AttachmentContact: + res := new(schemes.ContactAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentFile: - res := new(FileAttachment) + case schemes.AttachmentFile: + res := new(schemes.FileAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentImage: - res := new(PhotoAttachment) + case schemes.AttachmentImage: + res := new(schemes.PhotoAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentKeyboard: - res := new(InlineKeyboardAttachment) + case schemes.AttachmentKeyboard: + res := new(schemes.InlineKeyboardAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentLocation: - res := new(LocationAttachment) + case schemes.AttachmentLocation: + res := new(schemes.LocationAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentShare: - res := new(ShareAttachment) + case schemes.AttachmentShare: + res := new(schemes.ShareAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentSticker: - res := new(StickerAttachment) + case schemes.AttachmentSticker: + res := new(schemes.StickerAttachment) _ = json.Unmarshal(b, res) return res - case AttachmentVideo: - res := new(VideoAttachment) + case schemes.AttachmentVideo: + res := new(schemes.VideoAttachment) _ = json.Unmarshal(b, res) return res } return attachment } -func (a *Api) getUpdates(limit int, timeout int, marker int, types []string) (*UpdateList, error) { - result := new(UpdateList) +func (a *Api) getUpdates(limit int, timeout int, marker int, types []string) (*schemes.UpdateList, error) { + result := new(schemes.UpdateList) values := url.Values{} if limit > 0 { values.Set("limit", strconv.Itoa(limit)) @@ -171,8 +173,8 @@ func (a *Api) getUpdates(limit int, timeout int, marker int, types []string) (*U } //GetUpdates returns updates channel -func (a *Api) GetUpdates(ctx context.Context) chan UpdateInterface { - ch := make(chan UpdateInterface) +func (a *Api) GetUpdates(ctx context.Context) chan schemes.UpdateInterface { + ch := make(chan schemes.UpdateInterface) go func() { for { select { diff --git a/bots.go b/bots.go index 582f07c..ea43ddb 100644 --- a/bots.go +++ b/bots.go @@ -5,6 +5,8 @@ import ( "log" "net/http" "net/url" + + "github.com/neonxp/tamtam/schemes" ) type bots struct { @@ -16,8 +18,8 @@ func newBots(client *client) *bots { } //GetBot returns info about current bot. Current bot can be identified by access token. Method returns bot identifier, name and avatar (if any) -func (a *bots) GetBot() (*BotInfo, error) { - result := new(BotInfo) +func (a *bots) GetBot() (*schemes.BotInfo, error) { + result := new(schemes.BotInfo) values := url.Values{} body, err := a.client.request(http.MethodGet, "me", values, nil) if err != nil { @@ -32,8 +34,8 @@ func (a *bots) GetBot() (*BotInfo, error) { } //PatchBot edits current bot info. Fill only the fields you want to update. All remaining fields will stay untouched -func (a *bots) PatchBot(patch *BotPatch) (*BotInfo, error) { - result := new(BotInfo) +func (a *bots) PatchBot(patch *schemes.BotPatch) (*schemes.BotInfo, error) { + result := new(schemes.BotInfo) values := url.Values{} body, err := a.client.request(http.MethodPatch, "me", values, patch) if err != nil { diff --git a/chats.go b/chats.go index adb0c07..414b4f4 100644 --- a/chats.go +++ b/chats.go @@ -7,6 +7,8 @@ import ( "net/http" "net/url" "strconv" + + "github.com/neonxp/tamtam/schemes" ) type chats struct { @@ -18,8 +20,8 @@ func newChats(client *client) *chats { } //GetChats returns information about chats that bot participated in: a result list and marker points to the next page -func (a *chats) GetChats(count, marker int) (*ChatList, error) { - result := new(ChatList) +func (a *chats) GetChats(count, marker int) (*schemes.ChatList, error) { + result := new(schemes.ChatList) values := url.Values{} if count > 0 { values.Set("count", strconv.Itoa(int(count))) @@ -40,8 +42,8 @@ func (a *chats) GetChats(count, marker int) (*ChatList, error) { } //GetChat returns info about chat -func (a *chats) GetChat(chatID int) (*Chat, error) { - result := new(Chat) +func (a *chats) GetChat(chatID int) (*schemes.Chat, error) { + result := new(schemes.Chat) values := url.Values{} body, err := a.client.request(http.MethodGet, fmt.Sprintf("chats/%d", chatID), values, nil) if err != nil { @@ -56,8 +58,8 @@ func (a *chats) GetChat(chatID int) (*Chat, error) { } //GetChatMembership returns chat membership info for current bot -func (a *chats) GetChatMembership(chatID int) (*ChatMember, error) { - result := new(ChatMember) +func (a *chats) GetChatMembership(chatID int) (*schemes.ChatMember, error) { + result := new(schemes.ChatMember) values := url.Values{} body, err := a.client.request(http.MethodGet, fmt.Sprintf("chats/%d/members/me", chatID), values, nil) if err != nil { @@ -72,8 +74,8 @@ func (a *chats) GetChatMembership(chatID int) (*ChatMember, error) { } //GetChatMembers returns users participated in chat -func (a *chats) GetChatMembers(chatID, count, marker int) (*ChatMembersList, error) { - result := new(ChatMembersList) +func (a *chats) GetChatMembers(chatID, count, marker int) (*schemes.ChatMembersList, error) { + result := new(schemes.ChatMembersList) values := url.Values{} if count > 0 { values.Set("count", strconv.Itoa(int(count))) @@ -94,8 +96,8 @@ func (a *chats) GetChatMembers(chatID, count, marker int) (*ChatMembersList, err } //LeaveChat removes bot from chat members -func (a *chats) LeaveChat(chatID int) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *chats) LeaveChat(chatID int) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} body, err := a.client.request(http.MethodDelete, fmt.Sprintf("chats/%d/members/me", chatID), values, nil) if err != nil { @@ -110,8 +112,8 @@ func (a *chats) LeaveChat(chatID int) (*SimpleQueryResult, error) { } //EditChat edits chat info: title, icon, etc… -func (a *chats) EditChat(chatID int, update *ChatPatch) (*Chat, error) { - result := new(Chat) +func (a *chats) EditChat(chatID int, update *schemes.ChatPatch) (*schemes.Chat, error) { + result := new(schemes.Chat) values := url.Values{} body, err := a.client.request(http.MethodPatch, fmt.Sprintf("chats/%d", chatID), values, update) if err != nil { @@ -126,8 +128,8 @@ func (a *chats) EditChat(chatID int, update *ChatPatch) (*Chat, error) { } //AddMember adds members to chat. Additional permissions may require. -func (a *chats) AddMember(chatID int, users UserIdsList) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *chats) AddMember(chatID int, users schemes.UserIdsList) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} body, err := a.client.request(http.MethodPost, fmt.Sprintf("chats/%d/members", chatID), values, users) if err != nil { @@ -142,8 +144,8 @@ func (a *chats) AddMember(chatID int, users UserIdsList) (*SimpleQueryResult, er } //RemoveMember removes member from chat. Additional permissions may require. -func (a *chats) RemoveMember(chatID int, userID int) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *chats) RemoveMember(chatID int, userID int) (*schemes.SimpleQueryResult, error) { + result := new(schemes.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) @@ -159,10 +161,10 @@ func (a *chats) RemoveMember(chatID int, userID int) (*SimpleQueryResult, error) } //SendAction send bot action to chat -func (a *chats) SendAction(chatID int, action SenderAction) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *chats) SendAction(chatID int, action schemes.SenderAction) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} - body, err := a.client.request(http.MethodPost, fmt.Sprintf("chats/%d/actions", chatID), values, ActionRequestBody{Action: action}) + body, err := a.client.request(http.MethodPost, fmt.Sprintf("chats/%d/actions", chatID), values, schemes.ActionRequestBody{Action: action}) if err != nil { return result, err } diff --git a/client.go b/client.go index 7c918b9..83551fb 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,8 @@ import ( "io" "net/http" "net/url" + + "github.com/neonxp/tamtam/schemes" ) type client struct { @@ -42,7 +44,7 @@ func (cl *client) requestReader(method, path string, query url.Values, body io.R return nil, err } if resp.StatusCode != http.StatusOK { - errObj := new(Error) + errObj := new(schemes.Error) err = json.NewDecoder(resp.Body).Decode(errObj) if err != nil { return nil, err diff --git a/examples/example.go b/examples/example.go index b769e8a..d6b7ab4 100644 --- a/examples/example.go +++ b/examples/example.go @@ -2,12 +2,12 @@ package main import ( "context" - "fmt" "log" "os" "os/signal" "github.com/neonxp/tamtam" + "github.com/neonxp/tamtam/schemes" ) func main() { @@ -25,7 +25,7 @@ func main() { for upd := range api.GetUpdates(ctx) { // Чтение из канала с обновлениями log.Printf("Received: %#v", upd) switch upd := upd.(type) { // Определение типа пришедшего обновления - case *tamtam.MessageCreatedUpdate: + case *schemes.MessageCreatedUpdate: // Создание клавиатуры keyboard := api.Messages.NewKeyboardBuilder() keyboard. @@ -34,46 +34,27 @@ func main() { AddContact("Прислать контакт") keyboard. AddRow(). - AddLink("Библиотека", tamtam.POSITIVE, "https://github.com/neonxp/tamtam"). - AddCallback("Колбек 1", tamtam.NEGATIVE, "callback_1"). - AddCallback("Колбек 2", tamtam.NEGATIVE, "callback_2") + AddLink("Библиотека", schemes.POSITIVE, "https://github.com/neonxp/tamtam"). + AddCallback("Колбек 1", schemes.NEGATIVE, "callback_1"). + AddCallback("Колбек 2", schemes.NEGATIVE, "callback_2") keyboard. AddRow(). - AddCallback("Картинка", tamtam.POSITIVE, "picture") + AddCallback("Картинка", schemes.POSITIVE, "picture") // Отправка сообщения с клавиатурой - 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: + err := api.Messages.Send(tamtam.NewMessage().SetUser(upd.Message.Sender.UserId).AddKeyboard(keyboard)) + log.Printf("Answer: %#v", err) + case *schemes.MessageCallbackUpdate: // Ответ на коллбек - attachments := make([]interface{}, 0) if upd.Callback.Payload == "picture" { - photo, err := api.Uploads.UploadPhoto("./examples/example.jpg") + photo, err := api.Uploads.UploadPhotoFromFile("./examples/example.jpg") if err != nil { log.Fatal(err) } - attachments = append(attachments, tamtam.NewPhotoAttachmentRequest(tamtam.PhotoAttachmentRequestPayload{Photos: photo.Photos})) + if err := api.Messages.Send(tamtam.NewMessage().SetUser(upd.Message.Sender.UserId).AddPhoto(photo)); err != nil { + log.Fatal(err) + } } - res, err := api.Messages.AnswerOnCallback( - upd.Callback.CallbackID, - &tamtam.CallbackAnswer{ - UserId: upd.Callback.User.UserId, - Message: &tamtam.NewMessageBody{ - Text: "OK!", - }, - Notification: "Callback is ok", - }) - log.Printf("Answer: %#v %#v", res, err) - res2, err := api.Messages.SendMessage(0, upd.Callback.User.UserId, &tamtam.NewMessageBody{ - Text: upd.Callback.Payload + " at " + upd.GetUpdateTime().String(), - Attachments: attachments, - }) - log.Printf("Answer: %#v %#v", res2, err) default: log.Printf("Unknown type: %#v", upd) } diff --git a/examples/example_longpolling.go b/examples/example_longpolling.go index 3dbc66b..6bb4727 100644 --- a/examples/example_longpolling.go +++ b/examples/example_longpolling.go @@ -29,9 +29,11 @@ func main() { log.Printf("Received: %#v", upd) switch upd := upd.(type) { 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), - }) + err := api.Messages.Send( + tamtam.NewMessage(). + SetUser(upd.Message.Sender.UserId). + SetText(fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text)), + ) log.Printf("Answer: %#v %#v", res, err) default: log.Printf("Unknown type: %#v", upd) diff --git a/examples/example_webhook.go b/examples/example_webhook.go index ad1bdae..b4857c1 100644 --- a/examples/example_webhook.go +++ b/examples/example_webhook.go @@ -39,10 +39,12 @@ func main() { log.Printf("Received: %#v", upd) switch upd := upd.(type) { 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) + err := api.Messages.Send( + tamtam.NewMessage(). + SetUser(upd.Message.Sender.UserId). + SetText(fmt.Sprintf("Hello, %s! Your message: %s", upd.Message.Sender.Name, upd.Message.Body.Text)), + ) + log.Printf("Answer: %#v", err) default: log.Printf("Unknown type: %#v", upd) } diff --git a/keyboard.go b/keyboard.go index 5d38bd2..5259b7a 100644 --- a/keyboard.go +++ b/keyboard.go @@ -1,43 +1,45 @@ package tamtam -//KeyboardBuilder implements builder for inline keyboard -type KeyboardBuilder struct { +import "github.com/neonxp/tamtam/schemes" + +//Keyboard implements builder for inline keyboard +type Keyboard struct { rows []*KeyboardRow } //AddRow adds row to inline keyboard -func (k *KeyboardBuilder) AddRow() *KeyboardRow { +func (k *Keyboard) AddRow() *KeyboardRow { kr := &KeyboardRow{} k.rows = append(k.rows, kr) return kr } //Build returns result keyboard -func (k *KeyboardBuilder) Build() Keyboard { - buttons := make([][]ButtonInterface, 0, len(k.rows)) +func (k *Keyboard) Build() schemes.Keyboard { + buttons := make([][]schemes.ButtonInterface, 0, len(k.rows)) for _, r := range k.rows { buttons = append(buttons, r.Build()) } - return Keyboard{Buttons: buttons} + return schemes.Keyboard{Buttons: buttons} } //KeyboardRow represents buttons row type KeyboardRow struct { - cols []ButtonInterface + cols []schemes.ButtonInterface } //Build returns result keyboard row -func (k *KeyboardRow) Build() []ButtonInterface { +func (k *KeyboardRow) Build() []schemes.ButtonInterface { return k.cols } //AddLink button -func (k *KeyboardRow) AddLink(text string, intent Intent, url string) *KeyboardRow { - b := LinkButton{ +func (k *KeyboardRow) AddLink(text string, intent schemes.Intent, url string) *KeyboardRow { + b := schemes.LinkButton{ Url: url, - Button: Button{ + Button: schemes.Button{ Text: text, - Type: LINK, + Type: schemes.LINK, }, } k.cols = append(k.cols, b) @@ -45,13 +47,13 @@ func (k *KeyboardRow) AddLink(text string, intent Intent, url string) *KeyboardR } //AddCallback button -func (k *KeyboardRow) AddCallback(text string, intent Intent, payload string) *KeyboardRow { - b := CallbackButton{ +func (k *KeyboardRow) AddCallback(text string, intent schemes.Intent, payload string) *KeyboardRow { + b := schemes.CallbackButton{ Payload: payload, Intent: intent, - Button: Button{ + Button: schemes.Button{ Text: text, - Type: CALLBACK, + Type: schemes.CALLBACK, }, } k.cols = append(k.cols, b) @@ -60,10 +62,10 @@ func (k *KeyboardRow) AddCallback(text string, intent Intent, payload string) *K //AddContact button func (k *KeyboardRow) AddContact(text string) *KeyboardRow { - b := RequestContactButton{ - Button: Button{ + b := schemes.RequestContactButton{ + Button: schemes.Button{ Text: text, - Type: CONTACT, + Type: schemes.CONTACT, }, } k.cols = append(k.cols, b) @@ -72,11 +74,11 @@ func (k *KeyboardRow) AddContact(text string) *KeyboardRow { //AddGeolocation button func (k *KeyboardRow) AddGeolocation(text string, quick bool) *KeyboardRow { - b := RequestGeoLocationButton{ + b := schemes.RequestGeoLocationButton{ Quick: quick, - Button: Button{ + Button: schemes.Button{ Text: text, - Type: GEOLOCATION, + Type: schemes.GEOLOCATION, }, } k.cols = append(k.cols, b) diff --git a/message.go b/message.go new file mode 100644 index 0000000..20e0ff4 --- /dev/null +++ b/message.go @@ -0,0 +1,81 @@ +package tamtam + +import "github.com/neonxp/tamtam/schemes" + +type Message struct { + userID int + chatID int + message *schemes.NewMessageBody +} + +func NewMessage() *Message { + return &Message{userID: 0, chatID: 0, message: &schemes.NewMessageBody{Attachments: []interface{}{}}} +} + +func (m *Message) SetUser(userID int) *Message { + m.userID = userID + return m +} +func (m *Message) SetChat(chatID int) *Message { + m.chatID = chatID + return m +} + +func (m *Message) SetText(text string) *Message { + m.message.Text = text + return m +} + +func (m *Message) SetNotify(notify bool) *Message { + m.message.Notify = notify + return m +} + +func (m *Message) AddKeyboard(keyboard *Keyboard) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewInlineKeyboardAttachmentRequest(keyboard.Build())) + return m +} + +func (m *Message) AddPhoto(photo *schemes.PhotoTokens) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewPhotoAttachmentRequest(schemes.PhotoAttachmentRequestPayload{ + Photos: photo.Photos, + })) + return m +} + +func (m *Message) AddAudio(audio *schemes.UploadedInfo) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewAudioAttachmentRequest(*audio)) + return m +} + +func (m *Message) AddVideo(video *schemes.UploadedInfo) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewVideoAttachmentRequest(*video)) + return m +} + +func (m *Message) AddFile(file *schemes.UploadedInfo) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewFileAttachmentRequest(*file)) + return m +} + +func (m *Message) AddLocation(lat float64, lon float64) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewLocationAttachmentRequest(lat, lon)) + return m +} + +func (m *Message) AddContact(name string, contactID int, vcfInfo string, vcfPhone string) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewContactAttachmentRequest(schemes.ContactAttachmentRequestPayload{ + Name: name, + ContactId: contactID, + VcfInfo: vcfInfo, + VcfPhone: vcfPhone, + })) + return m +} + +func (m *Message) AddSticker(code string) *Message { + m.message.Attachments = append(m.message.Attachments, schemes.NewStickerAttachmentRequest(schemes.StickerAttachmentRequestPayload{ + Code: code, + })) + return m +} diff --git a/messages.go b/messages.go index 5131479..7c20e05 100644 --- a/messages.go +++ b/messages.go @@ -2,10 +2,13 @@ package tamtam import ( "encoding/json" + "errors" "log" "net/http" "net/url" "strconv" + + "github.com/neonxp/tamtam/schemes" ) type messages struct { @@ -17,8 +20,8 @@ func newMessages(client *client) *messages { } //GetMessages returns messages in chat: result page and marker referencing to the next page. Messages traversed in reverse direction so the latest message in chat will be first in result array. Therefore if you use from and to parameters, to must be less than from -func (a *messages) GetMessages(chatID int, messageIDs []string, from int, to int, count int) (*MessageList, error) { - result := new(MessageList) +func (a *messages) GetMessages(chatID int, messageIDs []string, from int, to int, count int) (*schemes.MessageList, error) { + result := new(schemes.MessageList) values := url.Values{} if chatID != 0 { values.Set("chat_id", strconv.Itoa(int(chatID))) @@ -49,17 +52,24 @@ func (a *messages) GetMessages(chatID int, messageIDs []string, from int, to int return result, json.NewDecoder(body).Decode(result) } -//SendMessage sends a message to a chat. As a result for this method new message identifier returns. -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))) +//EditMessage updates message by id +func (a *messages) EditMessage(messageID int, message *Message) error { + s, err := a.editMessage(messageID, message.message) + if err != nil { + return err } - if userID != 0 { - values.Set("user_id", strconv.Itoa(int(userID))) + if !s.Success { + return errors.New(s.Message) } - body, err := a.client.request(http.MethodPost, "messages", values, message) + return nil +} + +//DeleteMessage deletes message by id +func (a *messages) DeleteMessage(messageID int) (*schemes.SimpleQueryResult, error) { + result := new(schemes.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 } @@ -71,12 +81,12 @@ func (a *messages) SendMessage(chatID int, userID int, message *NewMessageBody) return result, json.NewDecoder(body).Decode(result) } -//EditMessage updates message by id -func (a *messages) EditMessage(messageID int, message *NewMessageBody) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +//AnswerOnCallback should be called to send an answer after a user has clicked the button. The answer may be an updated message or/and a one-time user notification. +func (a *messages) AnswerOnCallback(callbackID string, callback *schemes.CallbackAnswer) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} - values.Set("message_id", strconv.Itoa(int(messageID))) - body, err := a.client.request(http.MethodPut, "messages", values, message) + values.Set("callback_id", callbackID) + body, err := a.client.request(http.MethodPost, "answers", values, callback) if err != nil { return result, err } @@ -88,29 +98,46 @@ func (a *messages) EditMessage(messageID int, message *NewMessageBody) (*SimpleQ return result, json.NewDecoder(body).Decode(result) } -//DeleteMessage deletes message by id -func (a *messages) DeleteMessage(messageID int) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +//NewKeyboardBuilder returns new keyboard builder helper +func (a *messages) NewKeyboardBuilder() *Keyboard { + return &Keyboard{ + rows: make([]*KeyboardRow, 0), + } +} + +//Send sends a message to a chat. As a result for this method new message identifier returns. +func (a *messages) Send(m *Message) error { + return a.sendMessage(m.chatID, m.userID, m.message) +} + +func (a *messages) sendMessage(chatID int, userID int, message *schemes.NewMessageBody) error { + result := new(schemes.Error) values := url.Values{} - values.Set("message_id", strconv.Itoa(int(messageID))) - body, err := a.client.request(http.MethodDelete, "messages", values, nil) + 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 + return err } - defer func() { - if err := body.Close(); err != nil { - log.Println(err) - } - }() - return result, json.NewDecoder(body).Decode(result) + defer body.Close() + if err := json.NewDecoder(body).Decode(result); err != nil { + return err + } + if result.Code == "" { + return nil + } + return result } -//AnswerOnCallback should be called to send an answer after a user has clicked the button. The answer may be an updated message or/and a one-time user notification. -func (a *messages) AnswerOnCallback(callbackID string, callback *CallbackAnswer) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *messages) editMessage(messageID int, message *schemes.NewMessageBody) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} - values.Set("callback_id", callbackID) - body, err := a.client.request(http.MethodPost, "answers", values, callback) + values.Set("message_id", strconv.Itoa(int(messageID))) + body, err := a.client.request(http.MethodPut, "messages", values, message) if err != nil { return result, err } @@ -121,10 +148,3 @@ func (a *messages) AnswerOnCallback(callbackID string, callback *CallbackAnswer) }() return result, json.NewDecoder(body).Decode(result) } - -//NewKeyboardBuilder returns new keyboard builder helper -func (a *messages) NewKeyboardBuilder() *KeyboardBuilder { - return &KeyboardBuilder{ - rows: make([]*KeyboardRow, 0), - } -} diff --git a/models.go b/models.go deleted file mode 100644 index 5870cb9..0000000 --- a/models.go +++ /dev/null @@ -1,788 +0,0 @@ -package tamtam - -import ( - "encoding/json" - "time" -) - -type ActionRequestBody struct { - Action SenderAction `json:"action"` -} - -type AttachmentType string - -const ( - AttachmentImage AttachmentType = "image" - AttachmentVideo = "video" - AttachmentAudio = "audio" - AttachmentFile = "file" - AttachmentContact = "contact" - AttachmentSticker = "sticker" - AttachmentShare = "share" - AttachmentLocation = "location" - AttachmentKeyboard = "inline_keyboard" -) - -// Generic schema representing message attachment -type Attachment struct { - Type AttachmentType `json:"type"` -} - -func (a Attachment) GetAttachmentType() AttachmentType { - return a.Type -} - -type AttachmentInterface interface { - GetAttachmentType() AttachmentType -} - -type AttachmentPayload struct { - // Media attachment URL - Url string `json:"url"` -} - -// Request to attach some data to message -type AttachmentRequest struct { - Type AttachmentType `json:"type"` -} - -type AudioAttachment struct { - Attachment - Payload MediaAttachmentPayload `json:"payload"` -} - -// Request to attach audio to message. MUST be the only attachment in message -type AudioAttachmentRequest struct { - AttachmentRequest - Payload UploadedInfo `json:"payload"` -} - -func NewAudioAttachmentRequest(payload UploadedInfo) *AudioAttachmentRequest { - return &AudioAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentAudio}} -} - -type BotCommand struct { - Name string `json:"name"` // Command name - Description string `json:"description,omitempty"` // Optional command description -} - -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 ButtonType `json:"type"` - Text string `json:"text"` // Visible text of button -} - -func (b Button) GetType() ButtonType { - return b.Type -} - -func (b Button) GetText() string { - return b.Text -} - -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 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 { - 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 { - 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 -type ChatAdminPermission string - -// List of ChatAdminPermission -const ( - READ_ALL_MESSAGES ChatAdminPermission = "read_all_messages" - ADD_REMOVE_MEMBERS ChatAdminPermission = "add_remove_members" - ADD_ADMINS ChatAdminPermission = "add_admins" - CHANGE_CHAT_INFO ChatAdminPermission = "change_chat_info" - PIN_MESSAGE ChatAdminPermission = "pin_message" - WRITE ChatAdminPermission = "write" -) - -type ChatList struct { - Chats []Chat `json:"chats"` // List of requested chats - Marker *int `json:"marker"` // Reference to the next page of requested chats -} - -type ChatMember 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 - 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 { - 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"` -} - -// ChatStatus : Chat status for current bots -type ChatStatus string - -// List of ChatStatus -const ( - ACTIVE ChatStatus = "active" - REMOVED ChatStatus = "removed" - LEFT ChatStatus = "left" - CLOSED ChatStatus = "closed" - SUSPENDED ChatStatus = "suspended" -) - -// ChatType : Type of chat. Dialog (one-on-one), chat or channel -type ChatType string - -// List of ChatType -const ( - DIALOG ChatType = "dialog" - CHAT ChatType = "chat" - CHANNEL ChatType = "channel" -) - -type ContactAttachment struct { - Attachment - Payload ContactAttachmentPayload `json:"payload"` -} - -type ContactAttachmentPayload struct { - 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 { - AttachmentRequest - Payload ContactAttachmentRequestPayload `json:"payload"` -} - -func NewContactAttachmentRequest(payload ContactAttachmentRequestPayload) *ContactAttachmentRequest { - return &ContactAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentContact}} -} - -type ContactAttachmentRequestPayload struct { - 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 { - ErrorText string `json:"error,omitempty"` // Error - Code string `json:"code"` // Error code - Message string `json:"message"` // Human-readable description -} - -func (e Error) Error() string { - return e.ErrorText -} - -type FileAttachment struct { - 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 { - 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 { - Subscriptions []Subscription `json:"subscriptions"` // Current subscriptions -} - -// Generic schema describing image object -type Image struct { - Url string `json:"url"` // URL of image -} - -// Buttons in messages -type InlineKeyboardAttachment struct { - Attachment - CallbackId string `json:"callback_id"` // Unique identifier of keyboard - Payload Keyboard `json:"payload"` -} - -// Request to attach keyboard to message -type InlineKeyboardAttachmentRequest struct { - AttachmentRequest - Payload Keyboard `json:"payload"` -} - -func NewInlineKeyboardAttachmentRequest(payload Keyboard) *InlineKeyboardAttachmentRequest { - return &InlineKeyboardAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentKeyboard}} -} - -type ButtonType string - -const ( - LINK ButtonType = "link" - CALLBACK ButtonType = "callback" - CONTACT ButtonType = "request_contact" - GEOLOCATION ButtonType = "request_geo_location" -) - -// Intent : Intent of button -type Intent string - -// List of Intent -const ( - POSITIVE Intent = "positive" - NEGATIVE Intent = "negative" - DEFAULT Intent = "default" -) - -// Keyboard is two-dimension array of buttons -type Keyboard struct { - Buttons [][]ButtonInterface `json:"buttons"` -} - -// After pressing this type of button user follows the link it contains -type LinkButton struct { - Button - Url string `json:"url"` -} - -type LinkedMessage struct { - 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 { - Attachment - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` -} - -// Request to attach keyboard to message -type LocationAttachmentRequest struct { - 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 { - 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 { - 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 -} - -type UpdateType string - -const ( - TypeMessageCallback UpdateType = "message_callback" - TypeMessageCreated UpdateType = "message_created" - TypeMessageRemoved UpdateType = "message_removed" - TypeMessageEdited UpdateType = "message_edited" - TypeBotAdded UpdateType = "bot_added" - TypeBotRemoved UpdateType = "bot_removed" - TypeUserAdded UpdateType = "user_added" - TypeUserRemoved UpdateType = "user_removed" - TypeBotStarted UpdateType = "bot_started" - TypeChatTitleChanged UpdateType = "chat_title_changed" -) - -// MessageLinkType : Type of linked message -type MessageLinkType string - -// List of MessageLinkType -const ( - FORWARD MessageLinkType = "forward" - REPLY MessageLinkType = "reply" -) - -// Paginated list of messages -type MessageList struct { - Messages []Message `json:"messages"` // List of messages -} - -// Message statistics -type MessageStat struct { - Views int `json:"views"` -} - -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 NewMessageLink struct { - Type MessageLinkType `json:"type"` // Type of message link - Mid string `json:"mid"` // Message identifier of original message -} - -// Image attachment -type PhotoAttachment struct { - Attachment - Payload PhotoAttachmentPayload `json:"payload"` -} - -type PhotoAttachmentPayload struct { - PhotoId int `json:"photo_id"` // Unique identifier of this image - Token string `json:"token"` - Url string `json:"url"` // Image URL -} - -type PhotoAttachmentRequest struct { - AttachmentRequest - Payload PhotoAttachmentRequestPayload `json:"payload"` -} - -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 { - 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 { - Token string `json:"token"` // Encoded information of uploaded image -} - -// This is information you will receive as soon as an image uploaded -type PhotoTokens struct { - Photos map[string]PhotoToken `json:"photos"` -} - -// New message recipient. Could be user or chat -type Recipient struct { - 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 current user contact -type RequestContactButton struct { - Button -} - -// After pressing this type of button client sends new message with attachment of current user geo location -type RequestGeoLocationButton struct { - Button - Quick bool `json:"quick,omitempty"` // If *true*, sends location without asking user's confirmation -} - -type SendMessageResult struct { - Message Message `json:"message"` -} - -// SenderAction : Different actions to send to chat members -type SenderAction string - -// List of SenderAction -const ( - TYPING_ON SenderAction = "typing_on" - TYPING_OFF SenderAction = "typing_off" - SENDING_PHOTO SenderAction = "sending_photo" - SENDING_VIDEO SenderAction = "sending_video" - SENDING_AUDIO SenderAction = "sending_audio" - MARK_SEEN SenderAction = "mark_seen" -) - -type ShareAttachment struct { - Attachment - Payload AttachmentPayload `json:"payload"` -} - -// Simple response to request -type SimpleQueryResult struct { - 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 { - 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 { - AttachmentRequest - Payload StickerAttachmentRequestPayload `json:"payload"` -} - -func NewStickerAttachmentRequest(payload StickerAttachmentRequestPayload) *StickerAttachmentRequest { - return &StickerAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentSticker}} -} - -type StickerAttachmentRequestPayload struct { - Code string `json:"code"` // Sticker code -} - -// Schema to describe WebHook subscription -type Subscription struct { - 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 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 -} - -// 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 ( - PHOTO UploadType = "photo" - VIDEO UploadType = "video" - AUDIO UploadType = "audio" - FILE UploadType = "file" -) - -// This is information you will receive as soon as audio/video is uploaded -type UploadedInfo struct { - FileID int `json:"file_id,omitempty"` - Token string `json:"token,omitempty"` // Token is unique uploaded media identfier -} - -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 UserIdsList struct { - UserIds []int `json:"user_ids"` -} - -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 VideoAttachment struct { - Attachment - Payload MediaAttachmentPayload `json:"payload"` -} - -// Request to attach video to message -type VideoAttachmentRequest struct { - AttachmentRequest - Payload UploadedInfo `json:"payload"` -} - -func NewVideoAttachmentRequest(payload UploadedInfo) *VideoAttachmentRequest { - return &VideoAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentVideo}} -} - -// `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 -} - -func (u Update) GetUpdateType() UpdateType { - return u.UpdateType -} - -func (u Update) GetUpdateTime() time.Time { - return time.Unix(int64(u.Timestamp/1000), 0) -} - -type UpdateInterface interface { - GetUpdateType() UpdateType - GetUpdateTime() time.Time - GetUserID() int - GetChatID() int -} - -// You will receive this update when bots has been added to chat -type BotAddedToChatUpdate struct { - Update - ChatId int `json:"chat_id"` // Chat id where bots was added - User User `json:"user"` // User who added bots to chat -} - -func (b BotAddedToChatUpdate) GetUserID() int { - return b.User.UserId -} - -func (b BotAddedToChatUpdate) GetChatID() int { - return b.ChatId -} - -// 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 -} - -func (b BotRemovedFromChatUpdate) GetUserID() int { - return b.User.UserId -} - -func (b BotRemovedFromChatUpdate) GetChatID() int { - return b.ChatId -} - -// 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 -} - -func (b BotStartedUpdate) GetUserID() int { - return b.User.UserId -} - -func (b BotStartedUpdate) GetChatID() int { - return b.ChatId -} - -// 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 -} - -func (b Callback) GetUserID() int { - return b.User.UserId -} - -func (b Callback) GetChatID() int { - return 0 -} - -// 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 -} - -func (b ChatTitleChangedUpdate) GetUserID() int { - return b.User.UserId -} - -func (b ChatTitleChangedUpdate) GetChatID() int { - return b.ChatId -} - -// 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 -} - -func (b MessageCallbackUpdate) GetUserID() int { - return b.Callback.User.UserId -} - -func (b MessageCallbackUpdate) GetChatID() int { - return 0 -} - -// You will get this `update` as soon as message is created -type MessageCreatedUpdate struct { - Update - Message Message `json:"message"` // Newly created message -} - -func (b MessageCreatedUpdate) GetUserID() int { - return b.Message.Sender.UserId -} - -func (b MessageCreatedUpdate) GetChatID() int { - return b.Message.Recipient.ChatId -} - -// You will get this `update` as soon as message is edited -type MessageEditedUpdate struct { - Update - Message Message `json:"message"` // Edited message -} - -func (b MessageEditedUpdate) GetUserID() int { - return b.Message.Sender.UserId -} - -func (b MessageEditedUpdate) GetChatID() int { - return b.Message.Recipient.ChatId -} - -// You will get this `update` as soon as message is removed -type MessageRemovedUpdate struct { - Update - MessageId string `json:"message_id"` // Identifier of removed message -} - -func (b MessageRemovedUpdate) GetUserID() int { - return 0 -} - -func (b MessageRemovedUpdate) GetChatID() int { - return 0 -} - -// 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 -} - -func (b UserAddedToChatUpdate) GetUserID() int { - return b.User.UserId -} - -func (b UserAddedToChatUpdate) GetChatID() int { - return b.ChatId -} - -// 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 -} - -func (b UserRemovedFromChatUpdate) GetUserID() int { - return b.User.UserId -} - -func (b UserRemovedFromChatUpdate) GetChatID() int { - return b.ChatId -} diff --git a/schemes/schemes.go b/schemes/schemes.go new file mode 100644 index 0000000..2f81487 --- /dev/null +++ b/schemes/schemes.go @@ -0,0 +1,788 @@ +package schemes + +import ( + "encoding/json" + "time" +) + +type ActionRequestBody struct { + Action SenderAction `json:"action"` +} + +type AttachmentType string + +const ( + AttachmentImage AttachmentType = "image" + AttachmentVideo = "video" + AttachmentAudio = "audio" + AttachmentFile = "file" + AttachmentContact = "contact" + AttachmentSticker = "sticker" + AttachmentShare = "share" + AttachmentLocation = "location" + AttachmentKeyboard = "inline_keyboard" +) + +// Generic schema representing message attachment +type Attachment struct { + Type AttachmentType `json:"type"` +} + +func (a Attachment) GetAttachmentType() AttachmentType { + return a.Type +} + +type AttachmentInterface interface { + GetAttachmentType() AttachmentType +} + +type AttachmentPayload struct { + // Media attachment URL + Url string `json:"url"` +} + +// Request to attach some data to message +type AttachmentRequest struct { + Type AttachmentType `json:"type"` +} + +type AudioAttachment struct { + Attachment + Payload MediaAttachmentPayload `json:"payload"` +} + +// Request to attach audio to message. MUST be the only attachment in message +type AudioAttachmentRequest struct { + AttachmentRequest + Payload UploadedInfo `json:"payload"` +} + +func NewAudioAttachmentRequest(payload UploadedInfo) *AudioAttachmentRequest { + return &AudioAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentAudio}} +} + +type BotCommand struct { + Name string `json:"name"` // Command name + Description string `json:"description,omitempty"` // Optional command description +} + +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 ButtonType `json:"type"` + Text string `json:"text"` // Visible text of button +} + +func (b Button) GetType() ButtonType { + return b.Type +} + +func (b Button) GetText() string { + return b.Text +} + +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 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 { + 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 { + 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 +type ChatAdminPermission string + +// List of ChatAdminPermission +const ( + READ_ALL_MESSAGES ChatAdminPermission = "read_all_messages" + ADD_REMOVE_MEMBERS ChatAdminPermission = "add_remove_members" + ADD_ADMINS ChatAdminPermission = "add_admins" + CHANGE_CHAT_INFO ChatAdminPermission = "change_chat_info" + PIN_MESSAGE ChatAdminPermission = "pin_message" + WRITE ChatAdminPermission = "write" +) + +type ChatList struct { + Chats []Chat `json:"chats"` // List of requested chats + Marker *int `json:"marker"` // Reference to the next page of requested chats +} + +type ChatMember 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 + 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 { + 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"` +} + +// ChatStatus : Chat status for current bots +type ChatStatus string + +// List of ChatStatus +const ( + ACTIVE ChatStatus = "active" + REMOVED ChatStatus = "removed" + LEFT ChatStatus = "left" + CLOSED ChatStatus = "closed" + SUSPENDED ChatStatus = "suspended" +) + +// ChatType : Type of chat. Dialog (one-on-one), chat or channel +type ChatType string + +// List of ChatType +const ( + DIALOG ChatType = "dialog" + CHAT ChatType = "chat" + CHANNEL ChatType = "channel" +) + +type ContactAttachment struct { + Attachment + Payload ContactAttachmentPayload `json:"payload"` +} + +type ContactAttachmentPayload struct { + 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 { + AttachmentRequest + Payload ContactAttachmentRequestPayload `json:"payload"` +} + +func NewContactAttachmentRequest(payload ContactAttachmentRequestPayload) *ContactAttachmentRequest { + return &ContactAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentContact}} +} + +type ContactAttachmentRequestPayload struct { + 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 { + ErrorText string `json:"error,omitempty"` // Error + Code string `json:"code"` // Error code + Message string `json:"message"` // Human-readable description +} + +func (e Error) Error() string { + return e.ErrorText +} + +type FileAttachment struct { + 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 { + 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 { + Subscriptions []Subscription `json:"subscriptions"` // Current subscriptions +} + +// Generic schema describing image object +type Image struct { + Url string `json:"url"` // URL of image +} + +// Buttons in messages +type InlineKeyboardAttachment struct { + Attachment + CallbackId string `json:"callback_id"` // Unique identifier of keyboard + Payload Keyboard `json:"payload"` +} + +// Request to attach keyboard to message +type InlineKeyboardAttachmentRequest struct { + AttachmentRequest + Payload Keyboard `json:"payload"` +} + +func NewInlineKeyboardAttachmentRequest(payload Keyboard) *InlineKeyboardAttachmentRequest { + return &InlineKeyboardAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentKeyboard}} +} + +type ButtonType string + +const ( + LINK ButtonType = "link" + CALLBACK ButtonType = "callback" + CONTACT ButtonType = "request_contact" + GEOLOCATION ButtonType = "request_geo_location" +) + +// Intent : Intent of button +type Intent string + +// List of Intent +const ( + POSITIVE Intent = "positive" + NEGATIVE Intent = "negative" + DEFAULT Intent = "default" +) + +// Keyboard is two-dimension array of buttons +type Keyboard struct { + Buttons [][]ButtonInterface `json:"buttons"` +} + +// After pressing this type of button user follows the link it contains +type LinkButton struct { + Button + Url string `json:"url"` +} + +type LinkedMessage struct { + 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 { + Attachment + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// Request to attach keyboard to message +type LocationAttachmentRequest struct { + 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 { + 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 { + 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 +} + +type UpdateType string + +const ( + TypeMessageCallback UpdateType = "message_callback" + TypeMessageCreated UpdateType = "message_created" + TypeMessageRemoved UpdateType = "message_removed" + TypeMessageEdited UpdateType = "message_edited" + TypeBotAdded UpdateType = "bot_added" + TypeBotRemoved UpdateType = "bot_removed" + TypeUserAdded UpdateType = "user_added" + TypeUserRemoved UpdateType = "user_removed" + TypeBotStarted UpdateType = "bot_started" + TypeChatTitleChanged UpdateType = "chat_title_changed" +) + +// MessageLinkType : Type of linked message +type MessageLinkType string + +// List of MessageLinkType +const ( + FORWARD MessageLinkType = "forward" + REPLY MessageLinkType = "reply" +) + +// Paginated list of messages +type MessageList struct { + Messages []Message `json:"messages"` // List of messages +} + +// Message statistics +type MessageStat struct { + Views int `json:"views"` +} + +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 NewMessageLink struct { + Type MessageLinkType `json:"type"` // Type of message link + Mid string `json:"mid"` // Message identifier of original message +} + +// Image attachment +type PhotoAttachment struct { + Attachment + Payload PhotoAttachmentPayload `json:"payload"` +} + +type PhotoAttachmentPayload struct { + PhotoId int `json:"photo_id"` // Unique identifier of this image + Token string `json:"token"` + Url string `json:"url"` // Image URL +} + +type PhotoAttachmentRequest struct { + AttachmentRequest + Payload PhotoAttachmentRequestPayload `json:"payload"` +} + +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 { + 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 { + Token string `json:"token"` // Encoded information of uploaded image +} + +// This is information you will receive as soon as an image uploaded +type PhotoTokens struct { + Photos map[string]PhotoToken `json:"photos"` +} + +// New message recipient. Could be user or chat +type Recipient struct { + 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 current user contact +type RequestContactButton struct { + Button +} + +// After pressing this type of button client sends new message with attachment of current user geo location +type RequestGeoLocationButton struct { + Button + Quick bool `json:"quick,omitempty"` // If *true*, sends location without asking user's confirmation +} + +type SendMessageResult struct { + Message Message `json:"message"` +} + +// SenderAction : Different actions to send to chat members +type SenderAction string + +// List of SenderAction +const ( + TYPING_ON SenderAction = "typing_on" + TYPING_OFF SenderAction = "typing_off" + SENDING_PHOTO SenderAction = "sending_photo" + SENDING_VIDEO SenderAction = "sending_video" + SENDING_AUDIO SenderAction = "sending_audio" + MARK_SEEN SenderAction = "mark_seen" +) + +type ShareAttachment struct { + Attachment + Payload AttachmentPayload `json:"payload"` +} + +// Simple response to request +type SimpleQueryResult struct { + 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 { + 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 { + AttachmentRequest + Payload StickerAttachmentRequestPayload `json:"payload"` +} + +func NewStickerAttachmentRequest(payload StickerAttachmentRequestPayload) *StickerAttachmentRequest { + return &StickerAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentSticker}} +} + +type StickerAttachmentRequestPayload struct { + Code string `json:"code"` // Sticker code +} + +// Schema to describe WebHook subscription +type Subscription struct { + 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 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 +} + +// 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 ( + PHOTO UploadType = "photo" + VIDEO UploadType = "video" + AUDIO UploadType = "audio" + FILE UploadType = "file" +) + +// This is information you will receive as soon as audio/video is uploaded +type UploadedInfo struct { + FileID int `json:"file_id,omitempty"` + Token string `json:"token,omitempty"` // Token is unique uploaded media identfier +} + +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 UserIdsList struct { + UserIds []int `json:"user_ids"` +} + +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 VideoAttachment struct { + Attachment + Payload MediaAttachmentPayload `json:"payload"` +} + +// Request to attach video to message +type VideoAttachmentRequest struct { + AttachmentRequest + Payload UploadedInfo `json:"payload"` +} + +func NewVideoAttachmentRequest(payload UploadedInfo) *VideoAttachmentRequest { + return &VideoAttachmentRequest{Payload: payload, AttachmentRequest: AttachmentRequest{Type: AttachmentVideo}} +} + +// `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 +} + +func (u Update) GetUpdateType() UpdateType { + return u.UpdateType +} + +func (u Update) GetUpdateTime() time.Time { + return time.Unix(int64(u.Timestamp/1000), 0) +} + +type UpdateInterface interface { + GetUpdateType() UpdateType + GetUpdateTime() time.Time + GetUserID() int + GetChatID() int +} + +// You will receive this update when bots has been added to chat +type BotAddedToChatUpdate struct { + Update + ChatId int `json:"chat_id"` // Chat id where bots was added + User User `json:"user"` // User who added bots to chat +} + +func (b BotAddedToChatUpdate) GetUserID() int { + return b.User.UserId +} + +func (b BotAddedToChatUpdate) GetChatID() int { + return b.ChatId +} + +// 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 +} + +func (b BotRemovedFromChatUpdate) GetUserID() int { + return b.User.UserId +} + +func (b BotRemovedFromChatUpdate) GetChatID() int { + return b.ChatId +} + +// 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 +} + +func (b BotStartedUpdate) GetUserID() int { + return b.User.UserId +} + +func (b BotStartedUpdate) GetChatID() int { + return b.ChatId +} + +// 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 +} + +func (b Callback) GetUserID() int { + return b.User.UserId +} + +func (b Callback) GetChatID() int { + return 0 +} + +// 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 +} + +func (b ChatTitleChangedUpdate) GetUserID() int { + return b.User.UserId +} + +func (b ChatTitleChangedUpdate) GetChatID() int { + return b.ChatId +} + +// 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 +} + +func (b MessageCallbackUpdate) GetUserID() int { + return b.Callback.User.UserId +} + +func (b MessageCallbackUpdate) GetChatID() int { + return 0 +} + +// You will get this `update` as soon as message is created +type MessageCreatedUpdate struct { + Update + Message Message `json:"message"` // Newly created message +} + +func (b MessageCreatedUpdate) GetUserID() int { + return b.Message.Sender.UserId +} + +func (b MessageCreatedUpdate) GetChatID() int { + return b.Message.Recipient.ChatId +} + +// You will get this `update` as soon as message is edited +type MessageEditedUpdate struct { + Update + Message Message `json:"message"` // Edited message +} + +func (b MessageEditedUpdate) GetUserID() int { + return b.Message.Sender.UserId +} + +func (b MessageEditedUpdate) GetChatID() int { + return b.Message.Recipient.ChatId +} + +// You will get this `update` as soon as message is removed +type MessageRemovedUpdate struct { + Update + MessageId string `json:"message_id"` // Identifier of removed message +} + +func (b MessageRemovedUpdate) GetUserID() int { + return 0 +} + +func (b MessageRemovedUpdate) GetChatID() int { + return 0 +} + +// 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 +} + +func (b UserAddedToChatUpdate) GetUserID() int { + return b.User.UserId +} + +func (b UserAddedToChatUpdate) GetChatID() int { + return b.ChatId +} + +// 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 +} + +func (b UserRemovedFromChatUpdate) GetUserID() int { + return b.User.UserId +} + +func (b UserRemovedFromChatUpdate) GetChatID() int { + return b.ChatId +} diff --git a/subscriptions.go b/subscriptions.go index ec97adc..1090183 100644 --- a/subscriptions.go +++ b/subscriptions.go @@ -5,6 +5,8 @@ import ( "log" "net/http" "net/url" + + "github.com/neonxp/tamtam/schemes" ) type subscriptions struct { @@ -16,8 +18,8 @@ func newSubscriptions(client *client) *subscriptions { } //GetSubscriptions returns list of all subscriptions -func (a *subscriptions) GetSubscriptions() (*GetSubscriptionsResult, error) { - result := new(GetSubscriptionsResult) +func (a *subscriptions) GetSubscriptions() (*schemes.GetSubscriptionsResult, error) { + result := new(schemes.GetSubscriptionsResult) values := url.Values{} body, err := a.client.request(http.MethodGet, "subscriptions", values, nil) if err != nil { @@ -32,13 +34,13 @@ func (a *subscriptions) GetSubscriptions() (*GetSubscriptionsResult, error) { } //Subscribe subscribes bot to receive updates via WebHook -func (a *subscriptions) Subscribe(subscribeURL string, updateTypes []string) (*SimpleQueryResult, error) { - subscription := &SubscriptionRequestBody{ +func (a *subscriptions) Subscribe(subscribeURL string, updateTypes []string) (*schemes.SimpleQueryResult, error) { + subscription := &schemes.SubscriptionRequestBody{ Url: subscribeURL, UpdateTypes: updateTypes, Version: a.client.version, } - result := new(SimpleQueryResult) + result := new(schemes.SimpleQueryResult) values := url.Values{} body, err := a.client.request(http.MethodPost, "subscriptions", values, subscription) if err != nil { @@ -53,8 +55,8 @@ func (a *subscriptions) Subscribe(subscribeURL string, updateTypes []string) (*S } //Unsubscribe unsubscribes bot from receiving updates via WebHook -func (a *subscriptions) Unsubscribe(subscriptionURL string) (*SimpleQueryResult, error) { - result := new(SimpleQueryResult) +func (a *subscriptions) Unsubscribe(subscriptionURL string) (*schemes.SimpleQueryResult, error) { + result := new(schemes.SimpleQueryResult) values := url.Values{} values.Set("url", subscriptionURL) body, err := a.client.request(http.MethodDelete, "subscriptions", values, nil) diff --git a/uploads.go b/uploads.go index b6f30dc..51b5a40 100644 --- a/uploads.go +++ b/uploads.go @@ -9,7 +9,8 @@ import ( "net/http" "net/url" "os" - "path" + + "github.com/neonxp/tamtam/schemes" ) type uploads struct { @@ -20,145 +21,106 @@ func newUploads(client *client) *uploads { return &uploads{client: client} } -//GetUploadURL returns url to upload files -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) -} - //UploadMedia uploads file to TamTam server -func (a *uploads) UploadMedia(endpoint *UploadEndpoint, filename string) (*UploadedInfo, error) { - bodyBuf := &bytes.Buffer{} - bodyWriter := multipart.NewWriter(bodyBuf) - - fileWriter, err := bodyWriter.CreateFormFile("data", filename) - if err != nil { - return nil, err - } - +func (a *uploads) UploadMediaFromFile(uploadType schemes.UploadType, filename string) (*schemes.UploadedInfo, error) { 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 - } - contentType := bodyWriter.FormDataContentType() - resp, err := http.Post(endpoint.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) + defer fh.Close() + return a.UploadMediaFromReader(uploadType, fh) } //UploadMediaFromUrl uploads file from remote server to TamTam server -func (a *uploads) UploadMediaFromUrl(endpoint *UploadEndpoint, u url.URL) (*UploadedInfo, error) { +func (a *uploads) UploadMediaFromUrl(uploadType schemes.UploadType, u url.URL) (*schemes.UploadedInfo, error) { respFile, err := http.Get(u.String()) if err != nil { return nil, err } defer respFile.Body.Close() - bodyBuf := &bytes.Buffer{} - bodyWriter := multipart.NewWriter(bodyBuf) - fileWriter, err := bodyWriter.CreateFormFile("data", path.Base(u.Path)) + return a.UploadMediaFromReader(uploadType, respFile.Body) +} + +func (a *uploads) UploadMediaFromReader(uploadType schemes.UploadType, reader io.Reader) (*schemes.UploadedInfo, error) { + result := new(schemes.UploadedInfo) + return result, a.uploadMediaFromReader(uploadType, reader, result) +} + +//UploadPhotoFromFile uploads photos to TamTam server +func (a *uploads) UploadPhotoFromFile(filename string) (*schemes.PhotoTokens, error) { + fh, err := os.Open(filename) if err != nil { return nil, err } - _, err = io.Copy(fileWriter, respFile.Body) + defer fh.Close() + result := new(schemes.PhotoTokens) + return result, a.uploadMediaFromReader(schemes.PHOTO, fh, result) +} + +//UploadPhotoFromUrl uploads photo from remote server to TamTam server +func (a *uploads) UploadPhotoFromUrl(u url.URL) (*schemes.PhotoTokens, error) { + respFile, err := http.Get(u.String()) if err != nil { return nil, err } + defer respFile.Body.Close() + result := new(schemes.PhotoTokens) + return result, a.uploadMediaFromReader(schemes.PHOTO, respFile.Body, result) +} - if err := bodyWriter.Close(); err != nil { - return nil, err - } - contentType := bodyWriter.FormDataContentType() - if err := bodyWriter.Close(); err != nil { - return nil, err - } - resp, err := http.Post(endpoint.Url, contentType, bodyBuf) +//UploadPhotoFromReader uploads photo from reader +func (a *uploads) UploadPhotoFromReader(reader io.Reader) (*schemes.PhotoTokens, error) { + result := new(schemes.PhotoTokens) + return result, a.uploadMediaFromReader(schemes.PHOTO, reader, result) +} + +func (a *uploads) getUploadURL(uploadType schemes.UploadType) (*schemes.UploadEndpoint, error) { + result := new(schemes.UploadEndpoint) + values := url.Values{} + values.Set("type", string(uploadType)) + body, err := a.client.request(http.MethodPost, "uploads", values, nil) if err != nil { - return nil, err + return result, err } defer func() { - if err := resp.Body.Close(); err != nil { + if err := body.Close(); err != nil { log.Println(err) } }() - result := new(UploadedInfo) - return result, json.NewDecoder(resp.Body).Decode(result) + return result, json.NewDecoder(body).Decode(result) } -//UploadPhoto uploads photos to TamTam server -func (a *uploads) UploadPhoto(filename string) (*PhotoTokens, error) { - bodyBuf := &bytes.Buffer{} - bodyWriter := multipart.NewWriter(bodyBuf) - - fileWriter, err := bodyWriter.CreateFormFile("data", filename) +func (a *uploads) uploadMediaFromReader(uploadType schemes.UploadType, reader io.Reader, result interface{}) error { + endpoint, err := a.getUploadURL(uploadType) if err != nil { - return nil, err + return err } - - fh, err := os.Open(filename) + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + fileWriter, err := bodyWriter.CreateFormFile("data", "file") if err != nil { - return nil, err + return err } - defer func() { - if err := fh.Close(); err != nil { - log.Println(err) - } - }() - _, err = io.Copy(fileWriter, fh) + _, err = io.Copy(fileWriter, reader) if err != nil { - return nil, err + return err } if err := bodyWriter.Close(); err != nil { - return nil, err - } - - endpoint, err := a.GetUploadURL(PHOTO) - if err != nil { - return nil, err + return err } contentType := bodyWriter.FormDataContentType() - + if err := bodyWriter.Close(); err != nil { + return err + } resp, err := http.Post(endpoint.Url, contentType, bodyBuf) if err != nil { - return nil, err + return err } defer func() { if err := resp.Body.Close(); err != nil { log.Println(err) } }() - result := new(PhotoTokens) - return result, json.NewDecoder(resp.Body).Decode(result) + return json.NewDecoder(resp.Body).Decode(result) } -- cgit v1.2.3