fiumba
This commit is contained in:
271
src/client/notify/google/google_chat.go
Normal file
271
src/client/notify/google/google_chat.go
Normal file
@ -0,0 +1,271 @@
|
||||
// Package googlechat provides functionality to send google chat notifications
|
||||
package googlechat
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
|
||||
"quantex.com/qfixdpl/src/app/version"
|
||||
common "quantex.com/qfixdpl/src/client/notify"
|
||||
"quantex.com/qfixdpl/src/common/logger"
|
||||
"quantex.com/qfixdpl/src/common/tracerr"
|
||||
"quantex.com/qfixdpl/src/domain"
|
||||
)
|
||||
|
||||
//revive:disable:line-length-limit It's just links
|
||||
|
||||
type SpaceID string
|
||||
|
||||
// spaces URLs
|
||||
const googleChatURL = "https://chat.googleapis.com/v1/spaces/"
|
||||
|
||||
type Notify struct {
|
||||
spaces map[domain.MessageChannel]SpaceID
|
||||
messages map[string]time.Time
|
||||
m deadlock.Mutex
|
||||
}
|
||||
|
||||
//nolint:lll // It's just a link
|
||||
func New(chls domain.Channels) *Notify {
|
||||
if err := common.ValidateChannelConfig(chls); err != nil {
|
||||
panic(tracerr.Errorf("google config error: %w", err))
|
||||
}
|
||||
|
||||
messages := make(map[string]time.Time)
|
||||
spaces := make(map[domain.MessageChannel]SpaceID)
|
||||
|
||||
spaces[domain.MessageChannelTest] = SpaceID(chls.Test)
|
||||
spaces[domain.MessageChannelWeb] = SpaceID(chls.Web)
|
||||
spaces[domain.MessageChannelPanic] = SpaceID(chls.Panic)
|
||||
spaces[domain.MessageChannelError] = SpaceID(chls.Error)
|
||||
|
||||
return &Notify{
|
||||
spaces: spaces,
|
||||
messages: messages,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Notify) Example() {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
|
||||
go g.SendMsg(domain.MessageChannelTest, "Hello World!", domain.MessageStatusGood, &wg)
|
||||
go g.SendMsg(domain.MessageChannelTest, "Error", domain.MessageStatusStopper, &wg)
|
||||
go g.SendMsg(domain.MessageChannelTest, "Warning", domain.MessageStatusWarning, &wg)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (g *Notify) SendMsg(chat domain.MessageChannel, text string, status domain.MessageStatus, waitgroup *sync.WaitGroup) {
|
||||
var space SpaceID
|
||||
|
||||
if s, ok := g.spaces[chat]; !ok {
|
||||
space = g.spaces[domain.MessageChannelError]
|
||||
|
||||
err := tracerr.Errorf("error sending google notification, there's no space id for chat: %s", chat)
|
||||
|
||||
logger.ErrorWoNotifier("%v", err.Error())
|
||||
} else {
|
||||
space = s
|
||||
}
|
||||
|
||||
s := fmt.Sprintf("%s-%s-%s", text, status.String(), string(space))
|
||||
|
||||
if ok := g.shouldSendMessage(s); ok {
|
||||
go common.SendWithRetry(text, status, waitgroup, googleChatURL+string(space), getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:lll // It's just a link
|
||||
const (
|
||||
errImg = "https://media.gettyimages.com/id/1359003186/es/foto/illustration-of-a-tick-or-an-x-indicating-right-and-wrong.jpg?s=612x612&w=0&k=20&c=FdYIXI1qCPpVcY6phcIJom8sRhZw4hgYVtwOC6lNlmA="
|
||||
goodImg = "https://media.gettyimages.com/id/1352723074/es/foto/drawing-of-green-tick-check-mark.jpg?s=612x612&w=0&k=20&c=RgoCJ-n0OpZSClCWhJstoXAfiGrBZhnggUvOp2ooJqI="
|
||||
warnImg = "https://media.gettyimages.com/id/1407160246/es/vector/icono-de-tri%C3%A1ngulo-de-peligro.jpg?s=612x612&w=0&k=20&c=rI0IYmZmkg62txtNQFhyTbHx5oW311_OupBkbWODfjg="
|
||||
)
|
||||
|
||||
func getImage(status domain.MessageStatus) (string, error) {
|
||||
switch status {
|
||||
case domain.MessageStatusGood:
|
||||
return goodImg, nil
|
||||
case domain.MessageStatusWarning:
|
||||
return warnImg, nil
|
||||
case domain.MessageStatusStopper:
|
||||
return errImg, nil
|
||||
}
|
||||
|
||||
return "", tracerr.Errorf("unknown Message Status Type")
|
||||
}
|
||||
|
||||
func getMessage(text string, status domain.MessageStatus) string {
|
||||
image, err := getImage(status)
|
||||
if err != nil {
|
||||
err = tracerr.Errorf("error getting image at getMessage, error: %w", err)
|
||||
|
||||
logger.ErrorWoNotifier("%v", err.Error())
|
||||
|
||||
var e error
|
||||
|
||||
image, e = getImage(domain.MessageStatusWarning)
|
||||
if e != nil {
|
||||
e = tracerr.Errorf("error getting image at getMessage, error: %w", e)
|
||||
|
||||
logger.ErrorWoNotifier("%v", e.Error())
|
||||
}
|
||||
}
|
||||
|
||||
env := version.Environment()
|
||||
appVersion := version.Base()
|
||||
|
||||
text = strings.ReplaceAll(text, "'", "\"")
|
||||
|
||||
msg := `
|
||||
{
|
||||
'cardsV2': [{
|
||||
'cardId': 'createCardMessage',
|
||||
'card': {
|
||||
'header': {
|
||||
'title': 'qfixdpl',
|
||||
'subtitle': 'Notification',
|
||||
'imageUrl': '%s',
|
||||
'imageType': 'CIRCLE'
|
||||
},
|
||||
'sections': [
|
||||
{
|
||||
'widgets':[
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>Environment:</b> %s'
|
||||
}
|
||||
},
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>Message:</b> %s'
|
||||
}
|
||||
},
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>Build:</b> %s'
|
||||
}
|
||||
},
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>Time:</b> %s'
|
||||
}
|
||||
},
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>Hostname:</b> %s'
|
||||
}
|
||||
},
|
||||
{
|
||||
'textParagraph': {
|
||||
'text': '<b>IP:</b> %s'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
loc, err := time.LoadLocation("America/Argentina/Buenos_Aires")
|
||||
if err != nil {
|
||||
err := tracerr.Errorf("error loading timezone %v, setting default UTC", err)
|
||||
|
||||
logger.ErrorWoNotifier("%v", err.Error())
|
||||
|
||||
loc = time.UTC
|
||||
}
|
||||
|
||||
now := time.Now().In(loc).Format("15:04:05 02-01-2006 -0700")
|
||||
jsonMsg := fmt.Sprintf(msg, image, env, text, appVersion, now,
|
||||
getHostname(), getOutboundIP())
|
||||
|
||||
return jsonMsg
|
||||
}
|
||||
|
||||
func (g *Notify) shouldSendMessage(s string) (ok bool) {
|
||||
sha, err := generateShaString(s)
|
||||
if err != nil {
|
||||
err = tracerr.Errorf("error generating sha string at shouldSendMessage, error: %w", err)
|
||||
|
||||
logger.ErrorWoNotifier("%v", err.Error())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if t, ok := g.messages[sha]; ok && time.Since(t) < time.Minute {
|
||||
return false
|
||||
}
|
||||
|
||||
g.messages[sha] = time.Now()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func generateShaString(s string) (sha string, err error) {
|
||||
hash := sha256.New()
|
||||
if _, err := hash.Write([]byte(s)); err != nil {
|
||||
err = tracerr.Errorf("error generating hash at generateShaString, error: %w", err)
|
||||
logger.ErrorWoNotifier("%v", err.Error())
|
||||
|
||||
return sha, err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
//revive:enable:line-length-limit
|
||||
|
||||
//nolint:gochecknoglobals // need to be global
|
||||
var outboundIP string
|
||||
|
||||
func getOutboundIP() string {
|
||||
if outboundIP == "" {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
logger.ErrorWoNotifier("failed to get UDP address")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
defer func(conn net.Conn) {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
logger.ErrorWoNotifier("error closing net connection")
|
||||
}
|
||||
}(conn)
|
||||
|
||||
localAddr, ok := conn.LocalAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
logger.ErrorWoNotifier("error: conn.LocalAddr() is not a *net.UDPAddr type")
|
||||
}
|
||||
|
||||
outboundIP = localAddr.IP.String()
|
||||
}
|
||||
|
||||
return outboundIP
|
||||
}
|
||||
|
||||
func getHostname() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
logger.ErrorWoNotifier("failed to get hostname")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return hostname
|
||||
}
|
||||
Reference in New Issue
Block a user