diff --git a/go.mod b/go.mod index c8fe25e..679e143 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/yuin/goldmark v1.7.4 // indirect go.mau.fi/util v0.7.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect diff --git a/go.sum b/go.sum index b74992e..f4fb2bb 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws= go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= diff --git a/internal/matrix/client.go b/internal/matrix/client.go index 8b0c5a6..74e370f 100644 --- a/internal/matrix/client.go +++ b/internal/matrix/client.go @@ -11,16 +11,24 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/sebhoss/matrix-alertmanager-receiver/internal/config" - "html" "log/slog" "maunium.net/go/mautrix" "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" "os" - "regexp" + "slices" ) var ( + joinRoomSuccessTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "matrix_alertmanager_receiver_join_room_success_total", + Help: "The total number of successful join room operations", + }, []string{"room"}) + joinRoomFailureTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "matrix_alertmanager_receiver_join_room_failure_total", + Help: "The total number of failed join room operations", + }, []string{"room"}) sendSuccessTotal = promauto.NewCounter(prometheus.CounterOpts{ Name: "matrix_alertmanager_receiver_send_success_total", Help: "The total number of successful send operations", @@ -33,38 +41,22 @@ var ( type SendingFunc func(htmlText string, roomID string) -// An HTMLMessage is the contents of a Matrix HTML formated message event. -type HTMLMessage struct { - Body string `json:"body"` - MsgType string `json:"msgtype"` - Format string `json:"format"` - FormattedBody string `json:"formatted_body"` -} - -var htmlRegex = regexp.MustCompile("<[^<]+?>") - -// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition -// to the provided HTML. -func GetHTMLMessage(msgtype, htmlText string) HTMLMessage { - return HTMLMessage{ - Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")), - MsgType: msgtype, - Format: "org.matrix.custom.html", - FormattedBody: htmlText, - } -} +var joinedRoomIDs []string func CreatingSendingFunc(ctx context.Context, configuration config.Matrix) SendingFunc { matrixClient := createMatrixClient(ctx, configuration) + fetchJoinedRooms(ctx, matrixClient) return func(htmlText string, room string) { mappedRoom := room if mapped, ok := configuration.RoomMapping[room]; ok { mappedRoom = mapped } - if _, err := matrixClient.JoinRoom(ctx, mappedRoom, "", nil); err != nil { + if err := joinRoom(ctx, matrixClient, mappedRoom); err != nil { + joinRoomFailureTotal.WithLabelValues(mappedRoom).Inc() slog.ErrorContext(ctx, fmt.Sprintf("Could not join room %s", room), slog.Any("error", err)) } else { - if respSendEvent, err := matrixClient.SendMessageEvent(ctx, id.RoomID(room), event.NewEventType("m.room.message"), GetHTMLMessage("m.text", htmlText)); err != nil { + joinRoomSuccessTotal.WithLabelValues(mappedRoom).Inc() + if respSendEvent, err := matrixClient.SendMessageEvent(ctx, id.RoomID(mappedRoom), event.NewEventType("m.room.message"), format.HTMLToContent(htmlText)); err != nil { sendFailureTotal.Inc() slog.ErrorContext(ctx, "Could not send message to Matrix homeserver", slog.Any("error", err)) } else { @@ -82,8 +74,30 @@ func createMatrixClient(ctx context.Context, configuration config.Matrix) *mautr if matrixClient, err = mautrix.NewClient(configuration.HomeServerURL, id.UserID(configuration.UserID), configuration.AccessToken); err != nil { slog.ErrorContext(ctx, "Failed to create matrix client", slog.Any("error", err)) os.Exit(1) - } else { - slog.DebugContext(ctx, "Created Matrix client") } + slog.DebugContext(ctx, "Created Matrix client") return matrixClient } + +func fetchJoinedRooms(ctx context.Context, client *mautrix.Client) { + joinedRooms, err := client.JoinedRooms(ctx) + if err != nil { + slog.ErrorContext(ctx, "Could not fetch Matrix rooms", slog.Any("error", err)) + os.Exit(1) + } + for _, roomID := range joinedRooms.JoinedRooms { + joinedRoomIDs = append(joinedRoomIDs, roomID.String()) + } +} + +func joinRoom(ctx context.Context, client *mautrix.Client, roomToJoin string) error { + if !slices.Contains(joinedRoomIDs, roomToJoin) { + slog.DebugContext(ctx, "Joining room", slog.String("room", roomToJoin)) + _, err := client.JoinRoom(ctx, roomToJoin, "", nil) + if err != nil { + return err + } + joinedRoomIDs = append(joinedRoomIDs, roomToJoin) + } + return nil +}