diff --git a/Makefile b/Makefile index 9c5e35f..f737613 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,14 @@ ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -SRC_DIR=$(ROOT_DIR) +SRC_DIR=$(ROOT_DIR)/src MIGRATIONS_DIR=$(ROOT_DIR)/migrations CONFIG_FILE=$(ROOT_DIR)/config.yml CONFIG_TEST_FILE=$(ROOT_DIR)/config_test.yml BIN=$(ROOT_DIR)/bin/transport REVISION=$(shell git describe --tags 2>/dev/null || git log --format="v0.0-%h" -n 1 || echo "v0.0-unknown") -ifndef GOPATH - $(error GOPATH must be defined) -endif - -export GOPATH := $(GOPATH):$(ROOT_DIR) - build: deps fmt @echo "==> Building" - @cd $(ROOT_DIR) && CGO_ENABLED=0 go build -o $(BIN) -ldflags "-X common.build=${REVISION}" . + @cd $(SRC_DIR) && CGO_ENABLED=0 go build -o $(BIN) -ldflags "-X common.build=${REVISION}" . @echo $(BIN) run: migrate diff --git a/config.go b/src/config.go similarity index 100% rename from config.go rename to src/config.go diff --git a/database.go b/src/database.go similarity index 100% rename from database.go rename to src/database.go diff --git a/error_handler.go b/src/error_handler.go similarity index 100% rename from error_handler.go rename to src/error_handler.go diff --git a/locale.go b/src/locale.go similarity index 100% rename from locale.go rename to src/locale.go diff --git a/log.go b/src/log.go similarity index 100% rename from log.go rename to src/log.go diff --git a/main.go b/src/main.go similarity index 100% rename from main.go rename to src/main.go diff --git a/migrate.go b/src/migrate.go similarity index 100% rename from migrate.go rename to src/migrate.go diff --git a/models.go b/src/models.go similarity index 100% rename from models.go rename to src/models.go diff --git a/repository.go b/src/repository.go similarity index 100% rename from repository.go rename to src/repository.go diff --git a/routing.go b/src/routing.go similarity index 52% rename from routing.go rename to src/routing.go index 86b0444..03b3216 100644 --- a/routing.go +++ b/src/routing.go @@ -1,9 +1,10 @@ package main import ( - "net/http" - "fmt" + "net/http" + "strconv" + "time" "github.com/gin-gonic/gin" "github.com/go-telegram-bot-api/telegram-bot-api" @@ -271,3 +272,250 @@ func getIntegrationModule(clientId string) v5.IntegrationModule { }, } } + +func telegramWebhookHandler(c *gin.Context) { + token := c.Param("token") + b, err := getBotByToken(token) + if err != nil { + c.Error(err) + return + } + + if b.ID == 0 { + c.AbortWithStatus(http.StatusOK) + return + } + + conn := getConnectionById(b.ConnectionID) + if !conn.Active { + c.AbortWithStatus(http.StatusOK) + return + } + + var update tgbotapi.Update + if err := c.ShouldBindJSON(&update); err != nil { + c.Error(err) + return + } + + if config.Debug { + logger.Debugf("mgWebhookHandler request: %v", update) + } + + var client = v1.New(conn.MGURL, conn.MGToken) + + if update.Message != nil { + if update.Message.Text == "" { + setLocale(update.Message.From.LanguageCode) + update.Message.Text = getLocalizedMessage(getMessageID(update.Message)) + } + + nickname := update.Message.From.UserName + user := getUserByExternalID(update.Message.From.ID) + + if update.Message.From.UserName == "" { + nickname = update.Message.From.FirstName + } + + if user.Expired(config.UpdateInterval) || user.ID == 0 { + fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID) + if err != nil { + c.Error(err) + return + } + + if fileID != user.UserPhotoID && fileURL != "" { + picURL, err := UploadUserAvatar(fileURL) + if err != nil { + c.Error(err) + return + } + + user.UserPhotoID = fileID + user.UserPhotoURL = picURL + } + + if user.ExternalID == 0 { + user.ExternalID = update.Message.From.ID + } + + err = user.save() + if err != nil { + c.Error(err) + return + } + } + + lang := update.Message.From.LanguageCode + + if len(update.Message.From.LanguageCode) > 2 { + lang = update.Message.From.LanguageCode[:2] + } + + if config.Debug { + logger.Debugf("telegramWebhookHandler user %v", user) + } + + 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: nickname, + Firstname: update.Message.From.FirstName, + Avatar: user.UserPhotoURL, + Lastname: update.Message.From.LastName, + Language: lang, + }, + Channel: b.Channel, + ExternalChatID: strconv.FormatInt(update.Message.Chat.ID, 10), + } + + if update.Message.ReplyToMessage != nil { + snd.Quote = &v1.SendMessageRequestQuote{ExternalID: strconv.Itoa(update.Message.ReplyToMessage.MessageID)} + } + + data, st, err := client.Messages(snd) + if err != nil { + logger.Error(b.Token, err.Error(), st, data) + c.Error(err) + return + } + + if config.Debug { + logger.Debugf("telegramWebhookHandler Type: SendMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data) + } + } + + if update.EditedMessage != nil { + if update.EditedMessage.Text == "" { + setLocale(update.EditedMessage.From.LanguageCode) + update.EditedMessage.Text = getLocalizedMessage(getMessageID(update.Message)) + } + + 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, st, err := client.UpdateMessages(snd) + if err != nil { + logger.Error(b.Token, err.Error(), st, data) + c.Error(err) + return + } + + if config.Debug { + logger.Debugf("telegramWebhookHandler Type: UpdateMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data) + } + } + + c.JSON(http.StatusOK, gin.H{}) +} + +func mgWebhookHandler(c *gin.Context) { + clientID := c.GetHeader("Clientid") + if clientID == "" { + c.AbortWithStatus(http.StatusBadRequest) + return + } + + conn := getConnection(clientID) + if !conn.Active { + c.AbortWithStatus(http.StatusBadRequest) + return + } + + var msg v1.WebhookRequest + if err := c.ShouldBindJSON(&msg); err != nil { + c.Error(err) + return + } + + if config.Debug { + logger.Debugf("mgWebhookHandler request: %v", msg) + } + + uid, _ := strconv.Atoi(msg.Data.ExternalMessageID) + cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64) + + b := getBot(conn.ID, msg.Data.ChannelID) + if b.ID == 0 { + c.AbortWithStatus(http.StatusBadRequest) + return + } + + bot, err := tgbotapi.NewBotAPI(b.Token) + if err != nil { + c.Error(err) + return + } + + if msg.Type == "message_sent" { + m := tgbotapi.NewMessage(cid, msg.Data.Content) + + if msg.Data.QuoteExternalID != "" { + qid, err := strconv.Atoi(msg.Data.QuoteExternalID) + if err != nil { + c.Error(err) + return + } + m.ReplyToMessageID = qid + } + + msgSend, err := bot.Send(m) + if err != nil { + logger.Error(err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + if config.Debug { + logger.Debugf("mgWebhookHandler sent %v", msgSend) + } + + c.JSON(http.StatusOK, gin.H{"external_message_id": strconv.Itoa(msgSend.MessageID)}) + } + + if msg.Type == "message_updated" { + msgSend, err := bot.Send(tgbotapi.NewEditMessageText(cid, uid, msg.Data.Content)) + if err != nil { + logger.Error(err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + if config.Debug { + logger.Debugf("mgWebhookHandler update %v", msgSend) + } + + c.AbortWithStatus(http.StatusOK) + } + + if msg.Type == "message_deleted" { + msgSend, err := bot.Send(tgbotapi.NewDeleteMessage(cid, uid)) + if err != nil { + logger.Error(err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + if config.Debug { + logger.Debugf("mgWebhookHandler delete %v", msgSend) + } + + c.JSON(http.StatusOK, gin.H{}) + } +} diff --git a/routing_test.go b/src/routing_test.go similarity index 100% rename from routing_test.go rename to src/routing_test.go diff --git a/run.go b/src/run.go similarity index 100% rename from run.go rename to src/run.go diff --git a/stacktrace.go b/src/stacktrace.go similarity index 100% rename from stacktrace.go rename to src/stacktrace.go diff --git a/src/telegram.go b/src/telegram.go new file mode 100644 index 0000000..f191c93 --- /dev/null +++ b/src/telegram.go @@ -0,0 +1,57 @@ +package main + +import "github.com/go-telegram-bot-api/telegram-bot-api" + +//GetFileIDAndURL function +func GetFileIDAndURL(token string, userID int) (fileID, fileURL string, err error) { + bot, err := tgbotapi.NewBotAPI(token) + if err != nil { + return + } + + bot.Debug = config.Debug + + res, err := bot.GetUserProfilePhotos( + tgbotapi.UserProfilePhotosConfig{ + UserID: userID, + Limit: 1, + }, + ) + if err != nil { + return + } + + if config.Debug { + logger.Debugf("GetFileIDAndURL Photos: %v", res.Photos) + } + + if len(res.Photos) > 0 { + fileID = res.Photos[0][len(res.Photos[0])-1].FileID + fileURL, err = bot.GetFileDirectURL(fileID) + } + + return +} + +func getMessageID(data *tgbotapi.Message) string { + switch { + case data.Sticker != nil: + return "sticker" + case data.Audio != nil: + return "audio" + case data.Contact != nil: + return "contact" + case data.Document != nil: + return "document" + case data.Location != nil: + return "location" + case data.Video != nil: + return "video" + case data.Voice != nil: + return "voice" + case data.Photo != nil: + return "photo" + default: + return "undefined" + } +} diff --git a/utils.go b/src/utils.go similarity index 56% rename from utils.go rename to src/utils.go index 6382896..18fd9ce 100644 --- a/utils.go +++ b/src/utils.go @@ -9,6 +9,10 @@ import ( "sync/atomic" "time" + "github.com/aws/aws-sdk-go/aws" + "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" ) @@ -68,3 +72,42 @@ func checkCredentials(credential []string) []string { return rc } + +//UploadUserAvatar function +func UploadUserAvatar(url string) (picURLs3 string, err error) { + s3Config := &aws.Config{ + Credentials: credentials.NewStaticCredentials( + config.ConfigAWS.AccessKeyID, + config.ConfigAWS.SecretAccessKey, + ""), + Region: aws.String(config.ConfigAWS.Region), + } + + s := session.Must(session.NewSession(s3Config)) + uploader := s3manager.NewUploader(s) + + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode >= http.StatusBadRequest { + return "", errors.New(fmt.Sprintf("get: %v code: %v", url, resp.StatusCode)) + } + + result, err := uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String(config.ConfigAWS.Bucket), + Key: aws.String(fmt.Sprintf("%v/%v.jpg", config.ConfigAWS.FolderName, GenerateToken())), + Body: resp.Body, + ContentType: aws.String(config.ConfigAWS.ContentType), + ACL: aws.String("public-read"), + }) + if err != nil { + return + } + + picURLs3 = result.Location + + return +} diff --git a/validator.go b/src/validator.go similarity index 100% rename from validator.go rename to src/validator.go diff --git a/telegram.go b/telegram.go deleted file mode 100644 index 61296e5..0000000 --- a/telegram.go +++ /dev/null @@ -1,357 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/aws/aws-sdk-go/aws" - "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/gin-gonic/gin" - "github.com/go-telegram-bot-api/telegram-bot-api" - "github.com/retailcrm/mg-transport-api-client-go/v1" -) - -func telegramWebhookHandler(c *gin.Context) { - token := c.Param("token") - b, err := getBotByToken(token) - if err != nil { - c.Error(err) - return - } - - if b.ID == 0 { - c.AbortWithStatus(http.StatusOK) - return - } - - conn := getConnectionById(b.ConnectionID) - if !conn.Active { - c.AbortWithStatus(http.StatusOK) - return - } - - var update tgbotapi.Update - if err := c.ShouldBindJSON(&update); err != nil { - c.Error(err) - return - } - - if config.Debug { - logger.Debugf("mgWebhookHandler request: %v", update) - } - - var client = v1.New(conn.MGURL, conn.MGToken) - - if update.Message != nil { - if update.Message.Text == "" { - setLocale(update.Message.From.LanguageCode) - update.Message.Text = getLocalizedMessage(getMessageID(update.Message)) - } - - nickname := update.Message.From.UserName - user := getUserByExternalID(update.Message.From.ID) - - if update.Message.From.UserName == "" { - nickname = update.Message.From.FirstName - } - - if user.Expired(config.UpdateInterval) || user.ID == 0 { - fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID) - if err != nil { - c.Error(err) - return - } - - if fileID != user.UserPhotoID && fileURL != "" { - picURL, err := UploadUserAvatar(fileURL) - if err != nil { - c.Error(err) - return - } - - user.UserPhotoID = fileID - user.UserPhotoURL = picURL - } - - if user.ExternalID == 0 { - user.ExternalID = update.Message.From.ID - } - - err = user.save() - if err != nil { - c.Error(err) - return - } - } - - lang := update.Message.From.LanguageCode - - if len(update.Message.From.LanguageCode) > 2 { - lang = update.Message.From.LanguageCode[:2] - } - - if config.Debug { - logger.Debugf("telegramWebhookHandler user %v", user) - } - - 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: nickname, - Firstname: update.Message.From.FirstName, - Avatar: user.UserPhotoURL, - Lastname: update.Message.From.LastName, - Language: lang, - }, - Channel: b.Channel, - ExternalChatID: strconv.FormatInt(update.Message.Chat.ID, 10), - } - - if update.Message.ReplyToMessage != nil { - snd.Quote = &v1.SendMessageRequestQuote{ExternalID: strconv.Itoa(update.Message.ReplyToMessage.MessageID)} - } - - data, st, err := client.Messages(snd) - if err != nil { - logger.Error(b.Token, err.Error(), st, data) - c.Error(err) - return - } - - if config.Debug { - logger.Debugf("telegramWebhookHandler Type: SendMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data) - } - } - - if update.EditedMessage != nil { - if update.EditedMessage.Text == "" { - setLocale(update.EditedMessage.From.LanguageCode) - update.EditedMessage.Text = getLocalizedMessage(getMessageID(update.Message)) - } - - 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, st, err := client.UpdateMessages(snd) - if err != nil { - logger.Error(b.Token, err.Error(), st, data) - c.Error(err) - return - } - - if config.Debug { - logger.Debugf("telegramWebhookHandler Type: UpdateMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data) - } - } - - c.JSON(http.StatusOK, gin.H{}) -} - -func mgWebhookHandler(c *gin.Context) { - clientID := c.GetHeader("Clientid") - if clientID == "" { - c.AbortWithStatus(http.StatusBadRequest) - return - } - - conn := getConnection(clientID) - if !conn.Active { - c.AbortWithStatus(http.StatusBadRequest) - return - } - - var msg v1.WebhookRequest - if err := c.ShouldBindJSON(&msg); err != nil { - c.Error(err) - return - } - - if config.Debug { - logger.Debugf("mgWebhookHandler request: %v", msg) - } - - uid, _ := strconv.Atoi(msg.Data.ExternalMessageID) - cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64) - - b := getBot(conn.ID, msg.Data.ChannelID) - if b.ID == 0 { - c.AbortWithStatus(http.StatusBadRequest) - return - } - - bot, err := tgbotapi.NewBotAPI(b.Token) - if err != nil { - c.Error(err) - return - } - - if msg.Type == "message_sent" { - m := tgbotapi.NewMessage(cid, msg.Data.Content) - - if msg.Data.QuoteExternalID != "" { - qid, err := strconv.Atoi(msg.Data.QuoteExternalID) - if err != nil { - c.Error(err) - return - } - m.ReplyToMessageID = qid - } - - msgSend, err := bot.Send(m) - if err != nil { - logger.Error(err) - c.AbortWithStatus(http.StatusBadRequest) - return - } - - if config.Debug { - logger.Debugf("mgWebhookHandler sent %v", msgSend) - } - - c.JSON(http.StatusOK, gin.H{"external_message_id": strconv.Itoa(msgSend.MessageID)}) - } - - if msg.Type == "message_updated" { - msgSend, err := bot.Send(tgbotapi.NewEditMessageText(cid, uid, msg.Data.Content)) - if err != nil { - logger.Error(err) - c.AbortWithStatus(http.StatusBadRequest) - return - } - - if config.Debug { - logger.Debugf("mgWebhookHandler update %v", msgSend) - } - - c.AbortWithStatus(http.StatusOK) - } - - if msg.Type == "message_deleted" { - msgSend, err := bot.Send(tgbotapi.NewDeleteMessage(cid, uid)) - if err != nil { - logger.Error(err) - c.AbortWithStatus(http.StatusBadRequest) - return - } - - if config.Debug { - logger.Debugf("mgWebhookHandler delete %v", msgSend) - } - - c.JSON(http.StatusOK, gin.H{}) - } -} - -//GetFileIDAndURL function -func GetFileIDAndURL(token string, userID int) (fileID, fileURL string, err error) { - bot, err := tgbotapi.NewBotAPI(token) - if err != nil { - return - } - - bot.Debug = config.Debug - - res, err := bot.GetUserProfilePhotos( - tgbotapi.UserProfilePhotosConfig{ - UserID: userID, - Limit: 1, - }, - ) - if err != nil { - return - } - - if config.Debug { - logger.Debugf("GetFileIDAndURL Photos: %v", res.Photos) - } - - if len(res.Photos) > 0 { - fileID = res.Photos[0][len(res.Photos[0])-1].FileID - fileURL, err = bot.GetFileDirectURL(fileID) - } - - return -} - -//UploadUserAvatar function -func UploadUserAvatar(url string) (picURLs3 string, err error) { - s3Config := &aws.Config{ - Credentials: credentials.NewStaticCredentials( - config.ConfigAWS.AccessKeyID, - config.ConfigAWS.SecretAccessKey, - ""), - Region: aws.String(config.ConfigAWS.Region), - } - - s := session.Must(session.NewSession(s3Config)) - uploader := s3manager.NewUploader(s) - - resp, err := http.Get(url) - if err != nil { - return - } - defer resp.Body.Close() - - if resp.StatusCode >= http.StatusBadRequest { - return "", errors.New(fmt.Sprintf("get: %v code: %v", url, resp.StatusCode)) - } - - result, err := uploader.Upload(&s3manager.UploadInput{ - Bucket: aws.String(config.ConfigAWS.Bucket), - Key: aws.String(fmt.Sprintf("%v/%v.jpg", config.ConfigAWS.FolderName, GenerateToken())), - Body: resp.Body, - ContentType: aws.String(config.ConfigAWS.ContentType), - ACL: aws.String("public-read"), - }) - if err != nil { - return - } - - picURLs3 = result.Location - - return -} - -func getMessageID(data *tgbotapi.Message) string { - switch { - case data.Sticker != nil: - return "sticker" - case data.Audio != nil: - return "audio" - case data.Contact != nil: - return "contact" - case data.Document != nil: - return "document" - case data.Location != nil: - return "location" - case data.Video != nil: - return "video" - case data.Voice != nil: - return "voice" - case data.Photo != nil: - return "photo" - default: - return "undefined" - } -}