diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3ebba8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# IDE's files +.idea +*.iml +.env + +# Project ignores diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e7f2fd7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: go +go: + - '1.9' + - '1.10' + - '1.11' +before_install: + - go get -v github.com/google/go-querystring/query + - go get -v github.com/stretchr/testify/assert + - go get -v github.com/joho/godotenv + +script: go test -v ./... diff --git a/README.md b/README.md index 18cdbcb..2a9fc9f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ -# mg-bot-api-client-go -Go client for MG Bot API +[![Build Status](https://img.shields.io/travis/retailcrm/mg-bot-api-client-go/master.svg?style=flat-square)](https://travis-ci.org/retailcrm/mg-bot-api-client-go) +[![GitHub release](https://img.shields.io/github/release/retailcrm/mg-bot-api-client-go.svg?style=flat-square)](https://github.com/retailcrm/mg-bot-api-client-go/releases) +[![GoLang version](https://img.shields.io/badge/GoLang-1.9%2C%201.10%2C%201.11-blue.svg?style=flat-square)](https://golang.org/dl/) + + +# retailCRM Message Gateway Bot API Go client + +## Install + +```bash +go get -u -v github.com/retailcrm/mg-bot-api-client-go +``` + +## Usage + +```golang +package main + +import ( + "fmt" + "net/http" + + "github.com/retailcrm/mg-bot-api-client-go/v1" +) + +func main() { + var client = v1.New("https://token.url", "cb8ccf05e38a47543ad8477d49bcba99be73bff503ea6") + message := MessageSendRequest{ + Scope: "public", + Content: "test", + ChatID: 12, + } + + data, status, err := c.MessageSend(message) + if err != nil { + t.Errorf("%d %v", status, err) + } + + fmt.Printf("%v", data.MessageID) +} +``` + +## Documentation + +* [GoDoc](https://godoc.org/github.com/retailcrm/mg-bot-api-client-go) diff --git a/v1/client.go b/v1/client.go new file mode 100644 index 0000000..ddd0c95 --- /dev/null +++ b/v1/client.go @@ -0,0 +1,672 @@ +package v1 + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "github.com/google/go-querystring/query" +) + +// New initialize client +func New(url string, token string) *MgClient { + return &MgClient{ + URL: url, + Token: token, + httpClient: &http.Client{Timeout: 20 * time.Second}, + } +} + +// Bots get all available bots +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Bots() +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, bot := range data { +// fmt.Printf("%v %v\n", bot.Name, bot.CreatedAt) +// } +func (c *MgClient) Bots(request BotsRequest) ([]BotsResponseItem, int, error) { + var resp []BotsResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/bots?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Channels get all available channels +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Channels() +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, channel := range data { +// fmt.Printf("%v %v\n", channel.Type, channel.CreatedAt) +// } +func (c *MgClient) Channels(request ChannelsRequest) ([]ChannelResponseItem, int, error) { + var resp []ChannelResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/channels?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Users get all available users +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Users(UsersRequest:{Active:1}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, user := range data { +// fmt.Printf("%v %v\n", user.FirstName, user.IsOnline) +// } +func (c *MgClient) Users(request UsersRequest) ([]UsersResponseItem, int, error) { + var resp []UsersResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/users?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Customers get all available customers +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Customers() +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, customer := range data { +// fmt.Printf("%v %v\n", customer.FirstName, customer.Avatar) +// } +func (c *MgClient) Customers(request CustomersRequest) ([]CustomersResponseItem, int, error) { + var resp []CustomersResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/customers?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Chats get all available chats +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Chats(ChatsRequest{ChannelType:ChannelTypeWhatsapp}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, chat := range data { +// fmt.Printf("%v %v\n", chat.Customer, chat.LastMessage) +// } +func (c *MgClient) Chats(request ChatsRequest) ([]ChatResponseItem, int, error) { + var resp []ChatResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/chats?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Members get all available chat members +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Members(MembersRequest{State:ChatMemberStateActive}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, member := range data { +// fmt.Printf("%v\n", member.CreatedAt) +// } +func (c *MgClient) Members(request MembersRequest) ([]MemberResponseItem, int, error) { + var resp []MemberResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/members?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Dialogs get all available dialogs +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Dialogs(DialogsRequest{Active:1}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, dialog := range data { +// fmt.Printf("%v %v\n", dialog.ChatID, dialog.CreatedAt) +// } +func (c *MgClient) Dialogs(request DialogsRequest) ([]DialogResponseItem, int, error) { + var resp []DialogResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/dialogs?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) DialogAssign(request DialogAssignRequest) (DialogAssignResponse, int, error) { + var resp DialogAssignResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PatchRequest(fmt.Sprintf("/dialogs/%d/assign", request.DialogID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// DialogClose close selected dialog +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// _, status, err := client.DialogClose(123) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +func (c *MgClient) DialogClose(request uint64) (map[string]interface{}, int, error) { + var resp map[string]interface{} + outgoing, _ := json.Marshal(&request) + + data, status, err := c.DeleteRequest(fmt.Sprintf("/dialogs/%d/close", request), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Messages get all available messages +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Messages(MessagesRequest{ManagerID:5}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, message := range data { +// fmt.Printf("%v %v %v\n", message.ChatID, message.CreatedAt, message.CustomerID) +// } +func (c *MgClient) Messages(request MessagesRequest) ([]MessagesResponseItem, int, error) { + var resp []MessagesResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/messages?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// MessageSend send message +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.MessageSend(MessageSendRequest{ +// Scope: "public", +// Content: "test", +// ChatID: i, +// }) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// fmt.Printf("%v \n", data.MessageID, data.Time) +func (c *MgClient) MessageSend(request MessageSendRequest) (MessageSendResponse, int, error) { + var resp MessageSendResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PostRequest("/messages", []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// MessageEdit update selected message +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// _, status, err := client.MessageEdit(MessageEditRequest{ +// ID: 123, +// Content: "test", +// }) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +func (c *MgClient) MessageEdit(request MessageEditRequest) (map[string]interface{}, int, error) { + var resp map[string]interface{} + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PatchRequest(fmt.Sprintf("/messages/%d", request.ID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// MessageDelete delete selected message +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// _, status, err := client.MessageDelete(123) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +func (c *MgClient) MessageDelete(request uint64) (map[string]interface{}, int, error) { + var resp map[string]interface{} + outgoing, _ := json.Marshal(&request) + + data, status, err := c.DeleteRequest(fmt.Sprintf("/messages/%d", request), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Info updates bot information +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// _, status, err := client.Info(InfoRequest{Name: "AWESOME", Avatar: "https://example.com/logo.svg"}) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +func (c *MgClient) Info(request InfoRequest) (map[string]interface{}, int, error) { + var resp map[string]interface{} + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PatchRequest("/my/info", []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// Commands get all available commands for bot +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.Commands() +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// for _, command := range data { +// fmt.Printf("%v %v\n", command.Name, command.Description) +// } +func (c *MgClient) Commands(request CommandsRequest) ([]CommandsResponseItem, int, error) { + var resp []CommandsResponseItem + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/my/commands?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// CommandEdit create or change command for bot +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// data, status, err := client.CommandEdit(CommandEditRequest{ +// BotID: 1, +// Name: "show_payment_types", +// Description: "Get available payment types", +// }) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +// +// fmt.Printf("%v %v\n", data.Name, data.Description) +func (c *MgClient) CommandEdit(request CommandEditRequest) (CommandsResponseItem, int, error) { + var resp CommandsResponseItem + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PutRequest(fmt.Sprintf("/my/commands/%s", request.Name), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// CommandDelete delete selected command for bot +// +// Example: +// +// var client = v1.New("https://demo.url", "09jIJ") +// +// _, status, err := client.CommandDelete(show_payment_types) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// if status >= http.StatusBadRequest { +// fmt.Printf("%v", err) +// } +func (c *MgClient) CommandDelete(request string) (map[string]interface{}, int, error) { + var resp map[string]interface{} + outgoing, _ := json.Marshal(&request) + + data, status, err := c.DeleteRequest(fmt.Sprintf("/my/commands/%s", request), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Error(info []byte) error { + var data map[string]interface{} + + if err := json.Unmarshal(info, &data); err != nil { + return err + } + + values := data["errors"].([]interface{}) + + return errors.New(values[0].(string)) +} diff --git a/v1/client_test.go b/v1/client_test.go new file mode 100644 index 0000000..b641767 --- /dev/null +++ b/v1/client_test.go @@ -0,0 +1,318 @@ +package v1 + +import ( + "log" + "net/http" + "os" + "strconv" + "testing" + + "github.com/joho/godotenv" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + if os.Getenv("DEVELOPER_NODE") == "1" { + err := godotenv.Load("../.env") + if err != nil { + log.Fatal("Error loading .env file") + } + + os.Exit(m.Run()) + } +} + +var ( + mgURL = os.Getenv("MG_URL") + mgToken = os.Getenv("MG_BOT_TOKEN") +) + +func client() *MgClient { + return New(mgURL, mgToken) +} + +func TestMgClient_Bots(t *testing.T) { + c := client() + req := BotsRequest{Active: 1} + + data, status, err := c.Bots(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, bot := range data { + assert.NotEmpty(t, bot.CreatedAt) + } +} + +func TestMgClient_Channels(t *testing.T) { + c := client() + req := ChannelsRequest{Active: 1} + + data, status, err := c.Channels(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, channel := range data { + assert.NotEmpty(t, channel.Type) + } +} + +func TestMgClient_Users(t *testing.T) { + c := client() + req := UsersRequest{Active: 1} + + data, status, err := c.Users(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, user := range data { + assert.NotEmpty(t, user.FirstName) + } +} + +func TestMgClient_Customers(t *testing.T) { + c := client() + req := CustomersRequest{} + + data, status, err := c.Customers(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, customer := range data { + assert.NotEmpty(t, customer.ChannelId) + } +} + +func TestMgClient_Chats(t *testing.T) { + c := client() + req := ChatsRequest{ChannelType: ChannelTypeTelegram} + + data, status, err := c.Chats(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, chat := range data { + assert.NotEmpty(t, chat.Customer.Name) + } +} + +func TestMgClient_Members(t *testing.T) { + c := client() + req := MembersRequest{State: ChatMemberStateLeaved} + + data, status, err := c.Members(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, member := range data { + assert.NotEmpty(t, member.ChatID) + } +} + +func TestMgClient_Dialogs(t *testing.T) { + c := client() + req := DialogsRequest{Active: 0} + + data, status, err := c.Dialogs(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, dialog := range data { + assert.NotEmpty(t, dialog.ChatID) + } +} + +func TestMgClient_DialogAssign(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_DIALOG"), 10, 64) + m, err := strconv.ParseUint(os.Getenv("MG_BOT_USER"), 10, 64) + req := DialogAssignRequest{DialogID: i, ManagerID: m} + + _, status, err := c.DialogAssign(req) + + assert.Error(t, err) + assert.Equal(t, http.StatusBadRequest, status) +} + +func TestMgClient_DialogClose(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_DIALOG"), 10, 64) + _, status, err := c.DialogClose(i) + + assert.Error(t, err) + assert.Equal(t, http.StatusBadRequest, status) +} + +func TestMgClient_Messages(t *testing.T) { + c := client() + req := MessagesRequest{ChannelType: ChannelTypeTelegram, Scope: MessageScopePublic} + + data, status, err := c.Messages(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, message := range data { + assert.NotEmpty(t, message.Content) + } +} + +func TestMgClient_MessageSend(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_CHAT"), 10, 64) + message := MessageSendRequest{ + Scope: "public", + Content: "test", + ChatID: i, + } + + data, status, err := c.MessageSend(message) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data.MessageID) +} + +func TestMgClient_MessageEdit(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_CHAT"), 10, 64) + message := MessageSendRequest{ + Scope: "public", + Content: "test", + ChatID: i, + } + + s, status, err := c.MessageSend(message) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, s.MessageID) + + edit := MessageEditRequest{ + ID: s.MessageID, + Content: "test", + } + + e, status, err := c.MessageEdit(edit) + if err != nil { + t.Errorf("%d %v", status, err) + } + + t.Logf("Message edit: %v", e) +} + +func TestMgClient_MessageDelete(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_CHAT"), 10, 64) + message := MessageSendRequest{ + Scope: "public", + Content: "test", + ChatID: i, + } + + s, status, err := c.MessageSend(message) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, s.MessageID) + + d, status, err := c.MessageDelete(s.MessageID) + if err != nil { + t.Errorf("%d %v", status, err) + } + + t.Logf("Message delete: %v", d) +} + +func TestMgClient_Info(t *testing.T) { + c := client() + req := InfoRequest{Name: "AWESOME", Avatar: os.Getenv("MG_BOT_LOGO")} + + _, status, err := c.Info(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) +} + +func TestMgClient_Commands(t *testing.T) { + c := client() + req := CommandsRequest{} + + data, status, err := c.Commands(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, data) + + for _, command := range data { + assert.NotEmpty(t, command.Description) + } +} + +func TestMgClient_CommandEditDelete(t *testing.T) { + c := client() + i, err := strconv.ParseUint(os.Getenv("MG_BOT_ID"), 10, 64) + req := CommandEditRequest{ + BotID: i, + Name: "show_payment_types", + Description: "Get available payment types", + } + + n, status, err := c.CommandEdit(req) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + assert.NotEmpty(t, n.ID) + + d, status, err := c.CommandDelete(n.Name) + if err != nil { + t.Errorf("%d %v", status, err) + } + + assert.NoError(t, err) + t.Logf("%v", d) +} diff --git a/v1/request.go b/v1/request.go new file mode 100644 index 0000000..54f298e --- /dev/null +++ b/v1/request.go @@ -0,0 +1,108 @@ +package v1 + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +var prefix = "/api/bot/v1" + +// GetRequest implements GET Request +func (c *MgClient) GetRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "GET", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PostRequest implements POST Request +func (c *MgClient) PostRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "POST", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PatchRequest implements PATCH Request +func (c *MgClient) PatchRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "PATCH", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PutRequest implements PUT Request +func (c *MgClient) PutRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "PUT", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// DeleteRequest implements DELETE Request +func (c *MgClient) DeleteRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "DELETE", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +func makeRequest(reqType, url string, buf *bytes.Buffer, c *MgClient) ([]byte, int, error) { + var res []byte + req, err := http.NewRequest(reqType, url, buf) + if err != nil { + return res, 0, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Bot-Token", c.Token) + + if c.Debug { + log.Printf("MG BOT API Request: %s %s %s %s", reqType, url, c.Token, buf.String()) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return res, 0, err + } + + if resp.StatusCode >= http.StatusInternalServerError { + err = fmt.Errorf("http request error. Status code: %d", resp.StatusCode) + return res, resp.StatusCode, err + } + + res, err = buildRawResponse(resp) + if err != nil { + return res, 0, err + } + + if c.Debug { + log.Printf("MG BOT API Response: %s", res) + } + + return res, resp.StatusCode, err +} + +func buildRawResponse(resp *http.Response) ([]byte, error) { + defer resp.Body.Close() + + res, err := ioutil.ReadAll(resp.Body) + if err != nil { + return res, err + } + + return res, nil +} diff --git a/v1/types.go b/v1/types.go new file mode 100644 index 0000000..beea0d3 --- /dev/null +++ b/v1/types.go @@ -0,0 +1,358 @@ +package v1 + +import ( + "net/http" +) + +const ( + ChannelTypeTelegram string = "telegram" + ChannelTypeFacebook string = "fbmessenger" + ChannelTypeViber string = "viber" + ChannelTypeWhatsapp string = "whatsapp" + ChannelTypeSkype string = "skype" + ChannelTypeVk string = "vk" + ChannelTypeInstagram string = "instagram" + ChannelTypeConsultant string = "consultant" + ChannelTypeCustom string = "custom" + + ChatMemberStateActive string = "active" + ChatMemberStateKicked string = "kicked" + ChatMemberStateLeaved string = "leaved" + + MessageScopePublic string = "public" + MessageScopePrivate string = "private" + + BotEventMessageNew string = "message_new" + BotEventMessageUpdated string = "message_updated" + BotEventMessageDeleted string = "message_deleted" + BotEventDialogOpened string = "dialog_opened" + BotEventDialogClosed string = "dialog_closed" + BotEventDialogAssing string = "dialog_assign" + BotEventChatCreated string = "chat_created" + BotEventChatUpdated string = "chat_updated" + BotEventUserJoined string = "user_joined_chat" + BotEventUserLeave string = "user_left_chat" + BotEventUserUpdated string = "user_updated" +) + +// MgClient type +type MgClient struct { + URL string `json:"url"` + Token string `json:"token"` + Debug bool `json:"debug"` + httpClient *http.Client +} + +// Request types +type ( + BotsRequest struct { + ID uint64 `url:"id,omitempty"` + Self bool `url:"self,omitempty"` + Active uint8 `url:"active,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + ChannelsRequest struct { + ID uint64 `url:"id,omitempty"` + Types string `url:"types,omitempty"` + Active uint8 `url:"active,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + UsersRequest struct { + ID uint64 `url:"id,omitempty"` + ExternalID string `url:"external_id,omitempty" json:"external_id"` + Online uint8 `url:"online,omitempty"` + Active uint8 `url:"active,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + CustomersRequest struct { + ID uint64 `url:"id,omitempty"` + ExternalID string `url:"external_id,omitempty" json:"external_id"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + ChatsRequest struct { + ID uint64 `url:"id,omitempty"` + ChannelID uint64 `url:"channel_id,omitempty" json:"channel_id"` + ChannelType string `url:"channel_type,omitempty" json:"channel_type"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + MembersRequest struct { + ChatID uint64 `url:"chat_id,omitempty" json:"chat_id"` + ManagerID string `url:"manager_id,omitempty" json:"manager_id"` + CustomerID string `url:"customer_id,omitempty" json:"customer_id"` + State string `url:"state,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + DialogsRequest struct { + ID uint64 `url:"id,omitempty"` + ChatID string `url:"chat_id,omitempty" json:"chat_id"` + ManagerID string `url:"manager_id,omitempty" json:"manager_id"` + BotID string `url:"bot_id,omitempty" json:"bot_id"` + Assigned uint8 `url:"assigned,omitempty"` + Active uint8 `url:"active,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + DialogAssignRequest struct { + DialogID uint64 `url:"dialog_id,omitempty" json:"dialog_id"` + ManagerID uint64 `url:"manager_id,omitempty" json:"manager_id"` + BotID uint64 `url:"bot_id,omitempty" json:"bot_id"` + } + + MessagesRequest struct { + ID uint64 `url:"id,omitempty"` + ChatID uint64 `url:"chat_id,omitempty" json:"chat_id"` + DialogID uint64 `url:"dialog_id,omitempty" json:"dialog_id"` + ManagerID uint64 `url:"manager_id,omitempty" json:"manager_id"` + CustomerID uint64 `url:"customer_id,omitempty" json:"customer_id"` + BotID uint64 `url:"bot_id,omitempty" json:"bot_id"` + ChannelID uint64 `url:"channel_id,omitempty" json:"channel_id"` + ChannelType string `url:"channel_type,omitempty" json:"channel_type"` + Scope string `url:"scope,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + MessageSendRequest struct { + Content string `url:"content,omitempty" json:"content"` + Scope string `url:"scope,omitempty" json:"scope"` + ChatID uint64 `url:"chat_id,omitempty" json:"chat_id"` + QuoteMessageId uint64 `url:"quote_message_id,omitempty" json:"quote_message_id"` + } + + MessageEditRequest struct { + ID uint64 `url:"id,omitempty"` + Content string `url:"content,omitempty" json:"content"` + } + + InfoRequest struct { + Name string `url:"name,omitempty" json:"name"` + Avatar string `url:"avatar_url,omitempty" json:"avatar_url"` + Events []string `url:"events,omitempty" json:"events,brackets"` + } + + CommandsRequest struct { + ID uint64 `url:"id,omitempty"` + Name string `url:"name,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` + } + + CommandEditRequest struct { + BotID uint64 `url:"bot_id,omitempty" json:"bot_id"` + Name string `url:"name,omitempty" json:"name"` + Description string `url:"description,omitempty" json:"description"` + } +) + +// Response types +type ( + BotsResponseItem struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Events []string `json:"events,omitempty,brackets"` + ClientID string `json:"client_id,omitempty"` + AvatarUrl string `json:"avatar_url,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + DeactivatedAt string `json:"deactivated_at,omitempty"` + IsActive bool `json:"is_active"` + IsSelf bool `json:"is_self"` + } + + ChannelResponseItem struct { + ID uint64 `json:"id"` + Type string `json:"type"` + Events []string `json:"events,omitempty,brackets"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ActivatedAt string `json:"activated_at"` + DeactivatedAt string `json:"deactivated_at"` + IsActive bool `json:"is_active"` + } + + UsersResponseItem struct { + ID uint64 `json:"id"` + ExternalID string `json:"external_id,omitempty"` + Username string `json:"username,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at,omitempty"` + RevokedAt string `json:"revoked_at,omitempty"` + IsOnline bool `json:"is_online"` + IsActive bool `json:"is_active"` + Avatar string `json:"avatar_url,omitempty"` + } + + CustomersResponseItem struct { + ID uint64 `json:"id"` + ExternalID string `json:"external_id,omitempty"` + ChannelId uint64 `json:"channel_id,omitempty"` + Username string `json:"username,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at,omitempty"` + RevokedAt string `json:"revoked_at,omitempty"` + Avatar string `json:"avatar_url,omitempty"` + ProfileURL string `json:"profile_url,omitempty"` + Country string `json:"country,omitempty"` + Language string `json:"language,omitempty"` + Phone string `json:"phone,omitempty"` + } + + ChatResponseItem struct { + ID uint64 `json:"id"` + Avatar string `json:"avatar"` + Name string `json:"name"` + Channel Channel `json:"channel,omitempty"` + Customer UserRef `json:"customer"` + AuthorID uint64 `json:"author_id"` + LastMessage Message `json:"last_message"` + LastActivity string `json:"last_activity"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } + + MemberResponseItem struct { + ID uint64 `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at,omitempty"` + IsAuthor bool `json:"is_author"` + State string `json:"state"` + ChatID uint64 `json:"chat_id"` + UserID uint64 `json:"user_id"` + } + + DialogResponseItem struct { + ID uint64 `json:"id"` + ChatID uint64 `json:"chat_id"` + BeginMessageID uint64 `json:"begin_message_id,omitempty"` + EndingMessageID uint64 `json:"ending_message_id,omitempty"` + BotID uint64 `json:"bot_id,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at,omitempty"` + ClosedAt string `json:"closed_at,omitempty"` + IsAssigned bool `json:"is_assigned"` + Responsible Responsible `json:"responsible,omitempty"` + IsActive bool `json:"is_active"` + } + + DialogAssignResponse struct { + Responsible Responsible `json:"responsible"` + PreviousResponsible Responsible `json:"previous_responsible,omitempty"` + LeftManagerID uint64 `json:"left_manager_id,omitempty"` + IsReAssign bool `json:"is_reassign"` + } + + MessagesResponseItem struct { + Message + ChannelID uint64 `json:"channel_id,omitempty"` + ChannelSentAt string `json:"channel_sent_at,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } + + MessageSendResponse struct { + MessageID uint64 `json:"message_id"` + Time string `json:"time"` + } + + CommandsResponseItem struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at,omitempty"` + } +) + +// Single entity types +type ( + Message struct { + ID uint64 `json:"id"` + Time string `json:"time"` + Type string `json:"type"` + ChatID uint64 `json:"chat_id"` + IsRead bool `json:"is_read"` + Status string `json:"status"` + TextMessage + SystemMessage + } + + TextMessage struct { + Scope string `json:"scope"` + Content string `json:"content"` + From UserRef `json:"from"` + Quote QuoteMessage `json:"quote"` + IsEdit bool `json:"is_edit"` + Actions []string `json:"actions"` + } + + SystemMessage struct { + Action string `json:"action"` + Dialog SystemMessageDialog `json:"dialog,omitempty"` + User UserRef `json:"user,omitempty"` + } + + SystemMessageDialog struct { + ID uint64 `json:"id"` + } + + QuoteMessage struct { + ID uint64 `json:"id"` + Content string `json:"content"` + Time string `json:"time"` + From UserRef `json:"from"` + } + + UserRef struct { + ID uint64 `json:"id"` + Avatar string `json:"avatar"` + Type string `json:"type"` + Name string `json:"name"` + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + } + + Channel struct { + ID uint64 `json:"id"` + TransportID uint64 `json:"transport_id"` + Type string `json:"type"` + Supports ChannelSupports `json:"supports"` + } + + ChannelSupports struct { + Messages []string `json:"messages"` + Statuses []string `json:"statuses"` + } + + Responsible struct { + ID int64 `json:"id"` + Type string `json:"type"` + AssignAt string `json:"assigned_at"` + } + + Command struct { + ID uint64 + BotID uint64 + Name string + Description string + CreatedAt string + UpdatedAt string + } +)