Skip to content
Snippets Groups Projects
main.go 3.49 KiB
Newer Older
  • Learn to ignore specific revisions
  • Lars Seipel's avatar
    Lars Seipel committed
    package main
    
    import (
    	"encoding/hex"
    	"flag"
    	"fmt"
    	"net/http"
    	"os"
    	"time"
    
    	"code.fbi.h-da.de/its/bbbatscale-support-notify/webhooks"
    	"go.uber.org/zap"
    	"maunium.net/go/mautrix"
    	"maunium.net/go/mautrix/id"
    )
    
    var (
    	matrixHomeserver = flag.String("homeserver", os.Getenv("MATRIX_HOMESERVER"),
    		"Matrix homeserver")
    	matrixUserID = flag.String("userid", os.Getenv("MATRIX_USERID"),
    		"Matrix user ID")
    	matrixToken = flag.String("token", os.Getenv("MATRIX_TOKEN"),
    		"Matrix access token")
    	matrixRoomID = flag.String("roomid", os.Getenv("MATRIX_ROOMID"),
    		"Matrix room ID")
    
    	laddr = flag.String("listen",
    		getEnvOrDefault("NOTIFY_LISTEN_ADDR", ":8443"), "listen for requests on this `addr`")
    	noTLS = flag.Bool("notls", os.Getenv("NOTIFY_NOTLS") != "",
    		"serve plain-text HTTP instead of HTTPS")
    	certFile = flag.String("cert",
    		os.Getenv("NOTIFY_CERT"), "X.509 certificate to present to clients (PEM-encoded)")
    	keyFile = flag.String("key",
    		os.Getenv("NOTIFY_KEY"), "private key matching configured cert")
    
    	hookSecret = flag.String("secret",
    		os.Getenv("NOTIFY_HOOKSECRET"), "hex-encoded secret for authenticating webhooks")
    )
    
    func main() {
    	logger, err := zap.NewProduction()
    	if err != nil {
    		// Just tear it down already.
    		fmt.Fprintln(os.Stderr, err)
    		os.Exit(1)
    	}
    	zap.RedirectStdLog(logger)
    	log := logger.Sugar()
    
    	flag.Parse()
    
    	m, err := mautrix.NewClient(*matrixHomeserver, id.UserID(*matrixUserID),
    		*matrixToken)
    	if err != nil {
    		log.Fatalw("mautrix.NewClient returned with error",
    			"err", err,
    			"homeserver", *matrixHomeserver,
    			"userid", *matrixUserID,
    		)
    	}
    	n := &notifier{
    		matrix: m,
    		roomID: id.RoomID(*matrixRoomID),
    	}
    
    	secretKey, err := hex.DecodeString(*hookSecret)
    	if err != nil {
    		log.Fatal("decode hook secret: ", err)
    	}
    	ch, handler, err := webhooks.Receive(
    		webhooks.Authenticate(secretKey),
    		webhooks.WithLogger(log.Desugar()),
    	)
    	if err != nil {
    		log.Fatal("webhooks.Receive: ", err)
    	}
    
    	go func() {
    		for message := range ch {
    			switch message.Event {
    			case "SUPPORT_CHAT_INCOMING_MESSAGE":
    				chatMsg := webhookPayloadToChatMessage(message.Payload)
    				if err := n.notify(chatMsg); err != nil {
    					log.Error("notify: ", err)
    				}
    			default:
    				log.Info("received message for event: ", message.Event)
    			}
    		}
    	}()
    
    	http.Handle("/notify", handler)
    	if *noTLS {
    		err = http.ListenAndServe(*laddr, nil)
    	} else {
    		err = http.ListenAndServeTLS(*laddr, *certFile, *keyFile, nil)
    	}
    	if err != nil {
    		log.Fatalw("ListenAndServe failed",
    			"err", err,
    			"addr", *laddr,
    			"tls", !*noTLS,
    			"cert", *certFile,
    			"key", *keyFile,
    		)
    	}
    
    }
    
    type notifier struct {
    	matrix *mautrix.Client
    	roomID id.RoomID
    }
    
    func (n *notifier) notify(m chatMessage) error {
    	const notificationFmt = "Incoming support message from %s (%s):\n> %s"
    	msg := fmt.Sprintf(notificationFmt, m.user, m.displayName, m.message)
    	_, err := n.matrix.SendNotice(n.roomID, msg)
    	return err
    }
    
    type chatMessage struct {
    	user        string
    	displayName string
    	message     string
    	timestamp   time.Time
    }
    
    func webhookPayloadToChatMessage(payload map[string]interface{}) chatMessage {
    	var m chatMessage
    	m.user, _ = payload["username"].(string)
    	m.displayName, _ = payload["userDisplayName"].(string)
    	m.message, _ = payload["message"].(string)
    	if timestamp, ok := payload["timestamp"].(string); ok {
    		if t, err := time.Parse(time.RFC3339, timestamp); err == nil {
    			m.timestamp = t
    		}
    	}
    
    	return m
    }
    
    func getEnvOrDefault(key, def string) string {
    	if v := os.Getenv(key); v != "" {
    		return v
    	}
    	return def
    }