From 74b5205768acdfb21770ea271142e7a09dfa356c Mon Sep 17 00:00:00 2001 From: DmitryZagorulko Date: Wed, 23 May 2018 18:03:11 +0300 Subject: [PATCH] add telegram webhook, improve template, minor fixes --- config.go | 14 ++- routing.go | 179 ++++++++++++++++++++++++++++++------- telegram.go | 1 + templates/form.html | 15 ++-- templates/home.html | 11 +-- templates/layout.html | 8 +- translate/translate.en.yml | 8 +- translate/translate.ru.yml | 8 +- web/script.js | 20 +++-- web/style.css | 10 +++ 10 files changed, 207 insertions(+), 67 deletions(-) diff --git a/config.go b/config.go index 8190ae0..cb5aae8 100644 --- a/config.go +++ b/config.go @@ -10,10 +10,11 @@ import ( // TransportConfig struct type TransportConfig struct { - LogLevel logging.Level `yaml:"log_level"` - Database DatabaseConfig `yaml:"database"` - SentryDSN string `yaml:"sentry_dsn"` - HTTPServer HTTPServerConfig `yaml:"http_server"` + LogLevel logging.Level `yaml:"log_level"` + Database DatabaseConfig `yaml:"database"` + SentryDSN string `yaml:"sentry_dsn"` + HTTPServer HTTPServerConfig `yaml:"http_server"` + TelegramConfig TelegramConfig `yaml:"telegram"` } // DatabaseConfig struct @@ -32,6 +33,11 @@ type HTTPServerConfig struct { Listen string `yaml:"listen"` } +// TelegramConfig struct +type TelegramConfig struct { + Debug bool `yaml:"debug"` +} + // LoadConfig read configuration file func LoadConfig(path string) *TransportConfig { var err error diff --git a/routing.go b/routing.go index 421939b..8ec651c 100644 --- a/routing.go +++ b/routing.go @@ -8,8 +8,11 @@ import ( "io/ioutil" "net/http" "regexp" + "strconv" + "time" "github.com/getsentry/raven-go" + "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/retailcrm/api-client-go/v5" "github.com/retailcrm/mg-transport-api-client-go/v1" @@ -19,7 +22,7 @@ import ( var ( templates = template.Must(template.ParseFiles("templates/layout.html", "templates/form.html", "templates/home.html")) - validPath = regexp.MustCompile("^/(save|settings)/([a-zA-Z0-9]+)$") + validPath = regexp.MustCompile(`^/(save|settings|telegram)/([a-zA-Z0-9-:_+]+)$`) localizer *i18n.Localizer bundle = &i18n.Bundle{DefaultLanguage: language.English} matcher = language.NewMatcher([]language.Tag{ @@ -62,10 +65,16 @@ func setWrapperRoutes() { } func renderTemplate(w http.ResponseWriter, tmpl string, c interface{}) { - err := templates.ExecuteTemplate(w, tmpl+".html", c) + tm, err := template.ParseFiles("templates/layout.html", "templates/"+tmpl+".html") if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) + } + + err = tm.Execute(w, &c) + if err != nil { + raven.CaptureErrorAndWait(err, nil) + http.Error(w, err.Error(), http.StatusBadRequest) } } @@ -101,7 +110,7 @@ func connectHandler(w http.ResponseWriter, r *http.Request) { func addBotHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -109,25 +118,41 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &b) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } if b.Token == "" { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "no_bot_token"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "no_bot_token"}), http.StatusBadRequest) return } cl, _ := getBotByToken(b.Token) if cl.ID != 0 { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "bot_already_created"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "bot_already_created"}), http.StatusBadRequest) return } bot, err := GetBotInfo(b.Token) if err != nil { logger.Error(b.Token, err.Error()) - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusBadRequest) + return + } + + bot.Debug = false + + _, err = bot.SetWebhook(tgbotapi.NewWebhook("https://" + config.HTTPServer.Host + "/telegram/" + bot.Token)) + if err != nil { + logger.Error(b.Token, err.Error()) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest) + return + } + + _, err = bot.GetWebhookInfo() + if err != nil { + logger.Error(b.Token, err.Error()) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest) return } @@ -135,14 +160,14 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { c, err := getConnection(b.ClientID) if err != nil { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_find_account"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest) logger.Error(b.ClientID, err.Error()) return } if c.MGURL == "" || c.MGToken == "" { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_find_account"}), http.StatusInternalServerError) - logger.Error(b.ClientID) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest) + logger.Error(b.ClientID, "MGURL or MGToken is empty") return } @@ -159,7 +184,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { var client = v1.New(c.MGURL, c.MGToken) data, status, err := client.ActivateTransportChannel(ch) if status != http.StatusCreated { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusBadRequest) logger.Error(c.APIURL, status, err.Error(), data) return } @@ -170,7 +195,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { err = b.createBot() if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -182,7 +207,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { func activityBotHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -190,7 +215,7 @@ func activityBotHandler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &b) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -207,14 +232,14 @@ func activityBotHandler(w http.ResponseWriter, r *http.Request) { c, err := getConnection(b.ClientID) if err != nil { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_find_account"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest) logger.Error(b.ClientID, err.Error()) return } if c.MGURL == "" || c.MGToken == "" { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_find_account"}), http.StatusInternalServerError) - logger.Error(b.ClientID, "not find account") + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest) + logger.Error(b.ClientID, "MGURL or MGToken is empty") return } @@ -223,14 +248,14 @@ func activityBotHandler(w http.ResponseWriter, r *http.Request) { if b.Active { data, status, err := client.DeactivateTransportChannel(ch.ID) if status > http.StatusOK { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_deactivating_channel"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_deactivating_channel"}), http.StatusBadRequest) logger.Error(b.ClientID, status, err.Error(), data) return } } else { data, status, err := client.ActivateTransportChannel(ch) if status > http.StatusCreated { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusBadRequest) logger.Error(b.ClientID, status, err.Error(), data) return } @@ -239,7 +264,7 @@ func activityBotHandler(w http.ResponseWriter, r *http.Request) { err = b.setBotActivity() if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -252,7 +277,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) { p, err := getConnection(uid) if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -294,7 +319,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) { func saveHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -302,7 +327,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &c) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -316,12 +341,12 @@ func saveHandler(w http.ResponseWriter, r *http.Request) { err = c.saveConnection() if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) - w.Write([]byte("/settings/" + r.FormValue("clientId"))) + w.Write([]byte(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"}))) } func createHandler(w http.ResponseWriter, r *http.Request) { @@ -329,7 +354,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -337,7 +362,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &c) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -416,16 +441,30 @@ func createHandler(w http.ResponseWriter, r *http.Request) { c.MGURL = data.Info["baseUrl"] c.MGToken = data.Info["token"] + c.Active = true err = c.createConnection() if err != nil { raven.CaptureErrorAndWait(err, nil) - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError) + http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusBadRequest) return } + res := struct { + Url string + Message string + }{ + Url: "/settings/" + c.ClientID, + Message: localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"}), + } + + jss, err := json.Marshal(res) + if err != nil { + logger.Error(c.APIURL, err) + } + w.WriteHeader(http.StatusFound) - w.Write([]byte("/settings/" + c.ClientID)) + w.Write(jss) } func activityHandler(w http.ResponseWriter, r *http.Request) { @@ -483,3 +522,83 @@ func validate(c Connection) error { return nil } + +func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) { + t := time.Now() + b, err := getBotByToken(token) + if err != nil { + logger.Error(token, err) + } + + if !b.Active { + logger.Error(token, err) + } + + bot, err := GetBotInfo(token) + if err != nil { + logger.Error(token, err) + } + + bot.Debug = false + + bytes, _ := ioutil.ReadAll(r.Body) + + var update tgbotapi.Update + json.Unmarshal(bytes, &update) + + c, err := getConnection(b.ClientID) + if err != nil { + logger.Error(token, err.Error()) + return + } + + if c.MGURL == "" || c.MGToken == "" { + logger.Error(token, "MGURL or MGToken is empty") + return + } + + var client = v1.New(c.MGURL, c.MGToken) + + if update.Message != nil { + snd := v1.SendData{ + Message: v1.SendMessage{ + Message: v1.Message{ + ExternalID: strconv.Itoa(update.Message.MessageID), + Type: "text", + Text: update.Message.Text, + }, + SentAt: time.Now(), + }, + User: v1.User{ + ExternalID: strconv.Itoa(update.Message.From.ID), + Nickname: update.Message.From.UserName, + Firstname: update.Message.From.FirstName, + }, + Channel: b.Channel, + } + + data, status, err := client.Messages(snd) + if err != nil { + logger.Error(token, err.Error(), status, data) + } + } + + if update.EditedMessage != nil { + snd := v1.UpdateData{ + Message: v1.UpdateMessage{ + Message: v1.Message{ + ExternalID: strconv.Itoa(update.EditedMessage.MessageID), + Type: "text", + Text: update.EditedMessage.Text, + }, + }, + Channel: b.Channel, + } + + data, status, err := client.UpdateMessages(snd) + if err != nil { + logger.Error(token, err.Error(), status, data) + } + } + fmt.Println("Time working: ", time.Since(t)) +} diff --git a/telegram.go b/telegram.go index 2a79e55..2901a35 100644 --- a/telegram.go +++ b/telegram.go @@ -9,6 +9,7 @@ import ( func setTransportRoutes() { http.HandleFunc("/add-bot/", addBotHandler) http.HandleFunc("/activity-bot/", activityBotHandler) + http.HandleFunc("/telegram/", makeHandler(telegramWebhookHandler)) } // GetBotInfo function diff --git a/templates/form.html b/templates/form.html index 02d5190..f7f8f3d 100644 --- a/templates/form.html +++ b/templates/form.html @@ -1,17 +1,14 @@ -{{template "header"}} +{{define "body"}}
-
+ -
-
-
-
+
@@ -36,7 +33,7 @@
- +
@@ -51,7 +48,7 @@
- +
@@ -77,4 +74,4 @@ -{{template "footer"}} +{{end}} diff --git a/templates/home.html b/templates/home.html index 92f3fb5..2e4dd6e 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,9 +1,6 @@ -{{template "header"}} -
-
-
-
-
+{{define "body"}} +
+
@@ -25,4 +22,4 @@
-{{template "footer"}} +{{end}} diff --git a/templates/layout.html b/templates/layout.html index 297f802..4089a39 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -1,9 +1,8 @@ -{{ define "header" }} - + {{.Locale.Title}} @@ -13,13 +12,10 @@
-{{ end }} - -{{ define "footer" }} + {{template "body" .}}
-{{ end }} diff --git a/translate/translate.en.yml b/translate/translate.en.yml index 3902433..7af5c92 100644 --- a/translate/translate.en.yml +++ b/translate/translate.en.yml @@ -5,12 +5,15 @@ table_name: Bot name table_token: Bot token table_activity: Activity api_key: API Key +add_bot: Add bot +title: Telegram transport for retailCRM +successful: Data successfully updated no_bot_token: Enter the bot token wrong_data: Incorrect data set_method: Set POST method bot_already_created: Bot already created -not_find_account: Could not find account, please contact technical support +not_found_account: The account could not be found, contact technical support error_activating_channel: Error while activating the channel error_deactivating_channel: Error while deactivating the channel incorrect_url_key: Enter the correct CRM url or apiKey @@ -20,5 +23,4 @@ connection_already_created: Connection already created missing_url_key: Missing crm url or apiKey incorrect_url: Enter the correct CRM url incorrect_token: Set correct bot token -add_bot: Add bot -title: Telegram transport for retailCRM +error_creating_webhook: Error while creating webhook diff --git a/translate/translate.ru.yml b/translate/translate.ru.yml index 0aae946..6a79f5b 100644 --- a/translate/translate.ru.yml +++ b/translate/translate.ru.yml @@ -5,12 +5,15 @@ table_name: Имя table_token: Токен table_activity: Активность api_key: API Ключ +add_bot: Добавить бота +title: Модуль подключения Telegram к retailCRM +successful: Данные успешно обновлены no_bot_token: Введите токен wrong_data: Неверные данные set_method: Установить метод POST bot_already_created: Бот уже создан -not_find_account: Не удалось найти учетную запись, обратитесь в службу технической поддержки +not_found_account: Не удалось найти учетную запись, обратитесь в службу технической поддержки error_activating_channel: Ошибка при активации канала error_deactivating_channel: Ошибка при отключении канала incorrect_url_key: Введите корректный URL или apiKey @@ -20,5 +23,4 @@ connection_already_created: Соединение уже создано missing_url_key: Отсутствует URL или apiKey incorrect_url: Введите корректный URL CRM incorrect_token: Установите корректный токен -add_bot: Добавить бота -title: Модуль подключения Telegram к retailCRM +error_creating_webhook: Ошибка при создании webhook diff --git a/web/script.js b/web/script.js index 0a9a99f..a94887f 100644 --- a/web/script.js +++ b/web/script.js @@ -14,8 +14,8 @@ $("#save").on("submit", function(e) { send( $(this).attr('action'), formDataToObj($(this).serializeArray()), - function () { - return 0; + function (data) { + M.toast({html: data}); } ) }); @@ -31,6 +31,7 @@ $("#add-bot").on("submit", function(e) { bots.removeClass("hide") } $("#bots tbody").append(getBotTemplate(data)); + $("#token").val(""); } ) }); @@ -56,7 +57,6 @@ $(document).on("click", ".activity-bot", function(e) { }); function send(url, data, callback) { - $('#msg').empty(); $.ajax({ url: url, data: JSON.stringify(data), @@ -65,12 +65,14 @@ function send(url, data, callback) { error: function (res){ if (res.status < 400) { if (res.responseText) { + let resObj = JSON.parse(res.responseText); + localStorage.setItem("createdMsg", resObj.Message); + document.location.replace( - location.protocol.concat("//").concat(window.location.host) + res.responseText + location.protocol.concat("//").concat(window.location.host) + resObj.Url ); } } else { - //$('#msg').html(`

${res.responseText}

`); M.toast({html: res.responseText}) } } @@ -106,4 +108,12 @@ $( document ).ready(function() { if ($("table tbody").children().length === 0) { $("#bots").addClass("hide"); } + + let createdMsg = localStorage.getItem("createdMsg"); + if (createdMsg) { + setTimeout(function() { + M.toast({html: createdMsg}); + localStorage.removeItem("createdMsg"); + }, 1000); + } }); diff --git a/web/style.css b/web/style.css index cbda80d..69bda59 100644 --- a/web/style.css +++ b/web/style.css @@ -6,6 +6,16 @@ text-align: right; } +#tab{ + width: 50%; + margin: 0 auto 23px; +} + +.tab-el-center{ + width: 67%; + margin: 0 auto; +} + #bots .activity-bot{ float: right; }
{{.Locale.TableName}}