1
0
mirror of synced 2024-11-25 13:16:02 +03:00

Merge pull request #15 from DmitryZagorulko/master

sending avatars in mg, checking activity of bots and connections
This commit is contained in:
Alex Lushpai 2018-06-08 15:22:00 +03:00 committed by GitHub
commit 6257d19164
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 221 additions and 47 deletions

View File

@ -4,17 +4,29 @@ import (
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
logging "github.com/op/go-logging" "github.com/op/go-logging"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// TransportConfig struct // TransportConfig struct
type TransportConfig struct { type TransportConfig struct {
LogLevel logging.Level `yaml:"log_level"` LogLevel logging.Level `yaml:"log_level"`
Database DatabaseConfig `yaml:"database"` Database DatabaseConfig `yaml:"database"`
SentryDSN string `yaml:"sentry_dsn"` SentryDSN string `yaml:"sentry_dsn"`
HTTPServer HTTPServerConfig `yaml:"http_server"` HTTPServer HTTPServerConfig `yaml:"http_server"`
Debug bool `yaml:"debug"` Debug bool `yaml:"debug"`
UpdateInterval int `yaml:"update_interval"`
ConfigAWS ConfigAWS `yaml:"config_aws"`
}
// ConfigAWS struct
type ConfigAWS struct {
AccessKeyID string `yaml:"access_key_id"`
SecretAccessKey string `yaml:"secret_access_key"`
Region string `yaml:"region"`
Bucket string `yaml:"bucket"`
FolderName string `yaml:"folder_name"`
ContentType string `yaml:"content_type"`
} }
// DatabaseConfig struct // DatabaseConfig struct

View File

@ -10,3 +10,13 @@ sentry_dsn: ~
log_level: 5 log_level: 5
debug: false debug: false
update_interval: 24
config_aws:
access_key_id: ~
secret_access_key: ~
region: ~
bucket: ~
folder_name: ~
content_type: image/jpeg

View File

@ -10,3 +10,13 @@ sentry_dsn: ~
log_level: 5 log_level: 5
debug: false debug: false
update_interval: 24
config_aws:
access_key_id: ~
secret_access_key: ~
region: ~
bucket: ~
folder_name: ~
content_type: image/jpeg

View File

@ -25,21 +25,9 @@ func NewDb(config *TransportConfig) *Orm {
db.DB().SetMaxOpenConns(config.Database.MaxOpenConnections) db.DB().SetMaxOpenConns(config.Database.MaxOpenConnections)
db.DB().SetMaxIdleConns(config.Database.MaxIdleConnections) db.DB().SetMaxIdleConns(config.Database.MaxIdleConnections)
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return config.Database.TablePrefix + defaultTableName
}
db.SingularTable(true) db.SingularTable(true)
db.LogMode(config.Database.Logging) db.LogMode(config.Database.Logging)
setCreatedAt := func(scope *gorm.Scope) {
if scope.HasColumn("CreatedAt") {
scope.SetColumn("CreatedAt", time.Now())
}
}
db.Callback().Create().Replace("gorm:update_time_stamp", setCreatedAt)
return &Orm{ return &Orm{
DB: db, DB: db,
} }

View File

@ -0,0 +1 @@
drop table users;

View File

@ -0,0 +1,12 @@
create table users
(
id serial not null
constraint users_pkey
primary key,
external_id integer not null,
user_photo_url varchar(255),
user_photo_id varchar(100),
created_at timestamp with time zone,
updated_at timestamp with time zone,
constraint users_key unique(external_id)
);

View File

@ -28,5 +28,15 @@ type Bot struct {
Active bool `json:"active,omitempty"` Active bool `json:"active,omitempty"`
} }
// Users model
type Users struct {
ID int `gorm:"primary_key"`
ExternalID int `gorm:"external_id;not null;unique"`
UserPhotoURL string `gorm:"user_photo_url type:varchar(255);unique"`
UserPhotoID string `gorm:"user_photo_id type:varchar(100);unique"`
CreatedAt time.Time
UpdatedAt time.Time
}
//Bots list //Bots list
type Bots []Bot type Bots []Bot

View File

@ -1,6 +1,10 @@
package main package main
import "github.com/jinzhu/gorm" import (
"time"
"github.com/jinzhu/gorm"
)
func getConnection(uid string) *Connection { func getConnection(uid string) *Connection {
var connection Connection var connection Connection
@ -78,3 +82,19 @@ func getConnectionById(id int) *Connection {
return &connection return &connection
} }
func (u *Users) save() error {
return orm.DB.Save(u).Error
}
func getUserByExternalID(eid int) *Users {
user := Users{ExternalID: eid}
orm.DB.First(&user)
return &user
}
//Expired method
func (u *Users) Expired(updateInterval int) bool {
return time.Now().After(u.UpdatedAt.Add(time.Hour * time.Duration(updateInterval)))
}

View File

@ -115,6 +115,7 @@ func connectHandler(w http.ResponseWriter, r *http.Request) {
} }
func addBotHandler(w http.ResponseWriter, r *http.Request) { func addBotHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
setLocale(r.Header.Get("Accept-Language")) setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@ -152,7 +153,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
bot, err := GetBotInfo(b.Token) bot, err := tgbotapi.NewBotAPI(b.Token)
if err != nil { if err != nil {
logger.Error(b.Token, err.Error()) logger.Error(b.Token, err.Error())
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusBadRequest) http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusBadRequest)
@ -220,6 +221,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) {
} }
func activityBotHandler(w http.ResponseWriter, r *http.Request) { func activityBotHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
setLocale(r.Header.Get("Accept-Language")) setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@ -278,8 +280,8 @@ func activityBotHandler(w http.ResponseWriter, r *http.Request) {
err = b.setBotActivity() err = b.setBotActivity()
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
logger.Error(b.ID, err.Error())
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError) http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
logger.Error(b.ID, err.Error())
return return
} }
@ -321,6 +323,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
} }
func saveHandler(w http.ResponseWriter, r *http.Request) { func saveHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
setLocale(r.Header.Get("Accept-Language")) setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
@ -365,6 +368,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
} }
func createHandler(w http.ResponseWriter, r *http.Request) { func createHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
setLocale(r.Header.Get("Accept-Language")) setLocale(r.Header.Get("Accept-Language"))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
@ -483,6 +487,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) {
} }
func activityHandler(w http.ResponseWriter, r *http.Request) { func activityHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
setLocale(r.Header.Get("Accept-Language")) setLocale(r.Header.Get("Accept-Language"))
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
res := Response{Success: false} res := Response{Success: false}
@ -530,7 +535,7 @@ func activityHandler(w http.ResponseWriter, r *http.Request) {
} }
c := getConnection(rec.ClientId) c := getConnection(rec.ClientId)
c.Active = rec.Activity.Active c.Active = rec.Activity.Active && !rec.Activity.Freeze
if err := c.setConnectionActivity(); err != nil { if err := c.setConnectionActivity(); err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)

