This commit is contained in:
Pavel 2021-10-14 18:25:30 +03:00
commit 646caf2cab
10 changed files with 298 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{less,css,yml,json}]
indent_size = 2
[{*.md,go.mod,go.sum}]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

4
.env.dist Normal file
View File

@ -0,0 +1,4 @@
API_URL=https://test.retailcrm.pro
API_KEY=key
MESSAGE_SCOPE=public
TEXT_OPTIONS="Text 1,Text 2,Text 3,Text 4,Text 5"

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
vendor
.env
main
.idea

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Neur0toxine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
This bot will automatically respond to any Simla message with preconfigured quick replies.
Build: `go build -o main ./...`
Usage: copy `.env.dist` to `.env` then replace placeholders with your data. After that you can run the bot.

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module bot-quick-replies
go 1.17
require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/retailcrm/api-client-go v1.3.8 // indirect
github.com/retailcrm/mg-bot-api-client-go v1.2.9 // indirect
)

22
go.sum Normal file
View File

@ -0,0 +1,22 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/retailcrm/api-client-go v1.3.8 h1:oWyxmm2YB2bEz/F+0XrP0k6QhOVv9gICKv4A/3sDWS0=
github.com/retailcrm/api-client-go v1.3.8/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c=
github.com/retailcrm/mg-bot-api-client-go v1.2.9 h1:eUDrD20ysNCb4oAOmbJ4BTkXcHKZKU4Fq+aVKOx7Ttc=
github.com/retailcrm/mg-bot-api-client-go v1.2.9/go.mod h1:kWDUiT5pvUtWZrb/mpkJDgddU5ezW8EuwduJKJ35lPs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/h2non/gock.v1 v1.1.0/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

56
main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"log"
"os"
"strings"
"github.com/joho/godotenv"
v1 "github.com/retailcrm/mg-bot-api-client-go/v1"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file:", err)
}
apiURL := os.Getenv("API_URL")
apiKey := os.Getenv("API_KEY")
if apiURL == "" {
log.Fatal("API_URL environment variable must be set")
}
if apiKey == "" {
log.Fatal("API_KEY environment variable must be set")
}
botCode := os.Getenv("BOT_CODE")
botName := os.Getenv("BOT_NAME")
msgScope := os.Getenv("MESSAGE_SCOPE")
options := os.Getenv("TEXT_OPTIONS")
if botCode == "" {
botCode = "test-quick-reply-bot"
}
if botName == "" {
botName = "Test Quick Reply Bot"
}
if msgScope == "" {
msgScope = v1.MessageScopePrivate
}
if options == "" {
options = "Text reply"
}
endpoint, token, err := updateIntegrationModule(apiURL, apiKey, botCode, botName)
if err != nil {
log.Fatal("Error updating integration module: ", err)
}
log.Println("Integration module has been updated. Endpoint: ", endpoint, ", token: ", token)
if err := NewWebsocketListener(endpoint, token, msgScope, strings.Split(options, ",")).Listen(); err != nil {
log.Fatal(err)
}
}

62
settings.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"errors"
"fmt"
"strings"
"github.com/retailcrm/api-client-go/errs"
"github.com/retailcrm/api-client-go/v5"
)
func buildIntegrationModule(code, name string) v5.IntegrationModule {
return v5.IntegrationModule{
Code: code,
IntegrationCode: code,
Active: true,
Name: name,
ClientID: code,
BaseURL: "https://example.com",
Integrations: &v5.Integrations{
MgBot: &v5.MgBot{},
},
}
}
func updateIntegrationModule(apiURL, apiKey, code, name string) (string, string, error) {
client := v5.New(apiURL, apiKey)
resp, _, err := client.IntegrationModuleEdit(buildIntegrationModule(code, name))
if err != nil {
if nErr := normalizeAPIError(err); nErr != nil {
return "", "", nErr
}
}
return resp.Info.MgBotInfo.EndpointUrl, resp.Info.MgBotInfo.Token, nil
}
func normalizeAPIError(err *errs.Failure) error {
if err == nil {
return nil
}
if err.Error() != "" {
return errors.New(err.Error())
}
if err.ApiError() != "" {
return errors.New(err.ApiError())
}
if len(err.ApiErrors()) > 0 {
var sb strings.Builder
sb.Grow(128)
for field, value := range err.ApiErrors() {
sb.WriteString(fmt.Sprintf("[%s: %s]", field, value))
}
return errors.New(sb.String())
}
return nil
}

97
websocket.go Normal file
View File

@ -0,0 +1,97 @@
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/gorilla/websocket"
v1 "github.com/retailcrm/mg-bot-api-client-go/v1"
)
type WebsocketListener struct {
mg *v1.MgClient
ws *websocket.Conn
scope string
suggestions []v1.Suggestion
}
func NewWebsocketListener(endpoint, token, scope string, textOptions []string) *WebsocketListener {
suggestions := []v1.Suggestion{
{
Type: v1.SuggestionTypePhone,
Title: "Phone",
},
{
Type: v1.SuggestionTypeEmail,
Title: "E-Mail",
},
}
for _, s := range textOptions {
suggestions = append(suggestions, v1.Suggestion{
Type: v1.SuggestionTypeText,
Title: s,
})
}
return &WebsocketListener{
mg: v1.New(endpoint, token),
scope: scope,
suggestions: suggestions,
}
}
func (l *WebsocketListener) Listen() error {
data, header, err := l.mg.WsMeta([]string{v1.WsEventMessageNew})
if err != nil {
return fmt.Errorf("cannot get meta for connection: %w", err)
}
ws, _, err := websocket.DefaultDialer.Dial(data, header)
if err != nil {
return fmt.Errorf("cannot estabilish WebSocket connection to %s: %w", data, err)
}
log.Println("Listening for the new messages...")
for {
var wsEvent v1.WsEvent
if err := ws.ReadJSON(&wsEvent); err != nil {
log.Fatal("unexpected websocket error:", err)
}
var event v1.WsEventMessageNewData
err = json.Unmarshal(wsEvent.Data, &event)
if err != nil {
log.Printf("cannot unmarshal payload: %s\n", err)
continue
}
if event.Message == nil {
log.Print("invalid payload - nil message")
continue
}
if event.Message.From != nil && event.Message.From.Type != "customer" {
continue
}
log.Printf("Received message from %s with id=%d\n", event.Message.From.Name, event.Message.ID)
_, _, err := l.mg.MessageSend(v1.MessageSendRequest{
Type: v1.MsgTypeText,
Content: "The quick brown fox jumps over the lazy dog.",
// Items: nil,
Scope: l.scope,
ChatID: event.Message.ChatID,
QuoteMessageId: event.Message.ID,
TransportAttachments: &v1.TransportAttachments{
Suggestions: l.suggestions,
},
})
if err != nil {
log.Printf("error: cannot respond to the message: %s\n", err)
}
}
}