1
0
mirror of synced 2024-11-23 12:36:02 +03:00

Merge pull request #44 from gwinn/release

Release
This commit is contained in:
Alex Lushpai 2018-09-21 20:06:56 +03:00 committed by GitHub
commit 3431bbe210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 397 additions and 75 deletions

2
go.mod
View File

@ -45,7 +45,7 @@ require (
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/retailcrm/api-client-go v1.0.6
github.com/retailcrm/mg-transport-api-client-go v1.1.9
github.com/retailcrm/mg-transport-api-client-go v1.1.10
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect

4
go.sum
View File

@ -95,8 +95,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/retailcrm/api-client-go v1.0.6 h1:4Q3e4ve8GOOHIQdq3/wTGqgWuWa1cKMKqmgrTv4FoDU=
github.com/retailcrm/api-client-go v1.0.6/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c=
github.com/retailcrm/mg-transport-api-client-go v1.1.9 h1:ogh5ThoqZJM5v4ZY6CqctUj01pVVHfBLXkrmX+BFjHE=
github.com/retailcrm/mg-transport-api-client-go v1.1.9/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
github.com/retailcrm/mg-transport-api-client-go v1.1.10 h1:RR8S5NA6FPVrF6UVXaLwu/gJyKUg5aUObQ97S98M3Yc=
github.com/retailcrm/mg-transport-api-client-go v1.1.10/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co=
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=

View File

@ -0,0 +1 @@
alter table bot drop column channel_settings_hash;

View File

@ -0,0 +1 @@
alter table bot add column channel_settings_hash varchar(70);

View File

@ -0,0 +1 @@
alter table bot drop column lang;

View File

@ -0,0 +1 @@
alter table bot add column lang varchar(2);

View File

@ -41,6 +41,13 @@ func getLocalizedMessage(messageID string) string {
return localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
}
func getLocalizedTemplateMessage(messageID string, templateData map[string]interface{}) string {
return localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: messageID,
TemplateData: templateData,
})
}
func getLocale() map[string]interface{} {
return map[string]interface{}{
"ButtonSave": getLocalizedMessage("button_save"),
@ -52,6 +59,7 @@ func getLocale() map[string]interface{} {
"AddBot": getLocalizedMessage("add_bot"),
"TableDelete": getLocalizedMessage("table_delete"),
"Title": getLocalizedMessage("title"),
"Language": getLocalizedMessage("language"),
"InfoBot": template.HTML(getLocalizedMessage("info_bot")),
"CRMLink": template.HTML(getLocalizedMessage("crm_link")),
"DocLink": template.HTML(getLocalizedMessage("doc_link")),

View File

@ -14,6 +14,8 @@ type Options struct {
Config string `short:"c" long:"config" default:"config.yml" description:"Path to configuration file"`
}
const Type = "telegram"
var (
config *TransportConfig
orm *Orm

View File

@ -21,8 +21,10 @@ type Bot struct {
ID int `gorm:"primary_key"`
ConnectionID int `gorm:"connection_id" json:"connectionId,omitempty"`
Channel uint64 `gorm:"channel;not null;unique" json:"channel,omitempty"`
ChannelSettingsHash string `gorm:"channel_settings_hash type:varchar(70)"`
Token string `gorm:"token type:varchar(100);not null;unique" json:"token,omitempty"`
Name string `gorm:"name type:varchar(40)" json:"name,omitempty"`
Lang string `gorm:"lang type:varchar(2)" json:"lang,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@ -13,6 +13,13 @@ func getConnection(uid string) *Connection {
return &connection
}
func getConnections() []*Connection {
var connection []*Connection
orm.DB.Find(&connection)
return connection
}
func getConnectionByURL(urlCrm string) *Connection {
var connection Connection
orm.DB.First(&connection, "api_url = ?", urlCrm)
@ -52,6 +59,10 @@ func getBotByToken(token string) (*Bot, error) {
return &bot, nil
}
func (b *Bot) save() error {
return orm.DB.Save(b).Error
}
func (b *Bot) deleteBot() error {
return orm.DB.Delete(b, "token = ?", b.Token).Error
}

View File

@ -58,36 +58,10 @@ func addBotHandler(c *gin.Context) {
}
b.Name = bot.Self.FirstName
ch := v1.Channel{
Type: "telegram",
Settings: v1.ChannelSettings{
SpamAllowed: false,
Status: v1.Status{
Delivered: v1.ChannelFeatureSend,
Read: v1.ChannelFeatureNone,
},
Text: v1.ChannelSettingsText{
Creating: v1.ChannelFeatureBoth,
Editing: v1.ChannelFeatureBoth,
Quoting: v1.ChannelFeatureBoth,
Deleting: v1.ChannelFeatureReceive,
},
Product: v1.Product{
Creating: v1.ChannelFeatureReceive,
Editing: v1.ChannelFeatureReceive,
},
Order: v1.Order{
Creating: v1.ChannelFeatureReceive,
Editing: v1.ChannelFeatureReceive,
},
},
}
conn := getConnectionById(b.ConnectionID)
client := v1.New(conn.MGURL, conn.MGToken)
var client = v1.New(conn.MGURL, conn.MGToken)
data, status, err := client.ActivateTransportChannel(ch)
data, status, err := client.ActivateTransportChannel(getChannelSettings())
if status != http.StatusCreated {
c.AbortWithStatusJSON(BadRequest("error_activating_channel"))
logger.Error(conn.APIURL, status, err.Error(), data)
@ -147,11 +121,13 @@ func settingsHandler(c *gin.Context) {
Bots Bots
Locale map[string]interface{}
Year int
LangCode []string
}{
p,
bots,
getLocale(),
time.Now().Year(),
[]string{"en", "ru", "es"},
}
c.HTML(http.StatusOK, "form", &res)
@ -272,6 +248,25 @@ func activityHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}
func setLangBotHandler(c *gin.Context) {
b := c.MustGet("bot").(Bot)
cl, err := getBotByToken(b.Token)
if err != nil {
c.Error(err)
return
}
cl.Lang = b.Lang
err = cl.save()
if err != nil {
c.Error(err)
return
}
c.JSON(http.StatusOK, gin.H{})
}
func getIntegrationModule(clientId string) v5.IntegrationModule {
return v5.IntegrationModule{
Code: config.TransportInfo.Code,
@ -305,6 +300,98 @@ func getIntegrationModule(clientId string) v5.IntegrationModule {
}
}
func getChannelSettings(cid ...uint64) v1.Channel {
var channelID uint64
if len(cid) > 0 {
channelID = cid[0]
}
return v1.Channel{
ID: channelID,
Type: Type,
Settings: v1.ChannelSettings{
SpamAllowed: false,
Status: v1.Status{
Delivered: v1.ChannelFeatureSend,
Read: v1.ChannelFeatureNone,
},
Text: v1.ChannelSettingsText{
Creating: v1.ChannelFeatureBoth,
Editing: v1.ChannelFeatureBoth,
Quoting: v1.ChannelFeatureBoth,
Deleting: v1.ChannelFeatureReceive,
},
Product: v1.Product{
Creating: v1.ChannelFeatureReceive,
Editing: v1.ChannelFeatureReceive,
},
Order: v1.Order{
Creating: v1.ChannelFeatureReceive,
Editing: v1.ChannelFeatureReceive,
},
},
}
}
func updateChannelsSettings() {
hashSettings, err := getChannelSettingsHash()
if err != nil {
logger.Error(err.Error())
return
}
connections := getConnections()
if len(connections) > 0 {
for _, conn := range connections {
if !conn.Active {
logger.Infof(
"updateChannelsSettings connection %s deactivated",
conn.APIURL,
)
continue
}
updateBots(conn, hashSettings)
}
}
return
}
func updateBots(conn *Connection, hashSettings string) {
bots := conn.getBotsByClientID()
if len(bots) > 0 {
client := v1.New(conn.MGURL, conn.MGToken)
for _, bot := range bots {
if bot.ChannelSettingsHash == hashSettings {
continue
}
data, status, err := client.UpdateTransportChannel(getChannelSettings(bot.Channel))
if config.Debug {
logger.Infof(
"updateChannelsSettings apiURL: %s, ChannelID: %d, Data: %v, Status: %d, err: %v",
conn.APIURL, bot.Channel, data, status, err,
)
}
if err == nil {
bot.ChannelSettingsHash = hashSettings
err = bot.save()
if err != nil {
logger.Error(
"updateChannelsSettings bot.save apiURL: %s, bot.Channel: %d , err: %v",
conn.APIURL, bot.Channel, err,
)
}
}
}
}
return
}
func telegramWebhookHandler(c *gin.Context) {
token := c.Param("token")
b, err := getBotByToken(token)
@ -493,17 +580,26 @@ func mgWebhookHandler(c *gin.Context) {
return
}
setLocale(b.Lang)
switch msg.Type {
case "message_sent":
var mb string
if msg.Data.Type == v1.MsgTypeProduct {
switch msg.Data.Type {
case v1.MsgTypeProduct:
mb = fmt.Sprintf("%s\n", msg.Data.Product.Name)
if msg.Data.Product.Cost != nil && msg.Data.Product.Cost.Value != 0 {
mb += fmt.Sprintf(
"\n%v %s\n",
msg.Data.Product.Cost.Value,
currency[strings.ToLower(msg.Data.Product.Cost.Currency)],
"\n%s: %s\n",
getLocalizedMessage("item_cost"),
getLocalizedTemplateMessage(
"cost_currency",
map[string]interface{}{
"Amount": msg.Data.Product.Cost.Value,
"Currency": currency[strings.ToLower(msg.Data.Product.Cost.Currency)],
},
),
)
}
@ -512,26 +608,9 @@ func mgWebhookHandler(c *gin.Context) {
} else {
mb += msg.Data.Product.Img
}
} else if msg.Data.Type == v1.MsgTypeOrder {
mb = "Заказ"
if msg.Data.Order.Number != "" {
mb += " " + msg.Data.Order.Number
}
if msg.Data.Order.Date != "" {
mb += fmt.Sprintf(" (%s)", msg.Data.Order.Date)
}
mb += "\n"
if len(msg.Data.Order.Items) > 0 {
for _, v := range msg.Data.Order.Items {
mb += fmt.Sprintf("%s %v x %v %s\n", v.Name, v.Quantity.Value, v.Price.Value, currency[strings.ToLower(v.Price.Currency)])
}
}
mb += fmt.Sprintf("Сумма: %v %s", msg.Data.Order.Cost.Value, currency[strings.ToLower(msg.Data.Order.Cost.Currency)])
} else {
case v1.MsgTypeOrder:
mb = getOrderMessage(msg.Data.Order)
case v1.MsgTypeText:
mb = msg.Data.Content
}
@ -588,3 +667,135 @@ func mgWebhookHandler(c *gin.Context) {
}
}
func getOrderMessage(dataOrder *v1.MessageDataOrder) string {
mb := getLocalizedMessage("order")
if dataOrder.Number != "" {
mb += " " + dataOrder.Number
}
if dataOrder.Date != "" {
mb += fmt.Sprintf(" (%s)", dataOrder.Date)
}
mb += "\n"
if len(dataOrder.Items) > 0 {
mb += "\n"
for k, v := range dataOrder.Items {
mb += fmt.Sprintf(
"%d. %s",
k+1,
v.Name,
)
if v.Quantity != nil {
if v.Quantity.Value != 0 {
mb += fmt.Sprintf(
" %v",
v.Quantity.Value,
)
}
}
if v.Price != nil {
if val, ok := currency[strings.ToLower(v.Price.Currency)]; ok {
mb += fmt.Sprintf(
" x %s\n",
getLocalizedTemplateMessage(
"cost_currency",
map[string]interface{}{
"Amount": v.Price.Value,
"Currency": val,
},
),
)
}
} else {
mb += "\n"
}
}
}
if dataOrder.Delivery != nil {
if dataOrder.Delivery.Name != "" {
mb += fmt.Sprintf(
"\n%s:\n%s",
getLocalizedMessage("delivery"),
dataOrder.Delivery.Name,
)
}
if dataOrder.Delivery.Amount != nil {
if val, ok := currency[strings.ToLower(dataOrder.Delivery.Amount.Currency)]; ok && dataOrder.Delivery.Amount.Value != 0 {
mb += fmt.Sprintf(
"; %s",
getLocalizedTemplateMessage(
"cost_currency",
map[string]interface{}{
"Amount": dataOrder.Delivery.Amount.Value,
"Currency": val,
},
),
)
}
}
if dataOrder.Delivery.Address != "" {
mb += ";\n" + dataOrder.Delivery.Address
}
mb += "\n"
}
if len(dataOrder.Payments) > 0 {
mb += fmt.Sprintf(
"\n%s:\n",
getLocalizedMessage("payment"),
)
for _, v := range dataOrder.Payments {
mb += v.Name
if v.Amount != nil {
if val, ok := currency[strings.ToLower(v.Amount.Currency)]; ok && v.Amount.Value != 0 {
mb += fmt.Sprintf(
"; %s",
getLocalizedTemplateMessage(
"cost_currency",
map[string]interface{}{
"Amount": v.Amount.Value,
"Currency": val,
},
),
)
}
}
if v.Status != nil && v.Status.Name != "" {
mb += fmt.Sprintf(
" (%s)",
v.Status.Name,
)
}
mb += "\n"
}
}
if dataOrder.Cost != nil {
if val, ok := currency[strings.ToLower(dataOrder.Cost.Currency)]; ok && dataOrder.Cost.Value != 0 {
mb += fmt.Sprintf(
"\n%s: %s",
getLocalizedMessage("order_total"),
getLocalizedTemplateMessage(
"cost_currency",
map[string]interface{}{
"Amount": dataOrder.Cost.Value,
"Currency": val,
},
),
)
}
}
return mb
}

View File

@ -53,6 +53,7 @@ func start() {
func setup() *gin.Engine {
loadTranslateFile()
setValidation()
updateChannelsSettings()
if config.Debug == false {
gin.SetMode(gin.ReleaseMode)
@ -89,6 +90,7 @@ func setup() *gin.Engine {
r.POST("/create/", checkConnectionForRequest(), createHandler)
r.POST("/add-bot/", checkBotForRequest(), addBotHandler)
r.POST("/delete-bot/", checkBotForRequest(), deleteBotHandler)
r.POST("/set-lang/", checkBotForRequest(), setLangBotHandler)
r.POST("/actions/activity", activityHandler)
r.POST("/telegram/:token", telegramWebhookHandler)
r.POST("/webhook/", mgWebhookHandler)

View File

@ -1,7 +1,9 @@
package main
import (
"crypto/sha1"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"net/http"
@ -13,7 +15,6 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/retailcrm/api-client-go/v5"
)
@ -50,12 +51,14 @@ func getAPIClient(url, key string) (*v5.Client, error, int) {
if res := checkCredentials(cr.Credentials); len(res) != 0 {
logger.Error(url, status, res)
return nil,
errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "missing_credentials",
TemplateData: map[string]interface{}{
errors.New(
getLocalizedTemplateMessage(
"missing_credentials",
map[string]interface{}{
"Credentials": strings.Join(res, ", "),
},
})),
),
),
http.StatusBadRequest
}
@ -119,3 +122,13 @@ func UploadUserAvatar(url string) (picURLs3 string, err error) {
return
}
func getChannelSettingsHash() (hash string, err error) {
res, err := json.Marshal(getChannelSettings())
h := sha1.New()
h.Write(res)
hash = fmt.Sprintf("%x", h.Sum(nil))
return
}

View File

@ -1,3 +1,16 @@
$(document).on("change", "select", function(e) {
send(
"/set-lang/",
{
token: $(this).attr("data-token"),
lang: $(this).find(":selected").text()
},
function () {
return 0;
}
)
});
$('#save-crm').on("submit", function(e) {
e.preventDefault();
send(
@ -39,6 +52,7 @@ $("#add-bot").on("submit", function(e) {
}
$("#bots tbody").append(getBotTemplate(data));
$("#token").val("");
$('select').formSelect();
}
)
});
@ -101,6 +115,15 @@ function getBotTemplate(data) {
`<tr>
<td>${data.name}</td>
<td>${data.token}</td>
<td>
<div class="col s3 sel-lang">
<select data-token="${data.token}">
<option value="en" selected>en</option>
<option value="ru">ru</option>
<option value="es">es</option>
</select>
</div>
</td>
<td>
<button class="delete-bot btn btn-small waves-effect waves-light light-blue darken-1" type="submit" name="action"
data-token="${data.token}">
@ -120,6 +143,7 @@ function formDataToObj(formArray) {
}
$( document ).ready(function() {
$('select').formSelect();
M.Tabs.init(document.getElementById("tab"));
if ($("table tbody").children().length === 0) {
$("#bots").addClass("hide");

View File

@ -32,10 +32,19 @@ main {
float: right;
}
#bots .select-wrapper input.select-dropdown,
#bots {
font-size: 12px;
}
#bots .sel-lang{
padding: 0;
}
#bots .select-wrapper ul li span{
color: #039be5;
}
#msg{
height: 23px;
}

View File

@ -51,19 +51,31 @@
</div>
</div>
</form>
{{$LangCode := .LangCode}}
<table id="bots" class="tab-el-center">
<thead>
<tr>
<th>{{.Locale.TableName}}</th>
<th>{{.Locale.TableToken}}</th>
<th>{{.Locale.Language}}</th>
<th class="text-left">{{.Locale.TableDelete}}</th>
</tr>
</thead>
<tbody>
{{range .Bots}}
{{$lang := .Lang}}
<tr>
<td>{{.Name}}</td>
<td>{{.Token}}</td>
<td>
<div class="col s3 sel-lang">
<select data-token="{{.Token}}">
{{range $key, $value := $LangCode}}
<option value="{{$value}}" {{if eq $value $lang}}selected{{end}}>{{$value}}</option>
{{end}}
</select>
</div>
</td>
<td>
<button class="delete-bot btn btn-small waves-effect waves-light light-blue darken-1" type="submit" name="action"
data-token="{{.Token}}">

View File

@ -3,9 +3,9 @@
<meta title="Telegram transport">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{{.Locale.Title}}</title>
<link rel="stylesheet" href="/static/materialize.min.css">
<link rel="stylesheet" href="/static/font.css" >
<link rel="stylesheet" href="/static/jquery-confirm.min.css">
<link rel="stylesheet" href="/static/materialize.min.css">
<link rel="stylesheet" href="/static/style.css" >
</head>
<body>
@ -29,8 +29,8 @@
</div>
</div>
</footer>
<script src="/static/materialize.min.js"></script>
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/materialize.min.js"></script>
<script src="/static/jquery-confirm.min.js"></script>
<script src="/static/script.js"></script>
</body>

View File

@ -8,6 +8,7 @@ api_key: API key
add_bot: Add a bot
title: Module of connecting Telegram to retailCRM
successful: Data was updated successfully
language: Language
no_bot_token: Enter a token
wrong_data: Wrong data
@ -41,3 +42,10 @@ video: "[video]"
voice: "[voice message]"
photo: "[photo]"
undefined: "[undefined format of a message]"
item_cost: "Cost"
order: "Order"
delivery: "Delivery"
payment: "Payment"
order_total: "Order total"
cost_currency: "{{.Amount}} {{.Currency}}"

View File

@ -8,6 +8,7 @@ api_key: API key
add_bot: Añadir un bot
title: Múdulo de conexión de Telegram a retailCRM
successful: Datos actualizados con éxito
language: Idioma
no_bot_token: Introduzca un token
wrong_data: Datos erróneos
@ -41,3 +42,10 @@ video: "[video]"
voice: "[mensaje de voz]"
photo: "[foto]"
other: "[formato indefinido de mensaje]"
item_cost: "Precio"
order: "Pedido"
delivery: "Entrega"
payment: "Pago"
order_total: "Total pedido"
cost_currency: "{{.Amount}} {{.Currency}}"

View File

@ -8,6 +8,7 @@ api_key: API Ключ
add_bot: Добавить бота
title: Модуль подключения Telegram к retailCRM
successful: Данные успешно обновлены
language: Язык
no_bot_token: Введите токен
wrong_data: Неверные данные
@ -41,3 +42,10 @@ video: "[видео]"
voice: "[голосовое сообщение]"
photo: "[изображение]"
undefined: "[неопределенный формат сообщения]"
item_cost: "Цена"
order: "Заказ"
delivery: "Доставка"
payment: "Оплата"
order_total: "Сумма"
cost_currency: "{{.Amount}} {{.Currency}}"