View File

@ -2,11 +2,17 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"time" "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/getsentry/raven-go" "github.com/getsentry/raven-go"
"github.com/go-telegram-bot-api/telegram-bot-api" "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/retailcrm/mg-transport-api-client-go/v1" "github.com/retailcrm/mg-transport-api-client-go/v1"
@ -17,21 +23,13 @@ func setTransportRoutes() {
http.HandleFunc("/webhook/", mgWebhookHandler) http.HandleFunc("/webhook/", mgWebhookHandler)
} }
// GetBotInfo function
func GetBotInfo(token string) (*tgbotapi.BotAPI, error) {
bot, err := tgbotapi.NewBotAPI(token)
if err != nil {
return nil, err
}
return bot, nil
}
// GetBotName function // GetBotName function
func GetBotName(bot *tgbotapi.BotAPI) string { func GetBotName(bot *tgbotapi.BotAPI) string {
return bot.Self.FirstName return bot.Self.FirstName
} }
func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) { func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) {
defer r.Body.Close()
b, err := getBotByToken(token) b, err := getBotByToken(token)
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
@ -40,21 +38,15 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
return return
} }
if b.ID == 0 { if b.ID == 0 || !b.Active {
logger.Error(token, "missing") logger.Error(token, "missing or deactivated")
w.WriteHeader(http.StatusBadRequest)
return
}
if !b.Active {
logger.Error(token, "deactivated")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
c := getConnectionById(b.ConnectionID) c := getConnectionById(b.ConnectionID)
if c.MGURL == "" || c.MGToken == "" { if !c.Active {
logger.Error(token, "MGURL or MGToken is empty") logger.Error(c.ClientID, "connection deativated")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -64,8 +56,8 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
bytes, err := ioutil.ReadAll(r.Body) bytes, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
w.WriteHeader(http.StatusInternalServerError)
logger.Error(token, err) logger.Error(token, err)
w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -81,6 +73,44 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
return return
} }
user := getUserByExternalID(update.Message.From.ID)
if user.Expired(config.UpdateInterval) || user.ID == 0 {
fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if fileID != user.UserPhotoID && fileURL != "" {
picURL, err := UploadUserAvatar(fileURL)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
user.UserPhotoID = fileID
user.UserPhotoURL = picURL
}
if user.ExternalID == 0 {
user.ExternalID = update.Message.From.ID
}
err = user.save()
if err != nil {
raven.CaptureErrorAndWait(err, nil)
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
var client = v1.New(c.MGURL, c.MGToken) var client = v1.New(c.MGURL, c.MGToken)
if update.Message != nil { if update.Message != nil {
@ -98,6 +128,7 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
ExternalID: strconv.Itoa(update.Message.From.ID), ExternalID: strconv.Itoa(update.Message.From.ID),
Nickname: update.Message.From.UserName, Nickname: update.Message.From.UserName,
Firstname: update.Message.From.FirstName, Firstname: update.Message.From.FirstName,
Avatar: user.UserPhotoURL,
Lastname: update.Message.From.LastName, Lastname: update.Message.From.LastName,
Language: update.Message.From.LanguageCode, Language: update.Message.From.LanguageCode,
}, },
@ -150,10 +181,12 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
} }
func mgWebhookHandler(w http.ResponseWriter, r *http.Request) { func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
bytes, err := ioutil.ReadAll(r.Body) bytes, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
logger.Error(err) logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -166,6 +199,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
logger.Error(err) logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -173,13 +207,18 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64) cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64)
b := getBotByChannel(msg.Data.ChannelID) b := getBotByChannel(msg.Data.ChannelID)
if b.ID == 0 { if b.ID == 0 || !b.Active {
logger.Error(msg.Data.ChannelID, "missing") logger.Error(msg.Data.ChannelID, "missing or deactivated")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("missing or deactivated"))
return return
} }
if !b.Active { c := getConnectionById(b.ConnectionID)
logger.Error(msg.Data.ChannelID, "deactivated") if !c.Active {
logger.Error(c.ClientID, "connection deativated")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Connection deactivated"))
return return
} }
@ -187,6 +226,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
raven.CaptureErrorAndWait(err, nil) raven.CaptureErrorAndWait(err, nil)
logger.Error(err) logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -252,3 +292,69 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Message deleted")) w.Write([]byte("Message deleted"))
} }
} }
//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 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
}