1
0
mirror of synced 2024-11-22 12:26:02 +03:00
mg-transport-telegram/routing.go
2018-05-29 10:08:59 +03:00

567 lines
16 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"regexp"
"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"
"golang.org/x/text/language"
"gopkg.in/yaml.v2"
)
var (
templates = template.Must(template.ParseFiles("templates/layout.html", "templates/form.html", "templates/home.html"))
validPath = regexp.MustCompile(`^/(save|settings|telegram)/([a-zA-Z0-9-:_+]+)$`)
localizer *i18n.Localizer
bundle = &i18n.Bundle{DefaultLanguage: language.English}
matcher = language.NewMatcher([]language.Tag{
language.English,
language.Russian,
language.Spanish,
})
)
func init() {
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
files, err := ioutil.ReadDir("translate")
if err != nil {
logger.Error(err)
}
for _, f := range files {
if !f.IsDir() {
bundle.MustLoadMessageFile("translate/" + f.Name())
}
}
}
func setLocale(al string) {
tag, _ := language.MatchStrings(matcher, al)
localizer = i18n.NewLocalizer(bundle, tag.String())
}
// Response struct
type Response struct {
Success bool `json:"success"`
Error string `json:"error"`
}
func setWrapperRoutes() {
http.HandleFunc("/", connectHandler)
http.HandleFunc("/settings/", makeHandler(settingsHandler))
http.HandleFunc("/save/", saveHandler)
http.HandleFunc("/create/", createHandler)
http.HandleFunc("/actions/activity", activityHandler)
http.HandleFunc("/add-bot/", addBotHandler)
http.HandleFunc("/activity-bot/", activityBotHandler)
}
func renderTemplate(w http.ResponseWriter, tmpl string, c interface{}) {
tm, err := template.ParseFiles("templates/layout.html", "templates/"+tmpl+".html")
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tm.Execute(w, &c)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return
}
fn(w, r, m[2])
}
}
func connectHandler(w http.ResponseWriter, r *http.Request) {
setLocale(r.Header.Get("Accept-Language"))
p := Connection{}
res := struct {
Conn *Connection
Locale map[string]interface{}
}{
&p,
map[string]interface{}{
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
},
}
renderTemplate(w, "home", &res)
}
func addBotHandler(w http.ResponseWriter, r *http.Request) {
setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
return
}
var b Bot
err = json.Unmarshal(body, &b)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
return
}
if b.Token == "" {
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "no_bot_token"}), http.StatusBadRequest)
return
}
c := getConnection(b.ClientID)
if c.MGURL == "" || c.MGToken == "" {
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest)
logger.Error(b.ClientID, "MGURL or MGToken is empty")
return
}
cl := getBotByToken(b.Token)
if cl.ID != 0 {
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.StatusBadRequest)
return
}
bot.Debug = false
wr, 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
}
if !wr.Ok {
logger.Error(b.Token, wr.ErrorCode, wr.Result)
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
}
b.Name = GetBotName(bot)
ch := v1.Channel{
Type: "telegram",
Events: []string{
"message_sent",
"message_updated",
"message_deleted",
"message_read",
},
}
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.StatusBadRequest)
logger.Error(c.APIURL, status, err.Error(), data)
return
}
b.Channel = data.ChannelID
b.Active = true
err = b.createBot()
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
logger.Error(c.APIURL, err.Error())
return
}
jsonString, err := json.Marshal(b)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
logger.Error(c.APIURL, err.Error())
return
}
w.WriteHeader(http.StatusCreated)
w.Write(jsonString)
}
func activityBotHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
var b Bot
err = json.Unmarshal(body, &b)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
c := getConnection(b.ClientID)
if c.MGURL == "" || c.MGToken == "" {
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest)
logger.Error(b.ClientID, "MGURL or MGToken is empty")
return
}
ch := v1.Channel{
ID: getBotChannelByToken(b.Token),
Type: "telegram",
Events: []string{
"message_sent",
"message_updated",
"message_deleted",
"message_read",
},
}
var client = v1.New(c.MGURL, c.MGToken)
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.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.StatusBadRequest)
logger.Error(b.ClientID, status, err.Error(), data)
return
}
}
err = b.setBotActivity()
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(b.ClientID, err.Error())
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
setLocale(r.Header.Get("Accept-Language"))
p := getConnection(uid)
if p.ID == 0 {
http.Redirect(w, r, "/", http.StatusFound)
}
bots := Bots{}
err := bots.getBotsByClientID(uid)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
res := struct {
Conn *Connection
Bots Bots
Locale map[string]interface{}
}{
p,
bots,
map[string]interface{}{
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
"TabSettings": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_settings"}),
"TabBots": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_bots"}),
"TableName": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_name"}),
"TableToken": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_token"}),
"AddBot": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "add_bot"}),
"TableActivity": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_activity"}),
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
},
}
renderTemplate(w, "form", res)
}
func saveHandler(w http.ResponseWriter, r *http.Request) {
setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
var c Connection
err = json.Unmarshal(body, &c)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
err = validateCrmSettings(c)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
logger.Error(c.APIURL, err.Error())
return
}
err = c.saveConnection()
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
logger.Error(c.APIURL, err.Error())
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"})))
}
func createHandler(w http.ResponseWriter, r *http.Request) {
setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
var c Connection
err = json.Unmarshal(body, &c)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
return
}
c.ClientID = GenerateToken()
err = validateCrmSettings(c)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
logger.Error(c.APIURL, err.Error())
return
}
cl := getConnectionByURL(c.APIURL)
if cl.ID != 0 {
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "connection_already_created"}), http.StatusBadRequest)
return
}
client := v5.New(c.APIURL, c.APIKEY)
cr, status, errr := client.APICredentials()
if errr.RuntimeErr != nil {
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusInternalServerError)
logger.Error(c.APIURL, status, errr.RuntimeErr, cr)
return
}
if !cr.Success {
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url_key"}), http.StatusBadRequest)
logger.Error(c.APIURL, status, errr.ApiErr, cr)
return
}
//TODO: проверка на необходимые методы cr.Credentials
integration := v5.IntegrationModule{
Code: transport,
IntegrationCode: transport,
Active: true,
Name: "Telegram",
ClientID: c.ClientID,
Logo: fmt.Sprintf(
"https://%s/web/telegram_logo.svg",
config.HTTPServer.Host,
),
BaseURL: fmt.Sprintf(
"https://%s",
config.HTTPServer.Host,
),
AccountURL: fmt.Sprintf(
"https://%s/settings/%s",
config.HTTPServer.Host,
c.ClientID,
),
Actions: map[string]string{"activity": "/actions/activity"},
Integrations: &v5.Integrations{
MgTransport: &v5.MgTransport{
WebhookUrl: fmt.Sprintf(
"https://%s/webhook",
config.HTTPServer.Host,
),
},
},
}
data, status, errr := client.IntegrationModuleEdit(integration)
if errr.RuntimeErr != nil {
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_integration"}), http.StatusInternalServerError)
logger.Error(c.APIURL, status, errr.RuntimeErr, data)
return
}
if status >= http.StatusBadRequest {
http.Error(w, errr.ApiErr, http.StatusBadRequest)
logger.Error(c.APIURL, status, errr.ApiErr, data)
return
}
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)
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 {
raven.CaptureErrorAndWait(err, nil)
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusFound)
w.Write(jss)
}
func activityHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
res := Response{Success: false}
if r.Method != http.MethodPost {
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "set_method"})
jsonString, err := json.Marshal(res)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
return
}
w.Write(jsonString)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_data"})
jsonString, err := json.Marshal(res)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
return
}
w.Write(jsonString)
return
}
var rec Connection
err = json.Unmarshal(body, &rec)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_data"})
jsonString, err := json.Marshal(res)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
return
}
w.Write(jsonString)
return
}
if err := rec.setConnectionActivity(); err != nil {
raven.CaptureErrorAndWait(err, nil)
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_data"})
jsonString, err := json.Marshal(res)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
return
}
w.Write(jsonString)
return
}
res.Success = true
jsonString, err := json.Marshal(res)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
return
}
w.Write(jsonString)
}
func validateCrmSettings(c Connection) error {
if c.APIURL == "" || c.APIKEY == "" {
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "missing_url_key"}))
}
if res, _ := regexp.MatchString(`https://?[\da-z\.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`, c.APIURL); !res {
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url"}))
}
return nil
}