feat: add incoming webhook API support with configurable endpoints
Build / ubuntu-latest (push) Canceled after 1m11s
Build / windows-latest (push) Canceled after 1m36s

- Implemented the incoming webhook API to handle alerts from external applications.
- Added configuration options for webhook listening address, port, and endpoints in config.go.
- Created WebhookReceiverConfig and WebhookEndpointConfig structures to manage webhook settings.
- Developed WebhookHandler to process incoming requests, validate tokens, and deliver alerts to specified channels.
- Enhanced existing controller interfaces to support alert delivery.
- Updated message rendering to respect Markdown settings for different channels.
- Added tests for webhook functionality and ensured proper error handling.
This commit is contained in:
2026-09-24 11:21:38 +08:00
parent db352e870d
commit 48533404fa
17 changed files with 595 additions and 65 deletions
+16 -10
View File
@@ -14,11 +14,13 @@ import (
const maxMessageLen = 4000
// sendMessage sends a text message to a chat, splitting it into chunks that fit
// Telegram's 4096-character limit. All messages are sent with
// parse_mode=Markdown so fenced code blocks and inline formatting render as
// rich text. Templates must stay valid under Telegram's legacy Markdown:
// unpaired '*' or '_' characters (e.g. a lone '*Event: ...' label) make the
// API reject the whole message.
// Telegram's 4096-character limit. When the channel has markdown enabled the
// message is sent with parse_mode=Markdown so fenced code blocks and inline
// formatting render as rich text; templates must then stay valid under
// Telegram's legacy Markdown, because unpaired '*' or '_' characters (e.g. a
// lone '*Event: ...' label) make the API reject the whole message. With
// markdown disabled the message is sent without a parse mode, so it is
// delivered verbatim whatever it contains.
func (t *TelegramController) sendMessage(message controller.Message) error {
if t.client == nil {
return nil
@@ -40,11 +42,15 @@ func (t *TelegramController) sendMessageChunk(chatID int64, text string) error {
ctx, cancel := context.WithTimeout(context.Background(), apiTimeout)
defer cancel()
_, err := t.client.SendMessage(ctx, &bot.SendMessageParams{
ChatID: chatID,
Text: text,
ParseMode: models.ParseModeMarkdownV1, // Telegram legacy Markdown
})
params := &bot.SendMessageParams{
ChatID: chatID,
Text: text,
}
if t.cfg.Markdown {
params.ParseMode = models.ParseModeMarkdownV1 // Telegram legacy Markdown
}
_, err := t.client.SendMessage(ctx, params)
return err
}
+23 -3
View File
@@ -124,6 +124,12 @@ func (t *TelegramController) IsEnabled() bool {
return t.cfg.Enabled
}
// IsMarkdown returns whether the channel renders Markdown, per its markdown
// setting in config.json.
func (t *TelegramController) IsMarkdown() bool {
return t.cfg.Markdown
}
// handleUpdate processes a single Telegram update received via long polling. It
// is installed as the framework's default handler (every update with a Message
// reaches it). Updates are processed sequentially because the bot is created
@@ -271,7 +277,7 @@ func (t *TelegramController) SendStatusChange(change node.StatusChange) error {
cfg := config.C_globalConfig
params := template.BuildParamsFromStatusChange(change)
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params)
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, t.cfg.Markdown)
// Only notify trusted groups and admins whose event_status_notify is true.
if uc := config.C_botUserConfig; uc != nil {
@@ -299,7 +305,7 @@ func (t *TelegramController) SendServerList(onlineServers, offlineServers string
cfg := config.C_globalConfig
params := template.BuildParamsFromServerList()
message := template.Render(cfg.ControllerMessage.ServerList, params)
message := template.Render(cfg.ControllerMessage.ServerList, params, t.cfg.Markdown)
t.sendToGroups(message)
return nil
@@ -313,12 +319,26 @@ func (t *TelegramController) SendExecuteResult(serverName, serverUUID, command,
cfg := config.C_globalConfig
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params)
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, t.cfg.Markdown)
t.sendToGroups(message)
return nil
}
// SendAlert sends an alert submitted through the incoming webhook API to all
// Telegram trusted groups and admins.
func (t *TelegramController) SendAlert(alert controller.Alert) error {
if !t.cfg.Enabled || t.client == nil {
return nil
}
return t.SendMessage(controller.Message{
Source: t.Name(),
Content: alert.Render(t.cfg.Markdown),
Type: controller.MessageTypeAlert,
})
}
// telegramChatType maps a Telegram chat type to the unified ChatType value used
// by the controller package. Empty means the chat type is unsupported.
func telegramChatType(chatType string) string {