Files
wehub-resource-sync 75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

160 lines
3.7 KiB
Go

package telegram_notifier
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/google/uuid"
"databasus-backend/internal/util/encryption"
)
type TelegramNotifier struct {
NotifierID uuid.UUID `json:"notifierId" gorm:"primaryKey;column:notifier_id"`
BotToken string `json:"botToken" gorm:"not null;column:bot_token"`
TargetChatID string `json:"targetChatId" gorm:"not null;column:target_chat_id"`
ThreadID *int64 `json:"threadId" gorm:"column:thread_id"`
IsProxyEnabled bool `json:"isProxyEnabled" gorm:"column:is_proxy_enabled;type:boolean;not null;default:false"`
ProxyURL string `json:"proxyUrl" gorm:"column:proxy_url;type:text"`
}
func (t *TelegramNotifier) TableName() string {
return "telegram_notifiers"
}
func (t *TelegramNotifier) Validate(encryptor encryption.FieldEncryptor) error {
if t.BotToken == "" {
return errors.New("bot token is required")
}
if t.TargetChatID == "" {
return errors.New("target chat ID is required")
}
if t.IsProxyEnabled {
if t.ProxyURL == "" {
return errors.New("proxy URL is required")
}
proxyURL, err := encryptor.Decrypt(t.ProxyURL)
if err != nil {
return fmt.Errorf("failed to decrypt proxy URL: %w", err)
}
if _, err := parseProxyURL(proxyURL); err != nil {
return err
}
}
return nil
}
func (t *TelegramNotifier) Send(
encryptor encryption.FieldEncryptor,
logger *slog.Logger,
heading string,
message string,
) error {
botToken, err := encryptor.Decrypt(t.BotToken)
if err != nil {
return fmt.Errorf("failed to decrypt bot token: %w", err)
}
fullMessage := heading
if message != "" {
fullMessage = fmt.Sprintf("%s\n\n%s", heading, message)
}
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botToken)
data := url.Values{}
data.Set("chat_id", t.TargetChatID)
data.Set("text", fullMessage)
data.Set("parse_mode", "HTML")
if t.ThreadID != nil && *t.ThreadID != 0 {
data.Set("message_thread_id", strconv.FormatInt(*t.ThreadID, 10))
}
req, err := http.NewRequestWithContext(context.Background(), "POST", apiURL, strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client, err := t.buildHTTPClient(encryptor)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send telegram message: %w", err)
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf(
"telegram API returned non-OK status: %s. Error: %s",
resp.Status,
string(bodyBytes),
)
}
return nil
}
func (t *TelegramNotifier) HideSensitiveData() {
t.BotToken = ""
t.ProxyURL = ""
}
func (t *TelegramNotifier) Update(incoming *TelegramNotifier) {
t.TargetChatID = incoming.TargetChatID
t.ThreadID = incoming.ThreadID
t.IsProxyEnabled = incoming.IsProxyEnabled
if !incoming.IsProxyEnabled {
t.ProxyURL = ""
} else if incoming.ProxyURL != "" {
t.ProxyURL = incoming.ProxyURL
}
if incoming.BotToken != "" {
t.BotToken = incoming.BotToken
}
}
func (t *TelegramNotifier) EncryptSensitiveData(encryptor encryption.FieldEncryptor) error {
if t.BotToken != "" {
encrypted, err := encryptor.Encrypt(t.BotToken)
if err != nil {
return fmt.Errorf("failed to encrypt bot token: %w", err)
}
t.BotToken = encrypted
}
if !t.IsProxyEnabled {
t.ProxyURL = ""
} else if t.ProxyURL != "" {
encrypted, err := encryptor.Encrypt(t.ProxyURL)
if err != nil {
return fmt.Errorf("failed to encrypt proxy URL: %w", err)
}
t.ProxyURL = encrypted
}
return nil
}