mirror of
https://github.com/Neur0toxine/simla-bot-quickreplies-test.git
synced 2024-11-23 13:26:07 +03:00
initial
This commit is contained in:
commit
646caf2cab
18
.editorconfig
Normal file
18
.editorconfig
Normal 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
4
.env.dist
Normal 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
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
vendor
|
||||||
|
.env
|
||||||
|
main
|
||||||
|
.idea
|
21
LICENSE
Normal file
21
LICENSE
Normal 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
3
README.md
Normal 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
11
go.mod
Normal 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
22
go.sum
Normal 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
56
main.go
Normal 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
62
settings.go
Normal 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
97
websocket.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user