Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions cmd/bots/internal/bots/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,30 +139,33 @@ func (dscBot *DiscordBotEnvironment) SendMessage(msg string) {
}
}

func (dscBot *DiscordBotEnvironment) sendErrMsg(msg string, err error) {
errMsg := fmt.Sprintf("Bot Error - %s: %v", msg, err)
dscBot.SendMessage(errMsg)
}

func (dscBot *DiscordBotEnvironment) SendAlertMessage(msg generalMessaging.AlertMessage) {
alertType := msg.AlertType()
if !alertType.Exist() {
dscBot.logger.Error("Failed to construct message, unknown alert type",
slog.Uint64("alertType", uint64(alertType)),
attrs.Error(errUnknownAlertType),
)
_, err := dscBot.Bot.ChannelMessageSend(dscBot.ChatID, errUnknownAlertType.Error())

if err != nil {
dscBot.logger.Error("Failed to send a message to discord", attrs.Error(err))
}
dscBot.sendErrMsg("Failed to construct message, unknown alert type", errUnknownAlertType)
return
}

nodes, err := messaging.RequestAllNodes(dscBot.requestType, dscBot.responsePairType)
if err != nil {
dscBot.logger.Error("Failed to get nodes list", attrs.Error(err))
dscBot.sendErrMsg("Failed to get nodes list", err)
}

alertJSON := msg.Data()
messageToBot, err := constructMessage(alertType, alertJSON, dscBot.TemplatesExtension(), nodes)
if err != nil {
dscBot.logger.Error("Failed to construct message", attrs.Error(err))
dscBot.sendErrMsg("Failed to construct message", err)
return
}
alertID := msg.ReferenceID()
Expand All @@ -173,6 +176,10 @@ func (dscBot *DiscordBotEnvironment) SendAlertMessage(msg generalMessaging.Alert
dscBot.logger.Error("Failed to get message ID by the given alertID: alertID hasn't been found",
attrs.Stringer("alertID", alertID),
)
dscBot.sendErrMsg(
"Failed to get message ID by the given alertID",
errors.Errorf("alertID %s hasn't been found", alertID),
)
return
}
msgRef := &discordgo.MessageReference{MessageID: strconv.Itoa(messageID)}
Expand Down Expand Up @@ -297,6 +304,11 @@ func NewTelegramBotEnvironment(
}
}

func (tgEnv *TelegramBotEnvironment) sendErrMsg(msg string, err error) {
errMsg := fmt.Sprintf("Bot Error - %s: %v", msg, err)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TelegramBotEnvironment.sendErrMsg builds a raw string and routes it through SendMessage(), which always sends with ParseMode HTML. Since error strings can contain characters like '<', '&', etc., Telegram may reject the message with an entity parsing error, defeating the purpose of notifying on failures. Consider escaping the error text for HTML (or sending error messages with ParseMode disabled / as preformatted text).

Suggested change
errMsg := fmt.Sprintf("Bot Error - %s: %v", msg, err)
escapedMsg := htmltmpl.HTMLEscapeString(msg)
escapedErr := htmltmpl.HTMLEscapeString(fmt.Sprint(err))
errMsg := fmt.Sprintf("Bot Error - %s: %s", escapedMsg, escapedErr)

Copilot uses AI. Check for mistakes.
tgEnv.SendMessage(errMsg)
}

func (tgEnv *TelegramBotEnvironment) TemplatesExtension() ExpectedExtension { return HTML }

func (tgEnv *TelegramBotEnvironment) Start(ctx context.Context) error {
Expand All @@ -323,14 +335,7 @@ func (tgEnv *TelegramBotEnvironment) SendAlertMessage(msg generalMessaging.Alert
tgEnv.logger.Error("Failed to construct message, unknown alert type",
slog.Uint64("alertType", uint64(alertType)), attrs.Error(errUnknownAlertType),
)
_, err := tgEnv.Bot.Send(
chat,
errUnknownAlertType.Error(),
&telebot.SendOptions{ParseMode: telebot.ModeHTML},
)
if err != nil {
tgEnv.logger.Error("Failed to send a message to telegram", attrs.Error(err))
}
tgEnv.sendErrMsg("Failed to construct message, unknown alert type", errUnknownAlertType)
return
}

Expand All @@ -343,6 +348,7 @@ func (tgEnv *TelegramBotEnvironment) SendAlertMessage(msg generalMessaging.Alert
messageToBot, err := constructMessage(alertType, alertJSON, tgEnv.TemplatesExtension(), nodes)
if err != nil {
tgEnv.logger.Error("Failed to construct message", attrs.Error(err))
tgEnv.sendErrMsg("Failed to construct message", err)
return
}
alertID := msg.ReferenceID()
Expand All @@ -353,6 +359,10 @@ func (tgEnv *TelegramBotEnvironment) SendAlertMessage(msg generalMessaging.Alert
tgEnv.logger.Error("Failed to get message ID by the given alertID: alertID hasn't been found",
attrs.Stringer("alertID", alertID),
)
tgEnv.sendErrMsg(
"Failed to get message ID by the given alertID",
errors.Errorf("alertID %s hasn't been found", alertID),
)
return
}
opts := &telebot.SendOptions{ReplyTo: &telebot.Message{ID: messageID}, ParseMode: telebot.ModeHTML}
Expand Down
9 changes: 9 additions & 0 deletions cmd/bots/internal/bots/messaging/pubsub_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package messaging
import (
"context"
"log/slog"
"time"

"github.com/nats-io/nats.go"
"github.com/pkg/errors"

"nodemon/pkg/entities"
"nodemon/pkg/messaging"
"nodemon/pkg/tools/logging/attrs"
)
Expand All @@ -24,6 +26,13 @@ func StartSubMessagingClient(ctx context.Context, natsServerURL string, bot Bot,
hndlErr := handleReceivedMessage(msg.Data, bot)
if hndlErr != nil {
logger.Error("Failed to handle received message from pubsub server", attrs.Error(hndlErr))
internalAlert := entities.NewInternalErrorAlert(time.Now().Unix(), hndlErr)
alertMsg, iErr := messaging.NewAlertMessageFromAlert(internalAlert)
if iErr != nil { // must never happen
logger.Error("Failed to create alert message for internal alert", attrs.Error(iErr))
return
}
bot.SendAlertMessage(alertMsg) // send alert about something not working in the messaging service
Comment on lines +29 to +35
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the NATS subscription handler, any parse/handle error now triggers sending an InternalErrorAlert to the bot chat. If the pubsub server publishes malformed/unexpected messages (or an attacker can publish to the subject), this can easily turn into an alert storm and drown out real alerts. Consider adding a simple rate-limit/deduplication (e.g., per error type within a time window) or restricting internal alerts to specific error classes that indicate a real service failure (vs. a single bad message).

Copilot uses AI. Check for mistakes.
}
}
bot.SetAlertHandlerFunc(alertHandlerFunc)
Expand Down
2 changes: 1 addition & 1 deletion pkg/messaging/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func NewAlertMessageFromAlert(alert entities.Alert) (AlertMessage, error) {
func NewAlertMessageFromBytes(msgData []byte) (AlertMessage, error) {
const minMsgSize = 1 + crypto.DigestSize
if l := len(msgData); l < minMsgSize {
return AlertMessage{}, errors.Errorf("message has inssufficient length: want at least %d, got %d", minMsgSize, l)
return AlertMessage{}, errors.Errorf("message has insufficient length: want at least %d, got %d", minMsgSize, l)
}
referenceID, err := crypto.NewDigestFromBytes(msgData[1 : 1+crypto.DigestSize])
if err != nil {
Expand Down
Loading